summaryrefslogtreecommitdiffstats
path: root/.config/awesome/keychain.lua
diff options
context:
space:
mode:
Diffstat (limited to '.config/awesome/keychain.lua')
-rw-r--r--.config/awesome/keychain.lua334
1 files changed, 334 insertions, 0 deletions
diff --git a/.config/awesome/keychain.lua b/.config/awesome/keychain.lua
new file mode 100644
index 0000000..86ce7b2
--- /dev/null
+++ b/.config/awesome/keychain.lua
@@ -0,0 +1,334 @@
+-- -*- coding: utf-8 -*-
+--------------------------------------------------------------------------------
+-- @author Nicolas Berthier <nberthier@gmail.com>
+-- @copyright 2010 Nicolas Berthier
+--------------------------------------------------------------------------------
+--
+-- This is a module for defining keychains à la emacs in awesome. I was also
+-- inspired by ion3 behavior when designing it.
+--
+-- Remarks:
+--
+-- - This module does not handle `release' key bindings, but is it useful for
+-- keychains?
+--
+-- - It has not been tested with multiple screens yet.
+--
+-- - There might (... must) be incompatibilities with the shifty module. Also,
+-- defining global and per-client keychains with the same prefix is not
+-- allowed (or leads to unspecified behaviors... --- in practice: the
+-- per-client ones are ignored). However, I do think separation of per-client
+-- and global keys is a bad idea if client keys do not have a higher priority
+-- than the global ones...
+--
+-- Example usage: see `rc.lua' file.
+--
+--------------------------------------------------------------------------------
+
+--{{{ Grab environment (mostly aliases)
+local setmetatable = setmetatable
+local ipairs = ipairs
+local type = type
+local pairs = pairs
+local string = string
+local print = print
+local error = error
+local io = io
+
+local capi = capi
+local client = client
+local awesome = awesome
+local root = root
+
+local akey = require ("awful.key")
+local join = awful.util.table.join
+local clone = awful.util.table.clone
+--}}}
+
+module ("keychain")
+
+-- Privata data: we use weak keys in order to allow collection of private data
+-- if keys (clients) are collected (i.e., no longer used, after having been
+-- killed for instance)
+local data = setmetatable ({}, { __mode = 'k' })
+
+--{{{ Functional Tuples
+-- see http://lua-users.org/wiki/FunctionalTuples for details
+
+--- Creates a keystroke representation to fill the `escape' table configuration
+--- property.
+-- @param m Modifiers table.
+-- @param k The key itself.
+-- @return A keystroke representation (only for the escape sequence, for now?).
+function keystroke (m, k)
+ if type (m) ~= "table" then
+ error ("Keystroke modifiers must be given a table (got a "..
+ type (m)..")")
+ end
+ if type (k) ~= "string" then
+ error ("Keystroke key must be given a string (got a "..
+ type (m)..")")
+ end
+ return function (fn) return fn (m, k) end
+end
+
+-- keystroke accessors
+local function ks_mod (_m, _k) return _m end
+local function ks_key (_m, _k) return _k end
+
+-- ---
+
+--- Creates a final keychain binding to fill the keychain binding tables,
+--- meaning that the given function will be executed at the end of the keychain.
+-- @param m Modifiers table.
+-- @param k The key.
+-- @param cont The function to be bound to the given keys.
+-- @return A "final" key binding.
+function key (m, k, cont)
+ if type (cont) ~= "function" then
+ error ("Final binding must be given a function (got a "..
+ type (cont)..")")
+ end
+ return function (fn) return fn (keystroke (m, k), cont, true) end
+end
+
+--- Creates an intermediate (prefix) keychain binding.
+-- @param m Modifiers table.
+-- @param k The key.
+-- @param sub The subchain description table to be bound to the given keys.
+-- @return An "intermediate" key binding.
+function subchain (m, k, sub)
+ if type (sub) ~= "table" then
+ error ("Subchain binding must be given a table (got a "..
+ type (sub)..")")
+ end
+ return function (fn) return fn (keystroke (m, k), sub, false) end
+end
+
+-- key/subchain binding accessors
+local function binding_ks (ks, cont, leaf) return ks end
+local function binding_cont (ks, cont, leaf) return cont end
+local function binding_leaf (ks, cont, leaf) return leaf end
+
+--- Creates an intermediate keychain if sub is a table, or a final key binding
+--- otherwise (and then sub must be a function).
+-- @param m Modifiers table.
+-- @param k The key.
+-- @param sub Either the subchain description table, or the function, to be
+-- bound to the given keys.
+function sub (m, k, sub)
+ if type (sub) == "table" then
+ return subchain (m, k, sub)
+ else
+ return key (m, k, sub)
+ end
+end
+
+--}}}
+
+--{{{ Default values
+
+--- Default escape sequences (S-g is inspired by emacs...)
+local escape_keystrokes = {
+ keystroke ( { }, "Escape" ),
+ keystroke ( { "Mod4" }, "g" ),
+}
+
+--}}}
+
+--{{{ Key table management facilities
+
+local function set_keys (c, k)
+ if c == root then root.keys (k) else c:keys (k) end
+end
+
+local function keys_of (c)
+ if c == root then return root.keys () else return c:keys () end
+end
+
+--}}}
+
+--{{{ Signal emission helper
+
+local function notif (sig, w, ...)
+ if w ~= root then
+ client.emit_signal (sig, w, ...)
+ else -- we use global signals otherwise
+ awesome.emit_signal (sig, ...)
+ end
+end
+
+--}}}
+
+--{{{ Client/Root-related state management
+
+local function init_client_state_maybe (w)
+ if data[w] == nil then
+ local d = { }
+ d.keys = keys_of (w) -- save client keys
+ data[w] = d -- register client
+ notif ("keychain::enter", w)
+ end
+end
+
+local function restore_client_state (c)
+ local w = c or root
+ local d = data[w]
+ -- XXX: Turns out that `d' can be nil already here, in case the keyboard has
+ -- been grabbed since the previous call to this funtion... (that also seems
+ -- to be called again upon release…)
+ if d then
+ set_keys (w, d.keys) -- restore client keys
+ data[w] = nil -- unregister client
+ end
+end
+
+local function leave (c)
+ local w = c or root
+
+ -- Destroy notifier structures if needed
+ if data[w] then -- XXX: necessary test?
+ notif ("keychain::leave", w)
+ end
+end
+
+-- force disposal of resources when clients are killed
+client.add_signal ("unmanage", leave)
+
+--}}}
+
+--{{{ Key binding tree access helpers
+
+local function make_on_entering (m, k, subchain) return
+ function (c)
+ local w = c or root
+
+ -- Register and initialize client state, if not already in a keychain
+ init_client_state_maybe (w)
+
+ -- Update notifier text, and trigger its drawing if necessary
+ notif ("keychain::append", w, m, k)
+
+ -- Setup subchain
+ set_keys (w, subchain)
+ end
+end
+
+local function on_leaving (c)
+ -- Trigger disposal routine
+ leave (c)
+
+ -- Restore initial key mapping of client
+ restore_client_state (c)
+end
+
+--}}}
+
+--{{{ Configuration
+
+-- Flag to detect late initialization error
+local already_used = false
+
+-- Escape binding table built once upon initialization
+local escape_bindings = { }
+
+--- Fills the escape bindings table with actual `awful.key' elements triggering
+--- execution of `on_leaving'.
+local function init_escape_bindings ()
+ escape_bindings = { }
+ for _, e in ipairs (escape_keystrokes) do
+ escape_bindings = join (escape_bindings,
+ akey (e (ks_mod), e (ks_key), on_leaving))
+ end
+end
+
+-- Call it once upon module loading to initialize escape_bindings (in case
+-- `init' is not called).
+init_escape_bindings ()
+
+
+--- Initializes the keychain module, with given properties; to be called before
+--- ANY other function of this module.
+-- Configurations fields include:
+--
+-- `escapes': A table of keystrokes (@see keychain.keystroke) escaping keychains
+-- (defaults are `Mod4-g' and `Escape').
+--
+-- @param c The table of properties.
+function init (c)
+ local c = c or { }
+
+ if already_used then
+ -- heum... just signal the error: "print" or "error"?
+ return print ("E: keychain: Call to `init' AFTER having bound keys!")
+ end
+
+ escape_keystrokes = c.escapes and c.escapes or escape_keystrokes
+
+ -- Now, fill the escape bindings table again with actual `awful.key'
+ -- elements triggering `on_leaving' executions, in case escape keys has
+ -- changed.
+ init_escape_bindings ()
+end
+
+--}}}
+
+--{{{ Keychain creation
+
+--- Creates a new keychain binding.
+-- @param m Modifiers table.
+-- @param k The key.
+-- @param chains A table of keychains, describing either final bindings (see
+-- key constructor) or subchains (see subchain constructor). If arg is not a
+-- table, then `awful.key' is called directly with the arguments.
+-- @return A key binding for the `awful.key' module.
+-- @see awful.key
+function new (m, k, chains)
+
+ -- If the argument is a function, then we need to return an actual awful.key
+ -- directly.
+ if type (chains) ~= "table" then
+ return akey (m, k, chains)
+ end
+
+ -- This table will contain the keys to be mapped upon <m, k> keystroke. It
+ -- initially contains the escape bindings, so that one can still rebind them
+ -- differently in `chains'.
+ local subchain = clone (escape_bindings)
+
+ already_used = true -- subsequent init avoidance flag...
+
+ -- For each entry of the given chains, add a corresponding `awful.key'
+ -- element in the subchain
+ for _, e in ipairs (chains) do
+ local ks = e (binding_ks)
+ if e (binding_leaf) then
+ -- We encountered a leaf in the chains.
+ local function on_leaf (c) on_leaving (c); e (binding_cont) (c) end
+ subchain = join (subchain, akey (ks (ks_mod), ks (ks_key), on_leaf))
+ else
+ -- Recursively call subchain creation. "Funny" detail: I think there
+ -- is no way of creating ill-structured keychain descriptors that
+ -- would produce infinite recursive calls here, since we control
+ -- their creation with functional tuples, that cannot lead to cyclic
+ -- structures...
+ local subch = new (ks (ks_mod), ks (ks_key), e (binding_cont))
+ subchain = join (subchain, subch)
+ end
+ end
+
+ -- Then return an actual `awful.key', triggering the `on_entering' routine
+ return akey (m, k, make_on_entering (m, k, subchain))
+end
+--}}}
+
+-- Setup `__call' entry in module's metatable so that we can create new prefix
+-- binding using `keychain (m, k, ...)' directly.
+setmetatable (_M, { __call = function (_, ...) return new (...) end })
+
+-- Local variables:
+-- indent-tabs-mode: nil
+-- fill-column: 80
+-- lua-indent-level: 4
+-- End:
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80