This chapter should help your configure Ion to your liking. As the your probably already know, Ion uses Lua as a configuration and extension language. If you're new to it, you might first want to read some Lua documentation as already suggested and pointed to in the Introduction before continuing with this chapter.
Section 3.1 is an overview of the multiple configuration files Ion uses and as a perhaps more understandable introduction to the general layout of the configuration files, a walk-through of the main configuration file ion.lua is provided in section 3.2. How keys and mouse action are bound to functions is described in detail in 3.3 and in section 3.5 winprops are explained. For a reference on exported functions, see section 6.
Ion3, to which document applies, stores its stock configuration files in /usr/local/etc/ion3/ unless you, the OS package maintainer or whoever installed the package on the system has modified the variables PREFIX or ETCDIR in system.mk before compiling Ion. In the first case you probably know where to find the files and in the other case the system administrator or the OS package maintainer should have provided documentation to point to the correct location. If these instructions are no help in locating the correct directory, the command locate cfg_ion.lua might help provided updatedb has been run recently.
User configuration files go in ~/.ion3/. Ion always searches the user configuration file directory before the stock configuration file directory for files. Therefore, if you want to change some setting, it is advised against that you modify the stock configuration files in-place as subsequent installs of Ion will restore the stock configuration files. Instead you should always make a copy of the stock file in ~/.ion3/ and modify this file. When searching for a file, if no extension or path component is given, compiled .lc files are attempted before .lua files.
All the configuration files are named cfg_*.lua with the ''*'' part varying. The configuration file for each module mod_modname is cfg_modname.lua, with modname varying by the module in question. The following table summarises these and other configuration files:
File | Description |
cfg_ion.lua | The main configuration file |
cfg_bindings.lua | Most of Ion's bindings are configured here. Bindings that are specific to some module are configured in the module's configuration file. For details, see section 3.3. |
cfg_menus.lua | Menu definitions; see section 3.4. |
cfg_kludges.lua | Settings to get some applications behave more nicely have been collected here. See section 3.5. |
cfg_ionws.lua cfg_floatws.lua cfg_panews.lua cfg_query.lua cfg_menu.lua cfg_dock.lua cfg_statusbar.lua ... | Configuration files for different modules. |
Additionally, there's look.lua that configures the drawing engine, but it is covered in chapter 4.
As already mentioned cfg_ion.lua is Ion's main configuration file. Some basic 'feel' settings are usually configured there and the necessary modules and other configuration files configuring some more specific aspects of Ion are loaded there. In this section we take a walk through the stock cfg_ion.lua.
The first thing that is done in that file is set
MOD1="Mod1+" MOD2=""This causes most of Ion's key bindings to use Mod1 as the modifier key. If MOD2 is set, it is used as modifier for the keys that don't normally use a modifier. for details on modifiers and key binding setup in general see section 3.3.
Next we do some basic feel configuration:
ioncore.set{ dblclick_delay=250, kbresize_delay=1500, }
These two will set the delay between button presses in a double click, and the timeout to quit resize mode in milliseconds.
ioncore.set{ opaque_resize=true, warp=true, }
The first of these two settings enables opaque resize mode: in move/resize move frames and other objects mirror you actions immediately. If opaque resize is disabled, a XOR rubber band is shown during the mode instead. This will, unfortunately, cause Ion to also grab the X server and has some side effects.
ioncore.set{ default_ws_type="WIonWS", }
This will set the default workspace type to WIonWS - tiled workspaces.
To actually be able to do something besides display windows in full screen mode, we must next load some modules:
dopath("mod_query") dopath("mod_menu") dopath("mod_ionws") dopath("mod_floatws") dopath("mod_panews") dopath("mod_statusbar") --dopath("mod_dock") --dopath("mod_sp")
As already mentioned, each of these modules have their own configuration files. Finally, additional configuration files are loaded:
dopath("cfg_kludges") dopath("cfg_bindings") dopath("cfg_menus")
Bindings and menus are defined in cfg_bindings.lua and cfg_menus.lua. Details on making such definitions follow in sections 3.3 and 3.4, respectively. some kludges or ''winprops'' to make some applications behave better under Ion are colledted in cfg_kludges.lua; see section 3.5 for details. In addition to these, this file lists quite a few statements of the form
ioncore.defshortening("[^:]+: (.*)(<[0-9]+>)", "$1$2$|$1$<...$2")These are used to configure how Ion attempts to shorten window titles when they do not fit in a Tab. The first argument is a POSIX regular expression that is used to match against the title and the next is a rule to construct a new title of a match occurs. This particular rule is used to shorten e.g. 'Foo: barbaz<3>' to 'barba...<3>'; for details see the function reference entry for ioncore.defshortening.
In the stock configuration file setup, most key and mouse bindings are set from the file cfg_bindings.lua while module-specific bindings are set from the modules' main configuration files (cfg_modname.lua). This, however, does not have to be so as long as the module has been loaded prior to defining any module-specific bindings.
Bindings are defined by calling the function defbindings with the ''context'' of the bindings and the a table of new bindings to make. The context is simply string indicating one of the classes of regions (or modes such as WMoveresMode) introduced in section 2.2, and fully listed in appendix B, although not all define a binding map. For example, the following skeleton would be used to define new bindings for all frames:
defbindings("WFrame", { -- List of bindings to make goes here. })
There has been some confusion among users about the need to define the
''context'' for each binding, so let me try to explain this design
decision here. The thing is that if there was a just a simple 'bind this
key to this action' method without knowledge of the context, some
limitations would have to be made on the available actions and writing
custom handlers would be more complicated. In addition one may want to
bind the same function to different key for different types of objects.
Indeed, the workspace and frame tab switching functions are the same both
classes being based on WMPlex, and in the stock configuration the
switch to :th workspaces is bound to Mod1+n while the switch to
:th tab is bound to the sequence Mod1+k n.
The following subsections describe how to construct elements of the binding table. Note that defbindings adds the the newly defined bindings to the previous bindings of the context, overriding duplicates. To unbind an event, set the handler parameter to nil for each of the functions to be described in the following subsections.
Also note that when multiple objects want to handle a binding, the innermost (when the root window is considered the outermost) active object in the parent-child hierarchy (see Figure 2.2) of objects gets to handle the action.
Unlike in Ion2, in Ion3 binding handlers are not normally passed as ''anonymous functions'', although this is still possible. The preferred method now is to pass the code of the handler as a string. Two special variables are available in this code. These are
Variable | Description |
_ (underscore) | Reference to the object on which the binding was triggered. The object is of the same class as the the context of the defbindings call defining the binding. |
_sub | Usually, the currently active child of the object referred to by _, but sometimes (e.g. mouse actions on tabs of frames) something else relevant to the action triggering the binding. |
For example, supposing '_' is a WFrame, the following handler should move the active window to the right, if possible:
"_:inc_index(_sub)"
To suppress error messages, each binding handler may also be accompanied by a ''guard'' expression that blocks the handler from being called when the guard condition is not met. Currently the following guard expressions are supported:
Guard | Description |
"_sub:non-nil" | The _sub parameter must be set. |
"_sub:SomeClass" | The _sub parameter must be member of class SomeClass. |
The descriptions of the individual bindings in the binding table argument to defbindings should be constructed with the following functions.
Key presses:
The actions that most of these functions correspond to should be clear and as explained in the reference, kpress_wait is simply kpress with a flag set instructing Ioncore wait for all modifiers to be released before processing any further actions. This is to stop one from accidentally calling e.g. WRegion.rqclose multiple times in a row. The submap function is used to define submaps or ''prefix maps''. The second argument to this function is table listing the key press actions (kpress) in the submap
The parameters keyspec and buttonspec are explained below in detail. The parameter handler is the handler for the binding, and the optional parameter guard its guard. These should normally be strings as explained above.
For example, to just bind the key Mod1+1 to switch to the first workspace and Mod1+Right to the next workspace, you would make the following call
defbindings("WScreen", { kpress("Mod1+Right", "_:switch_next()"), kpress("Mod1+1", "_:switch_nth(1)"), })
Note that _:switch_nth(1) is the same as calling WMPlex.switch_next(_, 1) as WScreen inherits WMPlex and this is where the function is actually defined.
Similarly to the above example, to bind the key sequence Mod1+k n switch to the next managed object within a frame, and Mod1+k 1 to the first, you would issue the following call:
defbindings("WFrame", { submap("Mod1+K", { kpress("Right", "_:switch_next()"), kpress("1", "_:switch_nth(1)"), }), })
As seen above, the functions that create key binding specifications require a keyspec argument. This argument should be a string containing the name of a key as listed in the X header file keysymdef.h3.1 without the XK_ prefix. Most of the key names are quite intuitive while some are not. For example, the Enter key on the main part of the keyboard has the less common name Return while the one the numpad is called KP_Enter.
The keyspec string may optionally have multiple ''modifier'' names followed by a plus sign (+) as a prefix. X defines the following modifiers:
Shift, Control, Mod1 to Mod5, AnyModifier and Lock.
X allows binding all of these modifiers to almost any key and while this list of modifiers does not explicitly list keys such as Alt that are common on modern keyboards, such keys are bound to one of the ModN. On systems running XFree86 Alt is usually Mod1. On Suns Mod1 is the diamond key and Alt something else. One of the ''flying window'' keys on so called Windows-keyboards is probably mapped to Mod3 if you have such a key. Use the program xmodmap to find out what exactly is bound where.
Ion defaults to AnyModifier in submaps. This can sometimes lead to unwanted effects when the same key is used with and without explicitly specified modifiers in nested regions. For this reason, Ion recognises NoModifier as a special modifier that can be used to reset this default.
Ion ignores the Lock modifier and any ModN ()
bound to NumLock or
ScrollLock
by default because such3.2 locking keys may otherwise
cause confusion.
Button specifications are similar to key definitions but now instead of specifying modifiers and a key, you specify modifiers and one of the button names Button1 to Button5. Additionally the specification may end with an optional area name following an @-sign. Only frames currently support areas, and the supported values in this case are "border", "tab", "empty_tab", "client" and nil (for the whole frame).
For example, the following code binds dragging a tab with the first button pressed to initiate tab drag&drop handling:
defbindings("WFrame", { mdrag("Button1@tab", "_:p_tabdrag()"), })
The default binding configuration contains references to the variables MOD1 and MOD2 instead of directly using the default values of "Mod1+" and "" (nothing). As explained in section 3.2, the definitions of these variables appear in cfg_ion.lua. This way you can easily change the the modifiers used by all bindings in the default configuration without changing the whole binding configuration. Quite a few people prefer to use the Windows keys as modifiers because many applications already use Alt. Nevertheless, Mod1 is the default as a key bound to it is available virtually everywhere.
As client windows do not have a binding map of their own due to technical reasons, it is necessary to call client window functions by specifying the bindings somewhere else. In the stock configuration file setup this is done among WMPlex bindings, setting the guard to _sub:WClientWin and using _sub to refer to the client window.
For example, the full screen toggle key is bound like this:
defbindings("WMPlex", { kpress_wait("Mod1+Return", "_:toggle_fullscreen()", "_sub:WClientWin"), })
In the stock configuration file setup, menus are defined in the file cfg_menus.lua as previously mentioned. The mod_menu module must be loaded for one to be able to define menus, and this is done with the function defmenu provided by it.
Here's an example of the definition of a rather simple menu with a submenu:
defmenu("exitmenu", { menuentry("Restart", "ioncore.restart()"), menuentry("Exit", "ioncore.shutdown()"), }) defmenu("mainmenu", { menuentry("Lock screen", "ioncore.exec('xlock')"), menuentry("Help", "mod_query.query_man(_)"), submenu("Exit", "exitmenu"), })
The menuentry function is used to create an entry in the menu with a title and an entry handler to be called when the menu entry is activated. The parameters to the handler are similar to those of binding handlers, and usually the same as those of the binding that opened the menu.
The submenu function is used to insert a submenu at that point in the menu. (One could as well just pass a table with the menu entries, but it is not encouraged.)
The menu module predefines the following special menus. These can be used just like the menus defined as above.
Menu name | Description |
windowlist | List of all client windows. Activating an entry jumps to that window. |
workspacelist | List of all workspaces. Activating an entry jumps to that workspaces. |
stylemenu | List of available look_*.lua style files. Activating an entry loads that style and ask to save the selection. |
ctxmenu | Context menu for given object. |
The ''ctxmenu'' is a special menu that is assembled from a defined context menu for the object for which the menu was opened for, but also includes the context menus for the manager objects as submenus.
Context menus for a given region class are defined with the defctxmenu function. This is other ways similar to defmenu, but the first argument instead being the name of the menu, the name of the region class to define context menu for. For example, here's part of the stock WFrame context menu definition:
defctxmenu("WFrame", { menuentry("Close", "WRegion.rqclose_propagate(_, _sub)"), menuentry("Kill", "WClientWin.kill(_sub)", "_sub:WClientWin"), })
The following functions may be used to display menus from binding handlers (and elsewhere):
Function | Description |
mod_menu.menu | Keyboard (or mouse) operated menus that open in the bottom-left corner of a screen or frame. |
mod_menu.bigmenu | Same as previous, but uses another graphical style. |
mod_menu.pmenu | Mouse-operated drop-down menus. This function can only be called from a mouse press or drag handler. |
mod_menu.grabmenu | A special version of mod_menu.menu that grabs the keyboard and is scrolled with a given key until all modifiers have been released, after which the selected entry is activated. This function is meant to be used for implementing, for example, Win***s-style Alt-Tab handling.3.3 |
The grabmenu function takes the extra key parameter, but aside from that each of these functions takes three arguments, which when called from a binding handler, should be the parameters to the handler, and the name of the menu. For example, the following snippet of of code binds the both ways to open a context menu for a frame:
defbindings("WFrame", { kpress(MOD1.."M", "mod_menu.menu(_, _sub, 'ctxmenu')"), mpress("Button3", "mod_menu.pmenu(_, _sub, 'ctxmenu')"), })
The so-called ''winprops'' can be used to change how specific windows are handled and to set up some kludges to deal with badly behaving applications. They are defined by calling the function defwinprop with a table containing the properties to set and the necessary information to identify a window. The currently supported winprops are listed in the following table, and the subsequent subsections explain the usual method of identifying windows, and how to obtain this information.
Property | Type | Description |
switchto | boolean | Should a newly mapped client window be switched to within its frame. |
jumpto | boolean | Should a newly created client window always be made active, even if the allocated frame isn't. |
transient_mode | string | "normal": No change in behaviour. "current": The window should be thought of as a transient for the current active client window (if any) even if it is not marked as a transient by the application. "off": The window should be handled as a normal window even if it is marked as a transient by the application. |
target | string | The name of an object (workspace, frame) that should manage windows of this type. |
transparent | boolean | Should frames be made transparent when this window is selected? |
acrobatic | boolean | Set this to true for Acrobat Reader. It has an annoying habit of trying to manage its dialogs instead of setting them as transients and letting the window manager do its job, causing Ion and acrobat go a window-switching loop when a dialog is opened. |
max_size | table | The table should contain the entries w and h that override application-supplied maximum size hint. |
aspect | table | The table should contain the entries w and h that override application-supplied aspect ratio hint. |
ignore_resizeinc | boolean | Should application supplied size increments be ignored? |
fullscreen | boolean | Should the window be initially in full screen mode? |
ignore_cfgrq | boolean | Should configure requests on the window be ignored? Only has effect on windows on floatws:s. |
transients_at_top | boolean | When transients are managed by the client window itself (as it is the case on tiled workspaces), should the transients be placed at the top of the window instead of bottom? |
ignore_net_active_window | boolean | Ignore extended WM hints _NET_ACTIVE_WINDOW request. |
The identification information in the winprop specification is usually the class, role, instance and name of the window. The name field is a Lua-style regular expression matched against the window's title and the rest are strings that must exactly much the corresponding window information. It is not necessary to specify all of these fields.
Ion looks for a matching winprop in the order listed by the following table. An 'E' indicates that the field must be set in the winprop and it must match the window's corresponding property exactly or, in case of name, the regular expression must match the window title. An asterisk '*' indicates that a winprop where the field is not specified (or is itself an asterisk in case of the first three fields) is tried.
class | role | instance | name |
E | E | E | E |
E | E | E | * |
E | E | * | E |
E | E | * | * |
E | * | E | E |
E | * | E | * |
E | * | * | E |
etc. |
If there are multiple winprops with other identification information the same but different name, the longest match is chosen.
To get the identification information required for winprops, in case of normally framed windows you may use the command xprop WM_CLASS and click on the particular window of interest. The class is the latter of the strings while the instance is the former. To get the role - few windows have this property - use the command xprop WM_ROLE.
So-called ''transient windows'' are usually short-lived dialogs (although some programs abuse this property) that have a parent window that they are ''transient for''. On tiled workspaces Ion displays these windows simulatenously with the parent window at the bottom of the same frame. Unfortunately xprop is stupid and can't cope with this situation, returning the parent window's properties when the transient is clicked on. For this reason you'll have to do a little extra work to get the properties for that window.3.4
If you can guess the title of the transient, the simplest solution is to use the Mod1+A query (mod_query.query_attachclient) to attach it directly to a frame. Another easy solution is to create a WFloatWS and run the program for this once. A little more complicated solution is to run the following code in the Mod1+F3 (mod_query.query_lua) Lua code execution query, assuming there's only one transient (all on one line):
local id=_:current():managed_list()[1]:get_ident(); mod_query.message(_, id.class..'.'..id.instance);
Role and name can be retrieved similarly (see the documentation for WClientWin.get_ident or WRegion.name). Role may not always be set.
Finally, it should be mentioned that too many authors these days ''forget'' to set this vital identification to anything meaningful: everything except name is the same for all of the programs's windows, for example.
The following is absolutely necessary for Acrobat reader:
defwinprop{ class = "AcroRead", instance = "documentShell", acrobatic = true, }
Mozilla Firebird (0.7) incorrectly does not set the WM_TRANSIENT_FOR property for the dialog that is used to ask the action to take for a file. It, however, sets the the property point to the main window for the save dialog. This can be annoying and confusing, as the first dialog is not closed before the second is displayed.
We'd like the first dialog to be transient to the main window. The closest we can get to that is to consider it transient to the current window (if there's one). Unfortunately Firebird does not set any meaningful classes, instances or roles for the windows, so we'll have to rely on an ugly title match.
defwinprop{ class = "MozillaFirebird-bin", name = "Opening .*", transient_mode = "current", }
The following winprop should place xterm started with command-line parameter -name sysmon and running a system monitoring program in a particular frame:
defwinprop{ class = "XTerm", instance = "sysmon", target = "sysmonframe", }
For this example to work, we have to somehow create a frame named sysmonframe. One way to do this is to make the following call in the Mod1+F3 Lua code query:
mod_query.query_renameframe(_)
Recall that _ points to the multiplexer (frame or screen) in which the query was opened. Running this code should open a new query prefilled with the current name of the frame. In our example we would change the name to sysmonframe, but we could just as well have used the default name formed from the frame's class name and an instance number.