summaryrefslogtreecommitdiffstats
path: root/.config/awesome/keychain.lua
blob: 86ce7b2e3335f13563d8ee61d7815965cf7dfa0d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
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