Browse Source

keygrabber: Refactor to be an object instead of a function.

The old keygrabber API wasn't doing what the users want from a
keygrabber module. With tons of boilerplate code, everything could
be done, but it wasn't trivial.

This commit add a default grabber function that implements the
keybinding API already used by `awful.key` and `awful.prompt`.

It also add syntax candy left and right to make the module "feel"
like a native CAPI object.

Nothing is perfect and some parts, like adding root keybindings, are not
vevy pleasing. However it fulfill its goal when it comes to make
previously non-trivial use case very easy to implement and deploy.
Emmanuel Lepage Vallee 1 year ago
parent
commit
2bf930b044
2 changed files with 750 additions and 33 deletions
  1. 6
    24
      keygrabber.c
  2. 744
    9
      lib/awful/keygrabber.lua

+ 6
- 24
keygrabber.c View File

@@ -19,7 +19,7 @@
19 19
  *
20 20
  */
21 21
 
22
-/** awesome keygrabber API
22
+/*
23 23
  * @author Julien Danjou <julien@danjou.info>
24 24
  * @copyright 2008-2009 Julien Danjou
25 25
  * @module keygrabber
@@ -109,31 +109,12 @@ keygrabber_handlekpress(lua_State *L, xcb_key_press_event_t *e)
109 109
     return true;
110 110
 }
111 111
 
112
-/** Grab keyboard input and read pressed keys, calling a callback function at
112
+/* Grab keyboard input and read pressed keys, calling a callback function at
113 113
  * each keypress, until `keygrabber.stop` is called.
114 114
  * The callback function receives three arguments:
115 115
  *
116
- * * a table containing modifiers keys
117
- * * a string with the pressed key
118
- * * a string with either "press" or "release" to indicate the event type.
119
- *
120 116
  * @param callback A callback function as described above.
121
- * @function run
122
- * @usage The following function can be bound to a key, and will be used to
123
- *        resize a client using keyboard.
124
- *
125
- *     function resize(c)
126
- *       keygrabber.run(function(mod, key, event)
127
- *         if event == "release" then return end
128
- *
129
- *         if     key == 'Up'   then c:relative_move(0, 0, 0, 5)
130
- *         elseif key == 'Down' then c:relative_move(0, 0, 0, -5)
131
- *         elseif key == 'Right' then c:relative_move(0, 0, 5, 0)
132
- *         elseif key == 'Left'  then c:relative_move(0, 0, -5, 0)
133
- *         else   keygrabber.stop()
134
- *         end
135
- *       end)
136
- *     end
117
+ * @deprecated keygrabber.run
137 118
  */
138 119
 static int
139 120
 luaA_keygrabber_run(lua_State *L)
@@ -153,7 +134,7 @@ luaA_keygrabber_run(lua_State *L)
153 134
 }
154 135
 
155 136
 /** Stop grabbing the keyboard.
156
- * @function stop
137
+ * @deprecated keygrabber.stop
157 138
  */
158 139
 int
159 140
 luaA_keygrabber_stop(lua_State *L)
@@ -164,8 +145,9 @@ luaA_keygrabber_stop(lua_State *L)
164 145
 }
165 146
 
166 147
 /** Check if keygrabber is running.
167
- * @function isrunning
148
+ * @deprecated keygrabber.isrunning
168 149
  * @treturn bool A boolean value, true if keygrabber is running, false otherwise.
150
+ * @see keygrabber.is_running
169 151
  */
170 152
 static int
171 153
 luaA_keygrabber_isrunning(lua_State *L)

+ 744
- 9
lib/awful/keygrabber.lua View File

@@ -1,22 +1,162 @@
1 1
 ---------------------------------------------------------------------------
2
---- Keygrabber Stack
2
+--- A keyboard grabbing and transaction object.
3
+--
4
+-- This module allows to grab all keyboard inputs until stopped. It is used
5
+-- to gather input data (such as in `awful.prompt`), implement VI-like keybindings
6
+-- or multi key transactions such as emulating the Alt+Tab behavior.
7
+--
8
+-- Note that this module has been redesigned in Awesome 4.3 to be object oriented
9
+-- and stateful. The use of the older global keygrabbing API is discouraged
10
+-- going forward since it had problem with recursive keygrabbers and required
11
+-- a lot of boiler plate code to get anything done.
12
+--
13
+-- Using keygrabber as transactions
14
+-- --------------------------------
15
+--
16
+-- The transactional keybindings are keybindings that start with a normal
17
+-- keybindings, but only end when a (mod)key is released. In the classic
18
+-- "Alt+Tab" transaction, first pressing "Alt+Tab" will display a popup listing
19
+-- all windows (clients). This popup will only disappear when "Alt" is released.
20
+--
21
+-- @DOC_text_awful_keygrabber_alttab_EXAMPLE@
22
+--
23
+-- In that example, because `export_keybindings` is set to `true`, pressing
24
+-- `alt+tab` or `alt+shift+tab` will start the transaction even if the
25
+-- keygrabber is not (yet) running.
26
+--
27
+-- Using keygrabber for modal keybindings (VI like)
28
+-- ------------------------------------------------
29
+--
30
+-- VI-like modal keybindings are trigerred by a key, like `Escape`, followed by
31
+-- either a number, an adjective (or noun) and closed by a verb. For example
32
+-- `<Escape>+2+t+f` could mean "focus (f) the second (2) tag (t)".
33
+-- `<Escape>+2+h+t+f` would "focus (f) two (2) tags (t) to the right (h)".
34
+--
35
+-- Here is a basic implementation of such a system. Note that the action
36
+-- functions themselves are not implemented to keep the example size and
37
+-- complexity to a minimum. The implementation is just if/elseif of all action
38
+-- and the code can be found in the normal `rc.lua` keybindings section:
39
+--
40
+-- @DOC_text_awful_keygrabber_vimode_EXAMPLE@
41
+--
42
+-- Using signals
43
+-- -------------
44
+--
45
+-- When the keygrabber is running, it will emit signals on each event. The
46
+-- format is "key_name".."::".."pressed_or_released". For example, to attach
47
+-- a callback to `Escape` being pressed, do:
48
+--
49
+--    mykeygrabber:connect_signal("Escape::pressed", function(self, modifiers, modset)
50
+--        print("Escape called!")
51
+--    end)
52
+--
53
+-- The `self` argument is the keygrabber instance. The `modifiers` is a list of
54
+-- all the currently pressed modifiers and the `modset` is the same table, but
55
+-- with the modifiers as keys instead of value. It allow to save time by
56
+-- checking `if modset.Mod4 then ... end` instead of looping into the `modifiers`
57
+-- table or using `gears.table`.
3 58
 --
4 59
 -- @author dodo
60
+-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
5 61
 -- @copyright 2012 dodo
62
+-- @copyright 2017 Emmanuel Lepage Vallee
63
+-- @classmod awful.keygrabber
6 64
 ---------------------------------------------------------------------------
7 65
 
8 66
 local ipairs = ipairs
9 67
 local table = table
10
-local capi = {
11
-    keygrabber = keygrabber }
68
+local gdebug = require('gears.debug')
69
+local akey = require("awful.key")
70
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
71
+local gtable = require("gears.table")
72
+local gobject = require("gears.object")
73
+local gtimer = require("gears.timer")
74
+local glib = require("lgi").GLib
75
+local capi = { keygrabber = keygrabber, root = root }
12 76
 
13
-local keygrabber = {}
77
+local keygrab = {}
14 78
 
15 79
 -- Private data
16 80
 local grabbers = {}
17 81
 local keygrabbing = false
18 82
 
83
+local keygrabber = {
84
+    object = {}
85
+}
86
+
87
+-- Instead of checking for every modifiers, check the key directly.
88
+--FIXME This is slightly broken but still good enough for `mask_modkeys`
89
+local conversion = {
90
+    Super_L   = "Mod4",
91
+    Control_L = "Control",
92
+    Shift_L   = "Shift",
93
+    Alt_L     = "Mod1",
94
+    Super_R   = "Mod4",
95
+    Control_R = "Control",
96
+    Shift_R   = "Shift",
97
+    Alt_R     = "Mod1",
98
+}
99
+
100
+--BEGIN one day create a proper API to add and remove keybindings at runtime.
101
+-- Doing it this way is horrible.
102
+
103
+-- This list of keybindings to add in the next event loop cycle.
104
+local delay_list = {}
105
+
106
+local function add_root_keybindings(self, list)
107
+    assert(
108
+        list, "`add_root_keybindings` needs to be called with a list of keybindings"
109
+    )
110
+
111
+    local was_started = #delay_list > 0
112
+
113
+    -- When multiple `awful.keygrabber` objects are created in `rc.lua`, avoid
114
+    -- unpacking and repacking all keys for each instance and instead merge
115
+    -- everything into one operation. In not so extreme cases, not doing so
116
+    -- would slow down `awesome.restart()` by a small, but noticeable amount
117
+    -- of time.
118
+    gtable.merge(delay_list, list)
119
+
120
+    -- As of Awesome v4.3, `root.keys()` is an all or nothing API and there
121
+    -- isn't a standard mechanism to add and remove keybindings at runtime
122
+    -- without replacing the full list. Given `rc.lua` sets this list, not
123
+    -- using a delayed call would cause all `awful.keygrabber` created above
124
+    -- `root.keys(globalkeys)` to be silently overwritten. --FIXME v5
125
+    if not was_started then
126
+        gtimer.delayed_call(function()
127
+            local ret = {}
128
+
129
+            for _, v in ipairs(delay_list) do
130
+                local mods, key, press, release, description = unpack(v)
131
+
132
+                if press then
133
+                    local old_press = press
134
+                    press = function(...)
135
+                        self:start()
136
+                        old_press(...)
137
+                    end
138
+                end
139
+
140
+                if release then
141
+                    local old_release = release
142
+                    release = function(...)
143
+                        self:start()
144
+                        old_release(...)
145
+                    end
146
+                end
147
+
148
+                table.insert(ret, akey(mods, key, press, release, description))
149
+            end
150
+
151
+            -- Wow...
152
+            capi.root.keys(gtable.join( capi.root.keys(), unpack(ret) ))
153
+
154
+            delay_list = {}
155
+        end)
156
+    end
157
+end
158
+
159
+--END hack
19 160
 
20 161
 local function grabber(mod, key, event)
21 162
     for _, keygrabber_function in ipairs(grabbers) do
@@ -27,17 +167,119 @@ local function grabber(mod, key, event)
27 167
     end
28 168
 end
29 169
 
170
+local function runner(self, modifiers, key, event)
171
+    -- Stop the keygrabber with the `stop_key`
172
+    if key == self.stop_key
173
+        and event == self.release_event and self.stop_key then
174
+        self:stop(key, modifiers)
175
+        return false
176
+    end
177
+
178
+    -- Stop when only a subset of keys are allowed and it isn't one of them.
179
+    if self._private.allowed_keys and not self._private.allowed_keys[key] then
180
+        self:stop(key, modifiers)
181
+        return false
182
+    end
183
+
184
+    -- Support multiple stop keys
185
+    if type(self.stop_key) == "table" and event == self.release_event then
186
+        for _, k in ipairs(self.stop_key) do
187
+            if k == key then
188
+                self:stop(k, modifiers)
189
+                return false
190
+            end
191
+        end
192
+    end
193
+
194
+    local is_modifier = conversion[key] ~= nil
195
+
196
+    -- Reset the inactivity timer on each events.
197
+    if self._private.timer and self._private.timer.started then
198
+        self._private.timer:again()
199
+    end
200
+
201
+    -- Lua strings are bytes, to handle UTF-8, use GLib
202
+    local seq_len = glib.utf8_strlen(self.sequence, -1)
203
+
204
+    -- Record the key sequence
205
+    if key == "BackSpace" and seq_len > 0 then
206
+        self.sequence = glib.utf8_substring(self.sequence, 0, seq_len - 2)
207
+    elseif glib.utf8_strlen(key, -1) == 1 and  event == "release" then
208
+        self.sequence = self.sequence..key
209
+    end
210
+
211
+    -- Convert index array to hash table
212
+    local mod = {}
213
+    for _, v in ipairs(modifiers) do mod[v] = true end
214
+
215
+    -- Emit some signals so the user can connect to a single type of event.
216
+    self:emit_signal(key.."::"..event, modifiers, mod)
217
+
218
+    local filtered_modifiers = {}
219
+
220
+    -- User defined cases
221
+    if self._private.keybindings[key] and event == "press" then
222
+        -- Remove caps and num lock
223
+        for _, m in ipairs(modifiers) do
224
+            if not gtable.hasitem(akey.ignore_modifiers, m) then
225
+                table.insert(filtered_modifiers, m)
226
+            end
227
+        end
228
+
229
+        for _,v in ipairs(self._private.keybindings[key]) do
230
+            if #filtered_modifiers == #v[1] then
231
+                local match = true
232
+                for _,v2 in ipairs(v[1]) do
233
+                    match = match and mod[v2]
234
+                end
235
+                if match then
236
+                    v[3](self)
237
+
238
+                    if self.mask_event_callback ~= false then
239
+                        return
240
+                    end
241
+                end
242
+            end
243
+        end
244
+    end
245
+
246
+    -- Do not call the callbacks on modkeys
247
+    if is_modifier and self.mask_modkeys then
248
+        return
249
+    end
250
+
251
+    if self.keypressed_callback and event == "press" then
252
+        self.keypressed_callback(self, modifiers, key, event)
253
+    elseif self.keyreleased_callback and event == "release" then
254
+        self.keyreleased_callback(self, modifiers, key, event)
255
+    end
256
+end
257
+
30 258
 --- Stop grabbing the keyboard for the provided callback.
259
+--
31 260
 -- When no callback is given, the last grabber gets removed (last one added to
32 261
 -- the stack).
33
-function keygrabber.stop(g)
262
+--
263
+-- @param[opt] g The key grabber that must be removed.
264
+-- @deprecated awful.keygrabber.stop
265
+function keygrab.stop(g)
266
+    -- If `run` is used directly and stop() is called with `g==nil`, get the
267
+    -- first keygrabber.
268
+    g = g
269
+        or keygrab.current_instance and keygrab.current_instance.grabber
270
+        or grabbers[#grabbers]
271
+
34 272
     for i, v in ipairs(grabbers) do
35 273
         if v == g then
36 274
             table.remove(grabbers, i)
37 275
             break
38 276
         end
39 277
     end
278
+
279
+    if g and keygrab.current_instance and keygrab.current_instance.grabber == g then
280
+        keygrab.current_instance = nil
281
+    end
282
+
40 283
     -- Stop the global key grabber if the last grabber disappears from stack.
41 284
     if #grabbers == 0 then
42 285
         keygrabbing = false
@@ -45,7 +287,419 @@ function keygrabber.stop(g)
45 287
     end
46 288
 end
47 289
 
48
----
290
+--- The keygrabber timeout.
291
+--
292
+-- @DOC_text_awful_keygrabber_timeout_EXAMPLE@
293
+--
294
+-- @property timeout
295
+-- @see gears.timer
296
+-- @see timeout_callback
297
+
298
+--- The key on which the keygrabber listen to terminate itself.
299
+--
300
+-- When this is set, the running keygrabber will quit when [one of] the release
301
+-- key event occurs.
302
+--
303
+-- By default, the event is `press`. It is common for use case like the
304
+-- `awful.prompt` where `return` (enter) will terminate the keygrabbing. Using
305
+-- `release` as an event is more appropriate when the keygrabber is tied to a
306
+-- modifier key. For example, an Alt+Tab implementation stops when `mod1` (Alt)
307
+-- is released.
308
+--
309
+-- It can also be a table containing many keys (as values).
310
+--
311
+-- @DOC_text_awful_keygrabber_stop_keys_EXAMPLE@
312
+--
313
+-- @DOC_text_awful_keygrabber_stop_key_EXAMPLE@
314
+--
315
+-- Please note that modkeys are not accepted as `stop_key`s. You have to use
316
+-- their corresponding key names such as `Control_L`.
317
+--
318
+-- @property stop_key
319
+-- @param[opt=nil] string|table stop_key
320
+-- @see release_event
321
+
322
+--- The event on which the keygrabbing will be terminated.
323
+--
324
+-- the valid values are:
325
+--
326
+-- * "press" (default)
327
+-- * "release"
328
+--
329
+-- @property release_event
330
+-- @param string
331
+-- @see stop_key
332
+
333
+--- Whether or not to execute the key press/release callbacks when keybindings are called.
334
+--
335
+-- When this property is set to false, the `keyreleased_callback` and
336
+-- `keypressed_callback` callbacks will be executed even when the event was
337
+-- consumed by a `keybinding`.
338
+--
339
+-- By default, keybindings block those callbacks from being executed.
340
+--
341
+-- @property mask_event_callback
342
+-- @param[opt=true] boolean
343
+-- @see keybindings
344
+-- @see keyreleased_callback
345
+-- @see keypressed_callback
346
+
347
+--- Do not call the callbacks on modifier keys (like `Control` or `Mod4`) events.
348
+-- @property mask_modkeys
349
+-- @param[opt=false] boolean
350
+-- @see mask_event_callback
351
+
352
+--- Export all keygrabber keybindings as root (global) keybindings.
353
+--
354
+-- When this is enabled, calling all of the keygrabber object `keybinding`s will
355
+-- will create root `awful.key` and will automatically starts the grabbing.
356
+--
357
+-- Use this with caution. If many keygrabber or "real" root keybindings are set
358
+-- on the same key combination, they are all executed and there is almost no
359
+-- safe way to undo that. Make sure the `keygrabber` that use this option
360
+-- have a single instance.
361
+--
362
+-- @property export_keybindings
363
+-- @param[opt=false] boolean
364
+
365
+--- The root (global) keybinding to start this keygrabber.
366
+--
367
+-- Instead of adding an entry to `root.keys` or `rc.lua` `globalkeys` section,
368
+-- this property can be used to take care of everything. This way, it becomes
369
+-- easier to package modules using keygrabbers.
370
+--
371
+-- @DOC_text_awful_keygrabber_root_keybindings_EXAMPLE@
372
+--
373
+-- @property root_keybindings
374
+-- @see export_keybindings
375
+-- @see keybindings
376
+
377
+--- The keybindings associated with this keygrabber.
378
+--
379
+-- The keybindings syntax is the same as `awful.key` or `awful.prompt.hooks`. It
380
+-- consists of a table with 4 entries.
381
+--
382
+-- * `mods` A table with modifier keys, such as `shift`, `mod4`, `mod1` (alt) or
383
+--  `control`.
384
+-- * `key` The key name, such as `left` or `f`
385
+-- * `callback` A function that will be called when the key combination is
386
+--  pressed.
387
+-- * `description` A table various metadata to be used for `awful.hotkeys_popup`.
388
+--
389
+-- @property keybindings
390
+-- @param table
391
+-- @see export_keybindings
392
+-- @see root_keybindings
393
+
394
+--- If any key is pressed that is not in this list, the keygrabber is stopped.
395
+--
396
+-- When defined, this property allows to define an implicit way to release the
397
+-- keygrabber. It helps save some boilerplate code in the handler callbacks.
398
+--
399
+-- It is useful when a transaction only handle a limited number of keys. If
400
+-- a key unhandled by the transaction is trigerred, the transaction is
401
+-- canceled.
402
+--
403
+-- @DOC_text_awful_keygrabber_allowed_keys_EXAMPLE@
404
+--
405
+-- @property allowed_keys
406
+-- @tparam[opt=nil] table|nil allowed_keys The list of keys.
407
+
408
+--- The sequence of keys recorded since the start of the keygrabber.
409
+--
410
+-- In this example, the `stop_callback` is used to retrieve the final key
411
+-- sequence.
412
+--
413
+-- Please note that backspace will remove elements from the sequence and
414
+-- named keys and modifiers are ignored.
415
+--
416
+-- @DOC_text_awful_keygrabber_autostart_EXAMPLE@
417
+--
418
+-- @property sequence
419
+-- @param string
420
+--
421
+
422
+--- The current (running) instance of this keygrabber.
423
+--
424
+-- The keygrabber instance is created when the keygrabber starts. It is an object
425
+-- mirroring this keygrabber object, but where extra properties can be added. It
426
+-- is useful to keep some contextual data part of the current transaction without
427
+-- poluting the original object of having extra boilerplate code.
428
+--
429
+-- @tfield keygrabber current_instance
430
+-- @see property::current_instance
431
+
432
+--- The global signal used to track the `current_instance`.
433
+--
434
+-- @signal property::current_instance
435
+
436
+--- If a keygrabber is currently running.
437
+-- @tfield boolean is_running
438
+
439
+--- Start the keygrabber.
440
+--
441
+-- Note that only a single keygrabber can be started at any one time. If another
442
+-- keygrabber (or this one) is currently running. This method returns false.
443
+--
444
+-- @treturn boolean If the keygrabber was successfully started.
445
+function keygrabber:start()
446
+    if self.grabber or keygrab.current_instance then
447
+        return false
448
+    end
449
+
450
+    self.current_instance = setmetatable({}, {
451
+        __index = self,
452
+        __newindex = function(tab, key, value)
453
+            if keygrabber["set_"..key] then
454
+                self[key](self, value)
455
+            else
456
+                rawset(tab, key, value)
457
+            end
458
+        end
459
+    })
460
+
461
+    self.sequence = ""
462
+
463
+    if self.start_callback then
464
+        self.start_callback(self.current_instance)
465
+    end
466
+
467
+    self.grabber = keygrab.run(function(...) return runner(self, ...) end)
468
+
469
+    -- Ease making keygrabber that wont hang forever if no action is taken.
470
+    if self.timeout and not self._private.timer then
471
+        self._private.timer = gtimer {
472
+            timeout     = self.timeout,
473
+            single_shot = true,
474
+            callback    = function()
475
+                if self.timeout_callback then
476
+                    pcall(self.timeout_callback, self)
477
+                end
478
+                self:stop()
479
+            end
480
+        }
481
+    end
482
+
483
+    if self._private.timer then
484
+        self._private.timer:start()
485
+    end
486
+
487
+    keygrab.current_instance = self
488
+
489
+    keygrab.emit_signal("property::current_instance", keygrab.current_instance)
490
+
491
+    self:emit_signal("started")
492
+end
493
+
494
+--- Stop the keygrabber.
495
+-- @function keygrabber:stop
496
+function keygrabber:stop(_stop_key, _release_mods) -- (at)function disables ldoc params
497
+    keygrab.stop(self.grabber)
498
+
499
+    if self.stop_callback then
500
+        self.stop_callback(
501
+            self.current_instance, _stop_key, _release_mods, self.sequence
502
+        )
503
+    end
504
+
505
+    keygrab.emit_signal("property::current_instance", nil)
506
+
507
+    self.grabber = nil
508
+    self:emit_signal("stopped")
509
+end
510
+
511
+--- Add a keybinding to this keygrabber.
512
+--
513
+-- Those keybindings will automatically start the keygrabbing when hit.
514
+--
515
+-- @tparam table mods A table with modifier keys, such as `shift`, `mod4`, `mod1` (alt) or
516
+--  `control`.
517
+-- @tparam string key The key name, such as `left` or `f`
518
+-- @tparam function callback A function that will be called when the key
519
+-- combination is pressed.
520
+-- @tparam[opt] table description A table various metadata to be used for `awful.hotkeys_popup`.
521
+-- @tparam string description.description The keybinding description
522
+-- @tparam string description.group The keybinding group
523
+function keygrabber:add_keybinding(mods, key, callback, description)
524
+    self._private.keybindings[key] = self._private.keybindings[key] or {}
525
+    table.insert(self._private.keybindings[key], {
526
+        mods, key, callback, description
527
+    })
528
+
529
+    if self.export_keybindings then
530
+        add_root_keybindings(self, {{mods, key, callback, description}})
531
+    end
532
+end
533
+
534
+function keygrabber:set_root_keybindings(keys)
535
+    add_root_keybindings(self, keys)
536
+end
537
+
538
+-- Turn into a set.
539
+function keygrabber:set_allowed_keys(keys)
540
+    self._private.allowed_keys = {}
541
+
542
+    for _, v in ipairs(keys) do
543
+        self._private.allowed_keys[v] = true
544
+    end
545
+end
546
+
547
+--- When the keygrabber starts.
548
+-- @signal started
549
+
550
+--- When the keygrabber stops.
551
+-- @signal stopped
552
+
553
+--- A function called when a keygrabber starts.
554
+-- @callback start_callback
555
+-- @tparam keygrabber keygrabber The keygrabber.
556
+
557
+--- The callback when the keygrabbing stops.
558
+--
559
+-- @usage local function my_done_cb(self, stop_key, release_mods, sequence)
560
+--    -- do something
561
+-- end
562
+-- @tparam table self The current transaction object.
563
+-- @tparam string stop_key The key(s) that stop the keygrabbing (if any)
564
+-- @tparam table release_mods The release modifiers key (if any)
565
+-- @tparam sting sequence The recorded key sequence.
566
+-- @callback stop_callback
567
+-- @see sequence
568
+-- @see stop
569
+
570
+--- The function called when the keygrabber stops because of a `timeout`.
571
+--
572
+-- Note that the `stop_callback` is also called when the keygrabbers timeout.
573
+--
574
+-- @callback timeout_callback
575
+-- @see timeout
576
+
577
+--- The callback function to call with mod table, key and command as arguments
578
+-- when a key was pressed.
579
+--
580
+-- @callback keypressed_callback
581
+-- @tparam table self The current transaction object.
582
+-- @tparam table mod The current modifiers (like "Control" or "Shift").
583
+-- @tparam string key The key name.
584
+-- @tparam string event The event ("press" or "release").
585
+-- @usage local function my_keypressed_cb(mod, key, command)
586
+--    -- do something
587
+-- end
588
+
589
+--- The callback function to call with mod table, key and command as arguments
590
+-- when a key was released.
591
+-- @usage local function my_keyreleased_cb(mod, key, command)
592
+--    -- do something
593
+-- end
594
+-- @callback keyreleased_callback
595
+-- @tparam table self The current transaction object.
596
+-- @tparam table mod The current modifiers (like "Control" or "Shift").
597
+-- @tparam string key The key name.
598
+-- @tparam string event The event ("press" or "release")
599
+
600
+--- A wrapper around the keygrabber to make it easier to add keybindings.
601
+--
602
+-- This is similar to the `awful.prompt`, but without an actual widget. It can
603
+-- be used to implement custom transactions such as alt+tab or keyboard menu
604
+-- navigation.
605
+--
606
+-- Note that the object returned can be used as a client or global keybinding
607
+-- callback without modification. Make sure to set `stop_key` and `release_event`
608
+-- to proper values to avoid permanently locking the keyboard.
609
+--
610
+-- @tparam table args
611
+-- @tparam[opt="press"] string args.release_event Release event ("press" or "release")
612
+-- @tparam[opt=nil] string|table args.stop_key The key that has to be kept pressed.
613
+-- @tparam table args.keybindings All keybindings.
614
+-- @tparam[opt=-1] number args.timeout The maximum inactivity delay.
615
+-- @tparam[opt=true] boolean args.mask_event_callback Do not call `keypressed_callback`
616
+--  or `keyreleased_callback` when a hook is called.
617
+-- @tparam[opt] function args.start_callback
618
+-- @tparam[opt] function args.stop_callback
619
+-- @tparam[opt] function args.timeout_callback
620
+-- @tparam[opt] function args.keypressed_callback
621
+-- @tparam[opt] function args.keyreleased_callback
622
+-- @tparam[opt=nil] table|nil args.allowed_keys A table with all keys that
623
+--  **wont** stop the keygrabber.
624
+-- @tparam[opt] table args.root_keybindings The root (global) keybindings.
625
+-- @tparam[opt=false] boolean args.export_keybindings Create root (global) keybindings.
626
+-- @tparam[opt=false] boolean args.autostart Start the grabbing immediately
627
+-- @tparam[opt=false] boolean args.mask_modkeys Do not call the callbacks on
628
+--  modifier keys (like `Control` or `Mod4`) events.
629
+-- @function awful.keygrabber
630
+function keygrab.run_with_keybindings(args)
631
+    args = args or {}
632
+
633
+    local ret = gobject {
634
+        enable_properties   = true,
635
+        enable_auto_signals = true
636
+    }
637
+
638
+    rawset(ret, "_private", {})
639
+
640
+    -- Do not use `rawset` or `_private` because it uses the `gears.object`
641
+    -- auto signals.
642
+    ret.sequence = ""
643
+
644
+    gtable.crush(ret, keygrabber, true)
645
+
646
+    gtable.crush(ret, args)
647
+
648
+    ret._private.keybindings = {}
649
+    ret.release_event = args.release_event or "press"
650
+
651
+    -- Build the hook map
652
+    for _,v in ipairs(args.keybindings or {}) do
653
+        if #v >= 3 and #v <= 4 then
654
+            local _,key,callback = unpack(v)
655
+            if type(callback) == "function" then
656
+                ret._private.keybindings[key] = ret._private.keybindings[key] or {}
657
+                table.insert(ret._private.keybindings[key], v)
658
+            else
659
+                gdebug.print_warning(
660
+                    "The hook's 3rd parameter has to be a function. " ..
661
+                        gdebug.dump(v)
662
+                )
663
+            end
664
+        else
665
+            gdebug.print_warning(
666
+                "The hook has to have at least 3 parameters. ".. gdebug.dump(v)
667
+            )
668
+        end
669
+    end
670
+
671
+    if args.export_keybindings then
672
+        add_root_keybindings(ret, args.keybindings)
673
+    end
674
+
675
+    local mt = getmetatable(ret)
676
+
677
+    -- Add syntax-sugar to call `:start()`.
678
+    -- This allows keygrabbers object to be used as callbacks "functions".
679
+    function mt.__call()
680
+        ret:start()
681
+    end
682
+
683
+    if args.autostart then
684
+        ret:start()
685
+    end
686
+
687
+    return ret
688
+end
689
+
690
+--- Run a grabbing function.
691
+--
692
+-- Calling this is equivalent to `keygrabber.run`.
693
+--
694
+-- @param g The key grabber callback that will get the key events until it
695
+--   will be deleted or a new grabber is added.
696
+-- @return the given callback `g`.
697
+--
698
+-- @deprecated awful.keygrabber.run
699
+-- @see keygrabber.run
700
+
701
+--- A lower level API to interact with the keygrabber directly.
702
+--
49 703
 -- Grab keyboard input and read pressed keys, calling the least callback
50 704
 -- function from the stack at each keypress, until the stack is empty.
51 705
 --
@@ -56,11 +710,27 @@ end
56 710
 --
57 711
 -- * a table containing modifiers keys
58 712
 -- * a string with the pressed key
713
+-- * a string with either "press" or "release" to indicate the event type.
714
+--
715
+-- Here is the content of the modifier table:
716
+--
717
+-- <table>
718
+--  <tr style='font-weight: bold;'>
719
+--   <th align='center'>Modifier name </th>
720
+--   <th align='center'>Key name</th>
721
+--   <th align='center'>Alternate key name</th>
722
+--  </tr>
723
+--  <tr><td> Mod4</td><td align='center'> Super_L </td><td align='center'> Super_R </td></tr>
724
+--  <tr><td> Control </td><td align='center'> Control_L </td><td align='center'> Control_R </td></tr>
725
+--  <tr><td> Shift </td><td align='center'> Shift_L </td><td align='center'> Shift_R </td></tr>
726
+--  <tr><td> Mod1</td><td align='center'> Alt_L </td><td align='center'> Alt_R </td></tr>
727
+-- </table>
59 728
 --
60 729
 -- A callback can return `false` to pass the events to the next
61 730
 -- keygrabber in the stack.
731
+--
732
+-- @param g The key grabber callback that will get the key events until it
733
+--  will be deleted or a new grabber is added.
62 734
 -- @return the given callback `g`.
63 735
 -- @usage
64 736
 -- -- The following function can be bound to a key, and be used to resize a
@@ -79,19 +749,80 @@ end
79 749
 --     end
80 750
 --   end)
81 751
 -- end
82
-function keygrabber.run(g)
752
+-- @function awful.keygrabber.run
753
+function keygrab.run(g)
83 754
     -- Remove the grabber if it is in the stack.
84
-    keygrabber.stop(g)
755
+    keygrab.stop(g)
756
+
85 757
     -- Record the grabber that has been added most recently.
86 758
     table.insert(grabbers, 1, g)
759
+
87 760
     -- Start the keygrabber if it is not running already.
88 761
     if not keygrabbing then
89 762
         keygrabbing = true
90 763
         capi.keygrabber.run(grabber)
91 764
     end
765
+
92 766
     return g
93 767
 end
94 768
 
95
-return keygrabber
769
+-- Implement the signal system for the keygrabber.
770
+
771
+local signals = {}
772
+
773
+--- Connect to a signal for all keygrabbers at once.
774
+-- @function awful.keygrabber.connect_signal
775
+-- @tparam string name The signal name.
776
+-- @tparam function callback The callback.
777
+function keygrab.connect_signal(name, callback)
778
+    signals[name] = signals[name] or {}
779
+
780
+    -- Use table.insert instead of signals[name][callback] = true to make
781
+    -- the execution order stable across `emit_signal`. This avoids some
782
+    -- heisenbugs.
783
+    table.insert(signals[name], callback)
784
+end
785
+
786
+--- Disconnect to a signal for all keygrabbers at once.
787
+-- @function awful.keygrabber.disconnect_signal
788
+-- @tparam string name The signal name.
789
+-- @tparam function callback The callback.
790
+function keygrab.disconnect_signal(name, callback)
791
+    signals[name] = signals[name] or {}
792
+
793
+    for k, v in ipairs(signals[name]) do
794
+        if v == callback then
795
+            table.remove(signals[name], k)
796
+            break
797
+        end
798
+    end
799
+end
800
+
801
+--- Emit a signal on the keygrabber module itself.
802
+--
803
+-- Warning, usually don't use this directly, use
804
+-- `my_keygrabber:emit_signal(name, ...)`. This function works on the whole
805
+-- keygrabber module, not one of its instance.
806
+--
807
+-- @function awful.keygrabber.emit_signal
808
+-- @tparam string name The signal name.
809
+-- @param ... Other arguments for the callbacks.
810
+function keygrab.emit_signal(name, ...)
811
+    signals[name] = signals[name] or {}
812
+
813
+    for _, cb in ipairs(signals[name]) do
814
+        cb(...)
815
+    end
816
+end
817
+
818
+function keygrab.get_is_running()
819
+    return keygrab.current_instance ~= nil
820
+end
821
+
822
+--@DOC_object_COMMON@
823
+
824
+return setmetatable(keygrab, {
825
+    __call = function(_, args) return keygrab.run_with_keybindings(args) end
826
+})
96 827
 
97 828
 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

Loading…
Cancel
Save