ion.app¶
This module is the configuration entry point. It hosts both persistent application services - lifecycle and environment functions plus dynamic data - and the declarative registries that the config file populates and the compositor consults at runtime. The registries do not accumulate across reloads: they are cleared and re-declared each time the config runs.
- When code runs
Top-level config code runs at load time - that is where the declarative registries and preferences are populated. Live state in
ion.wmonly exists once the compositor is running, so window, frame, and workspace manipulation belongs in callbacks (keybindings, hooks, operatorexec) that fire later, not at config load.- Reloads
reload()re-runs the config file. Declarative state is cleared first (seereload()for what is cleared), so the config is always the complete declaration of those settings rather than an addition to the previous load. Useis_init_startupto gate one-shot setup that should not re-run on each reload.
Application settings.
- ion.app.is_init¶
True during config loading (both initial startup and reload), False otherwise.
- Type:
bool
- ion.app.is_init_startup¶
True only during the initial startup config load, False during reload and at runtime.
- Type:
bool
- ion.app.ipc_args¶
Arguments passed to the current IPC command. Empty list outside of IPC execution.
- Type:
list[str]
Functions¶
- ion.app.config_dir()¶
Config directory (
XDG_CONFIG_HOME/ionwlor~/.config/ionwl).- Return type:
str
- ion.app.data_dir()¶
Data directory containing resources and bundled Python packages. Resolved once at startup from the XDG data directory search path.
- Return type:
str
- ion.app.logout()¶
Graceful logout: request all windows to close, then exit when none remain. Windows that need confirmation (unsaved files etc.) will stay open until the user handles them.
- Return type:
None
- ion.app.quit()¶
Quit the compositor.
- ion.app.reload()¶
Reload the configuration file.
All keybindings, hooks, and menus are cleared before the config re-runs, so the config file is always the complete declaration of these settings. Check
ion.app.is_init_startupto gate actions that should only run on initial startup.Note
This action is deferred until the current callback returns.
- ion.app.version()¶
Get IonWL version.
- Return type:
str
Data¶
- ion.app.decor¶
- class ion.app.Decor¶
Decoration configuration (geometry and visual properties).
- Type:
- ion.app.hooks¶
Namespace exposing all hooks as attributes, e.g.
ion.app.hooks.window_new_post.The set of hooks is fixed at compile time. Users attach callbacks to a
ion.types.Hookviaion.types.Hook.append(); they do not add or remove hooks themselves.- Type:
Output Hotplug
Use the
output_add_prehook to configure monitors as they are connected. The callback receives theion.types.Outputobject before it is used, so properties like position and transform take effect immediately.import ion @ion.app.hooks.output_add_pre.append def configure_output(output: ion.types.Output) -> None: if output.name == "DP-2": output.configure(position=(0, 0), transform=90) elif output.name == "HDMI-A-1": output.configure(position=(2560, 0))
Startup Layout
Use the
compositor_startup_posthook to create desktops and workspaces when the compositor starts. Desktop 0 always exists, so only additional desktops need to be created explicitly.import ion from ion.types import Rect @ion.app.hooks.compositor_startup_post.append def on_startup() -> None: # Desktop 0 already exists; create three more. desktops = [ion.wm.desktops[0]] for i in range(3): desktops.append(ion.wm.desktops.new(str(i + 2))) # Give each desktop a named workspace with a horizontal split. for i, desktop in enumerate(desktops): desktop.name = str(i + 1) ws = ion.wm.workspaces.new(Rect((0, 0), (800, 600))) desktop.items_tiling.add(ws) node = ws.frames[0].split_node assert node is not None node.split(0, 1)
- ion.app.keymaps¶
Registry for keymaps, accessed as
ion.app.keymaps["global"],ion.app.keymaps["window"], etc.Keymaps are checked in registration order (first registered = highest priority). Standard keymaps:
"global": Always active, lowest priority."window": Active when any window has keyboard focus."tiling": Active when focus is on a tiled frame."floating": Active when focus is on a floating frame."fullscreen": Active when a fullscreen window has focus."tab": Active when the pointer is over a tab."title": Active when the pointer is over a frame title bar."root": Active when the pointer is over the empty background."divider": Active when hovering a split divider."decoration": Active when the pointer is over a window decoration.
The registry follows the dict protocol: iteration yields keymap names,
"name" in keymapstests membership,keymaps["name"]returns theKeymap, andkeys()/values()/items()expose the three views.Basic Keybindings
Set up keyboard and pointer bindings using
ion.types.MatchKeyandion.types.MatchPointer. Modifiers such aslogo,shift,ctrlandaltcan be combined freely.import ion from ion.types import MatchKey, MatchPointer import ion.constants.keys as kbd import ion.constants.pointer_buttons as btn import ion.constants.event_actions as act import ion.ops as ops km = ion.app.keymaps['global'] # Launch a terminal. km.items.new(MatchKey(kbd.RETURN, logo=True), operator=ops.system_exec(command=("foot",))) # Close the focused window (or remove frame if empty). km.items.new(MatchKey(kbd.Q, logo=True), operator=ops.window_close()) # Toggle fullscreen. km.items.new(MatchKey(kbd.F, logo=True), operator=ops.window_fullscreen_set()) # Vim-style focus navigation. km.items.new(MatchKey(kbd.H, logo=True), operator=ops.tiling_focus_directional(direction="LEFT")) km.items.new(MatchKey(kbd.J, logo=True), operator=ops.tiling_focus_directional(direction="DOWN")) km.items.new(MatchKey(kbd.K, logo=True), operator=ops.tiling_focus_directional(direction="UP")) km.items.new(MatchKey(kbd.L, logo=True), operator=ops.tiling_focus_directional(direction="RIGHT")) # Move windows with Logo+Shift. km.items.new(MatchKey(kbd.H, logo=True, shift=True), operator=ops.window_tiling_move_directional(direction="LEFT")) km.items.new(MatchKey(kbd.L, logo=True, shift=True), operator=ops.window_tiling_move_directional(direction="RIGHT")) # Tab cycling with Alt+Tab / Alt+Shift+Tab. km.items.new(MatchKey(kbd.TAB, alt=True), operator=ops.frame_tab_cycle(direction=1)) km.items.new(MatchKey(kbd.TAB, alt=True, shift=True), operator=ops.frame_tab_cycle(direction=-1)) # Pointer: Logo+Right-click drag to move a floating window. km.items.new(MatchPointer(btn.RIGHT, logo=True, action=act.PRESS), operator=ops.floating_move()) # Pointer: Logo+Left-click drag to resize a floating window. km.items.new(MatchPointer(btn.LEFT, logo=True, action=act.PRESS), operator=ops.floating_resize())
Registry of named menus with builder callbacks:
ion.app.menus.new('tab', 'Tab', callback).Callbacks receive a
ion.types.MenuBuilderand are called each time the menu is shown, enabling dynamic items.import ion import ion.ops as ops def my_menu(menu): menu.layout.operator(ops.window_close()) menu.layout.separator() menu.layout.operator(ops.frame_delete()) ion.app.menus.new("context", "Context Menu", my_menu)
The registry follows the dict protocol: iteration yields menu idnames,
"name" in menustests membership,menus["name"]returns theion.types.Menu, andkeys()/values()/items()expose the three views.- Type:
Context Menus
Register context menus with
ion.types.MenuRegistry.new(). The callback receives aion.types.MenuBuilderand populates itsion.types.UILayout(menu.layout) with operators, separators, and sub-menus. Bind the menu to a pointer event with the built-inmenu_showoperator.import ion from ion.types import MatchPointer, MenuBuilder import ion.constants.pointer_buttons as btn import ion.ops as ops # Tab context menu - shown on right-click over a tab. def _tab_menu(menu: MenuBuilder) -> None: layout = menu.layout layout.operator(ops.window_close()) layout.operator(ops.frame_delete()) layout.separator() layout.operator(ops.tag_set()) layout.operator(ops.window_floating_set()) ion.app.menus.new('tab', "Tab", _tab_menu) # Frame context menu with a nested sub-menu. def _frame_menu(menu: MenuBuilder) -> None: layout = menu.layout layout.operator(ops.tiling_split(direction='RIGHT')) layout.operator(ops.tiling_split(direction='DOWN')) layout.operator(ops.tiling_split_axis_set()) layout.separator() layout.menu(idname="root") ion.app.menus.new('frame', "Frame", _frame_menu) # Root sub-menu referenced by the frame menu above. def _root_menu(menu: MenuBuilder) -> None: layout = menu.layout layout.operator(ops.workspace_new_tiling_on_desktop()) layout.separator() layout.operator(ops.desktop_new()) layout.operator(ops.desktop_delete()) ion.app.menus.new('root', "Root", _root_menu) # Bind right-click on a tab to show the "tab" menu. ion.app.keymaps['tab'].items.new( MatchPointer(btn.RIGHT), operator=ops.menu_show(idname="tab"), )
- ion.app.preferences¶
Main preferences object accessed via
ion.app.preferences.- Type: