1 -- -*- coding: utf-8 -*-
2 --------------------------------------------------------------------------------
3 -- @author Nicolas Berthier <nberthier@gmail.com>
4 -- @copyright 2010 Nicolas Berthier
5 --------------------------------------------------------------------------------
7 -- This is a module for defining keychains à la emacs in awesome. I was also
8 -- inspired by ion3 behavior when designing it.
12 -- - This module does not handle `release' key bindings, but is it useful for
15 -- - It has not been tested with multiple screens yet.
17 -- - There might (... must) be incompatibilities with the shifty module. Also,
18 -- defining global and per-client keychains with the same prefix is not
19 -- allowed (or leads to unspecified behaviors... --- in practice: the
20 -- per-client ones are ignored). However, I do think separation of per-client
21 -- and global keys is a bad idea if client keys do not have a higher priority
22 -- than the global ones...
24 -- Example usage: (TODO)
26 --------------------------------------------------------------------------------
28 --{{{ Grab environment (mostly aliases)
29 local setmetatable = setmetatable
40 local awesome = awesome
44 local infoline = require ("infoline")
45 local akey = require ("awful.key")
46 local join = awful.util.table.join
47 local clone = awful.util.table.clone
52 -- Privata data: we use weak keys in order to allow collection of private data
53 -- if keys (clients) are collected (i.e., no longer used, after having been
54 -- killed for instance)
55 local data = setmetatable ({}, { __mode = 'k' })
57 --{{{ Functional Tuples
58 -- see http://lua-users.org/wiki/FunctionalTuples for details
60 --- Creates a keystroke representation to fill the `escape' table configuration
62 -- @param m Modifiers table.
63 -- @param k The key itself.
64 -- @return A keystroke representation (only for the escape sequence, for now?).
65 function keystroke (m, k)
66 if type (m) ~= "table" then
67 error ("Keystroke modifiers must be given a table (got a "..
70 if type (k) ~= "string" then
71 error ("Keystroke key must be given a string (got a "..
74 return function (fn) return fn (m, k) end
77 -- keystroke accessors
78 local function ks_mod (_m, _k) return _m end
79 local function ks_key (_m, _k) return _k end
83 --- Creates a final keychain binding to fill the keychain binding tables,
84 --- meaning that the given function will be executed at the end of the keychain.
85 -- @param m Modifiers table.
87 -- @param cont The function to be bound to the given keys.
88 -- @return A "final" key binding.
89 function key (m, k, cont)
90 if type (cont) ~= "function" then
91 error ("Final binding must be given a function (got a "..
94 return function (fn) return fn (keystroke (m, k), cont, true) end
97 --- Creates an intermediate (prefix) keychain binding.
98 -- @param m Modifiers table.
100 -- @param sub The subchain description table to be bound to the given keys.
101 -- @return An "intermediate" key binding.
102 function subchain (m, k, sub)
103 if type (sub) ~= "table" then
104 error ("Subchain binding must be given a table (got a "..
107 return function (fn) return fn (keystroke (m, k), sub, false) end
110 -- key/subchain binding accessors
111 local function binding_ks (ks, cont, leaf) return ks end
112 local function binding_cont (ks, cont, leaf) return cont end
113 local function binding_leaf (ks, cont, leaf) return leaf end
119 --- Default escape sequences (S-g is inspired by emacs...)
120 local escape_keystrokes = {
121 keystroke ( { }, "Escape" ),
122 keystroke ( { "Mod4" }, "g" ),
124 --- Default modifier filter
129 ["Shift"] = string.upper,
132 -- Defines whether we use bowls or not. Bowls are kind of helpers that can be
133 -- drawn (at the bottom --- for now) of an area, and displaying the current key
134 -- prefix. It is inspired by emacs' behavior, that prints prefix keys in the
135 -- minibuffer after a certain time.
137 -- I call it `bowl' as a reference to the bowl that one might have at home,
138 -- where one puts its actual keys... A more serious name would be `hint' or
139 -- `tooltip' (but they do not fit well for this usage).
141 -- Note one could emit signals, observable by a specific object, that would then
142 -- print the keychain prefix somewhere else... (in the titlebar, for instance).
143 local use_bowls = true
145 -- Timers configuration
146 local use_timers = true
151 --{{{ Keychain pretty-printing
153 local function mod_to_string (mods, k)
155 for _, mod in ipairs (mods) do
156 if modfilter[mod] then
157 local t = type (modfilter[mod])
158 if t == "function" then
159 k = modfilter[mod](k)
160 elseif t == "string" then
161 ret = ret .. modfilter[mod] .. "-"
163 error ("Invalid modifier key filter: got a " .. t)
166 ret = ret .. mod .. "-"
172 local function ks_to_string (m, k)
173 local m, k = mod_to_string (m, k)
179 --{{{ Timer management
181 local function delete_timer_maybe (d)
182 if d.timer then -- stop and remove the timer
183 d.timer:remove_signal ("timeout", d.timer_function)
186 d.timer_expired = true
190 local function delayed_call_maybe (d, f)
192 if not d.timer_expired and not d.timer then
193 -- create and start the timer
194 d.timer = timer ({ timeout = timeout })
195 d.timer_function = function () f (); delete_timer_maybe (d) end
196 d.timer:add_signal ("timeout", d.timer_function)
198 d.timer_expired = false
199 elseif not d.timer_expired then
200 -- restart the timer...
202 -- XXX: What is the actual semantics of the call to `start' (ie,
203 -- does it restart the timer with the initial timeout)?
205 d.timer.timeout = timeout -- reset timeout
208 else -- timers disabled
209 f () -- call the given function directly
215 --{{{ Key table management facilities
217 local function set_keys (c, k)
218 if c == root then root.keys (k) else c:keys (k) end
221 local function keys_of (c)
222 if c == root then return root.keys () else return c:keys () end
227 --{{{ Client/Root-related state management
229 local function retrieve_or_init_client_state (w)
230 if data[w] then return data[w] end
232 d.keys = keys_of (w) -- save client keys
233 if use_bowls then -- create bowl if needed
234 -- XXX: Note the prefix text could be customizable...
235 d.bowl = infoline.new (" ")
236 -- TODO: ...:signal_emit ("keychain:enter", ...)
238 data[w] = d -- register client
242 local function restore_client_state (c)
244 set_keys (w, data[w].keys) -- restore client keys
245 data[w] = nil -- unregister client
248 local function dispose (c)
252 -- Destroy bowl and delete timer if needed
253 if d and use_bowls then
254 if d.bowl then -- if bowl was enabled...
255 infoline.dispose (d.bowl)
257 -- TODO: ...:signal_emit ("keychain:dispose", ...)
259 delete_timer_maybe (d)
263 -- force disposal of resources when clients are killed
264 client.add_signal ("unmanage", dispose)
268 --{{{ Key binding tree access helpers
270 local function make_on_entering (m, k, subchain)
271 local pretty_ks = ks_to_string (m, k) .. " "
276 -- Register and initialize client state, if not already in a keychain
277 local d = retrieve_or_init_client_state (w)
279 -- Update bowl text, and trigger its drawing if necessary
281 infoline.set_text (d.bowl, infoline.get_text (d.bowl) .. pretty_ks)
283 local function enable_bowl ()
284 -- XXX: is there a possible bad interleaving that could make
285 -- this function execute while the bowl has already been
286 -- disposed of? in which case the condition should be checked
290 infoline.attach (d.bowl, w)
294 delayed_call_maybe (d, enable_bowl)
296 -- TODO: ...:signal_emit ("keychain:update", ...)
300 set_keys (w, subchain)
304 local function on_leaving (c)
305 -- Trigger disposal routine
308 -- Restore initial key mapping of client
309 restore_client_state (c)
316 -- Flag to detect late initialization error
317 local already_used = false
319 -- Escape binding table built once upon initialization
320 local escape_bindings = { }
322 --- Fills the escape bindings table with actual `awfull.key' elements triggering
323 --- execution of `on_leaving'.
324 local function init_escape_bindings ()
325 escape_bindings = { }
326 for _, e in ipairs (escape_keystrokes) do
327 escape_bindings = join (escape_bindings,
328 akey (e (ks_mod), e (ks_key), on_leaving))
332 -- Call it once upon module loading to initialize escape_bindings (in case
333 -- `init' is not called).
334 init_escape_bindings ()
336 --- Initializes the keychain module, with given properties; to be called before
337 --- ANY other function of this module.
338 -- Configurations fields include:
340 -- `escapes': A table of keystrokes (@see keychain.keystroke) escaping keychains
341 -- (defaults are `Mod4-g' and `Escape').
343 -- `use_bowls': A boolean defining whether bowls are enabled or not (default is
346 -- `use_timers', `timeout': A boolean defining whether bowls drawing should be
347 -- delayed, along with a number being this time shift, in seconds (Default
348 -- values are `true' and `2').
350 -- `modfilter': A table associating modifiers (Mod1, Mod4, Control, Shift, etc.)
351 -- with either a string (in this case it will replace the modifier when printed
352 -- in heplers) or functions (in this case the key string will be repaced by a
353 -- call to this function with the key string as parameter). Default value is:
354 -- { ["Mod1"] = "M", ["Mod4"] = "S", ["Control"] = "C", ["Shift"] =
357 -- @param c The table of properties.
362 -- heum... just signal the error: "print" or "error"?
363 return print ("E: keychain: Call to `init' AFTER having bound keys!")
366 escape_keystrokes = c.escapes and c.escapes or escape_keystrokes
367 if c.use_bowls ~= nil then use_bowls = c.use_bowls end
370 modfilter = c.modfilter and c.modfilter or modfilter
371 if c.use_timers ~= nil then use_timers = c.use_timers end
373 timeout = c.timeout ~= nil and c.timeout or timeout
377 -- Now, fill the escape bindings table again with actual `awfull.key'
378 -- elements triggering `on_leaving' executions, in case escape keys has
380 init_escape_bindings ()
385 --{{{ Keychain creation
387 --- Creates a new keychain binding.
388 -- @param m Modifiers table.
390 -- @param chains A table of keychains, describing either final bindings (see
391 -- key constructor) or subchains (see subchain constructor).
392 -- @return A key binding for the `awful.key' module.
394 function new (m, k, chains)
396 -- This table will contain the keys to be mapped upon <m, k> keystroke. It
397 -- initially contains the escape bindings, so that one can still rebind them
398 -- differently in `chains'.
399 local subchain = clone (escape_bindings)
401 already_used = true -- subsequent init avoidance flag...
403 -- For each entry of the given chains, add a corresponding `awful.key'
404 -- element in the subchain
405 for _, e in ipairs (chains) do
406 local ks = e (binding_ks)
407 if e (binding_leaf) then
408 -- We encountered a lead in the chains.
409 local function on_leaf (c) on_leaving (c); e (binding_cont) (c) end
410 subchain = join (subchain, akey (ks (ks_mod), ks (ks_key), on_leaf))
412 -- Recursively call subchain creation. "Funny" detail: I think there
413 -- is no way of creating ill-structured keychain descriptors that
414 -- would produce infinite recursive calls here, since we control
415 -- their creation with functional tuples, that cannot lead to cyclic
417 local subch = new (ks (ks_mod), ks (ks_key), e (binding_cont))
418 subchain = join (subchain, subch)
422 -- Then return an actual `awful.key', triggering the `on_entering' routine
423 return akey (m, k, make_on_entering (m, k, subchain))
427 -- Setup `__call' entry in module's metatable so that we can create new prefix
428 -- binding using `keychain (m, k, ...)' directly.
429 setmetatable (_M, { __call = function (_, ...) return new (...) end })
432 -- indent-tabs-mode: nil
434 -- lua-indent-level: 4
436 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80