From a8dcd3b2a297f065b67afe45e8632e3add4e1145 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 1 Dec 2019 19:58:51 -0500 Subject: [PATCH 01/20] tests: Add capi.client to the prompt unit tests. It will be needed later. --- spec/awful/prompt_spec.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/awful/prompt_spec.lua b/spec/awful/prompt_spec.lua index 5d27570bd..2c0f65225 100644 --- a/spec/awful/prompt_spec.lua +++ b/spec/awful/prompt_spec.lua @@ -57,6 +57,9 @@ insulate('main', function () run = function() end, stop = function() end, } + _G.client = { + connect_signal = function() end + } -- luacheck: globals string function string.wlen(self) return #self From fbe2b34af4e8c0419da0e8c655ff61bb02990e33 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 19 Oct 2019 19:49:14 -0400 Subject: [PATCH 02/20] properties: Allow to delay some operations. This will be useful for the append/remove support. --- lib/gears/object/properties.lua | 37 ++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/gears/object/properties.lua b/lib/gears/object/properties.lua index f671d98a4..ed072fc2f 100644 --- a/lib/gears/object/properties.lua +++ b/lib/gears/object/properties.lua @@ -8,6 +8,7 @@ --------------------------------------------------------------------------- local gtable = require("gears.table") +local gtimer = nil --require("gears.timer") -- local gdebug = require("gears.debug") local object = {} local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1) @@ -83,6 +84,8 @@ function object.capi_index_fallback(class, args) end end + assert(type(class) ~= "function") + -- Attach the accessor methods class.set_index_miss_handler(getter) class.set_newindex_miss_handler(setter) @@ -190,7 +193,7 @@ local function copy_object(obj, to_set, name, capi_name, is_object, join_if, set }) end -function object._legacy_accessors(obj, name, capi_name, is_object, join_if, set_empty) +function object._legacy_accessors(obj, name, capi_name, is_object, join_if, set_empty, delay) -- Some objects have a special "object" property to add more properties, but -- not all. @@ -248,15 +251,33 @@ function object._legacy_accessors(obj, name, capi_name, is_object, join_if, set_ --TODO v6 Use the original directly and drop this legacy copy - local result = is_formatted and objs - or gtable.join(unpack(objs)) + local function apply() + local result = is_formatted and objs + or gtable.join(unpack(objs)) - if is_object and capi_name then - self[capi_name](self, result) - elseif capi_name then - obj[capi_name](result) + if is_object and capi_name then + self[capi_name](self, result) + elseif capi_name then + obj[capi_name](result) + else + self._private[name.."_formatted"] = result + end + end + + -- Some properties, like keys, are expensive to set, schedule them + -- instead. + if not delay then + apply() else - self._private[name.."_formatted"] = result + if not self._private["_delayed_"..name] then + gtimer = gtimer or require("gears.timer") + gtimer.delayed_call(function() + self._private["_delayed_"..name]() + self._private["_delayed_"..name] = nil + end) + end + + self._private["_delayed_"..name] = apply end self._private[name] = copy_object( From 42dceb1db8a8dc73acd5a8abef5d4ffe9818dad8 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 19 Oct 2019 20:17:31 -0400 Subject: [PATCH 03/20] object: Add a standard implementation for append and remove. This is for legacy accessors only, but it will be expanded later. --- lib/gears/object/properties.lua | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/gears/object/properties.lua b/lib/gears/object/properties.lua index ed072fc2f..6118ef4c3 100644 --- a/lib/gears/object/properties.lua +++ b/lib/gears/object/properties.lua @@ -193,7 +193,8 @@ local function copy_object(obj, to_set, name, capi_name, is_object, join_if, set }) end -function object._legacy_accessors(obj, name, capi_name, is_object, join_if, set_empty, delay) +function object._legacy_accessors(obj, name, capi_name, is_object, join_if, set_empty, delay, add_append_name) + delay = delay or add_append_name -- Some objects have a special "object" property to add more properties, but -- not all. @@ -284,6 +285,31 @@ function object._legacy_accessors(obj, name, capi_name, is_object, join_if, set_ obj, objs, name, capi_name, is_object, join_if, set_empty ) end + + if add_append_name then + magic_obj["append_"..add_append_name] = function(self, obj2) + self._private[name] = self._private[name] + or magic_obj["get_"..name](self, nil) + + table.insert(self._private[name], obj2) + + magic_obj["set_"..name](self, self._private[name]) + end + + magic_obj["remove_"..add_append_name] = function(self, obj2) + self._private[name] = self._private[name] + or magic_obj["get_"..name](self, nil) + + for k, v in ipairs(self._private[name]) do + if v == obj2 then + table.remove(self._private[name], k) + break + end + end + + magic_obj["set_"..name](self, self._private[name]) + end + end end return setmetatable( object, {__call = function(_,...) object.capi_index_fallback(...) end}) From a065e2e1a9da448a6848e531e814d60ff69296d7 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 19 Oct 2019 20:45:26 -0400 Subject: [PATCH 04/20] client: Add append/remove methods for buttons and keys. Another step toward ensuring all components can be manipulated non-destructively by modules. --- lib/awful/client.lua | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/awful/client.lua b/lib/awful/client.lua index 630733264..2a7c978d6 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -365,6 +365,29 @@ function client.cycle(clockwise, s, stacked) end end +--- Append a keybinding. +-- +-- @method append_keybinding +-- @tparam awful.key key The key. +-- @see remove_keybinding +-- @see append_mousebinding +-- @see remove_mousebinding + +--- Remove a keybinding. +-- +-- @method remove_keybinding +-- @tparam awful.key key The key. + +--- Append a mousebinding. +-- +-- @method append_mousebinding +-- @tparam awful.button button The button. + +--- Remove a mousebinding. +-- +-- @method remove_mousebinding +-- @tparam awful.button button The button. + --- Get the master window. -- -- @legacylayout awful.client.getmaster @@ -1348,13 +1371,13 @@ object.properties._legacy_accessors(client, "buttons", "_buttons", true, functio return new_btns[1] and ( type(new_btns[1]) == "button" or new_btns[1]._is_capi_button ) or false -end, true) +end, true, true, "mousebinding") object.properties._legacy_accessors(client, "keys", "_keys", true, function(new_btns) return new_btns[1] and ( type(new_btns[1]) == "key" or new_btns[1]._is_capi_key ) or false -end, true) +end, true, true, "keybinding") --- Set the client shape. -- @property shape From c96487515f90b7d0e785e1b672c5b3281275995f Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 20 Oct 2019 01:10:08 -0400 Subject: [PATCH 05/20] object: Make the legacy accessor code more robust. * Using `= one and two or three` is a bad idea on boolean. * Using # to check if a table has content doesn't work on named keys --- lib/gears/object/properties.lua | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/gears/object/properties.lua b/lib/gears/object/properties.lua index 6118ef4c3..d1ab5a223 100644 --- a/lib/gears/object/properties.lua +++ b/lib/gears/object/properties.lua @@ -135,7 +135,7 @@ end -- -- TO BE USED FOR DEPRECATION ONLY. -local function copy_object(obj, to_set, name, capi_name, is_object, join_if, set_empty) +local function copy_object(obj, to_set, name, capi_name, is_object, join_if, set_empty)-- luacheck: no unused local ret = gtable.clone(to_set, false) -- .buttons used to be a function taking the result of `gears.table.join`. @@ -148,12 +148,14 @@ local function copy_object(obj, to_set, name, capi_name, is_object, join_if, set -- {deprecated_in=5} -- ) - new_objs, self = is_object and new_objs or self, is_object and self or obj + if not is_object then + new_objs, self = self, obj + end + + local has_content = new_objs and next(new_objs) -- Setter - if new_objs and #new_objs > 0 then - if (not set_empty) and not next(new_objs) then return end - + if new_objs and has_content then local is_formatted = join_if(new_objs) -- Because modules may rely on :buttons taking a list of From a8e8c46b56d6a111ea293d45e5bd972debd59e73 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 16 Oct 2019 02:10:15 -0400 Subject: [PATCH 06/20] awful.keyboard: Add a method to add a default client key. This is the first commit of a new API to add and remove buttons and keys from clients. The goal is to get rid of the default `rc.lua` "hardcoded" list of client buttons and keys to allow modules to modify the defaults. This is part of the larger effort to make `rc.lua` modular. --- lib/awful/keyboard.lua | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/awful/keyboard.lua b/lib/awful/keyboard.lua index da549c8b3..5c04df89f 100644 --- a/lib/awful/keyboard.lua +++ b/lib/awful/keyboard.lua @@ -6,7 +6,7 @@ -- @inputmodule awful.keyboard --------------------------------------------------------------------------- -local capi = {root = root, awesome = awesome} +local capi = {root = root, awesome = awesome, client = client} local module = {} --- Convert the modifiers into pc105 key names @@ -138,4 +138,39 @@ function module.remove_global_keybinding(key) capi.root._remove_key(key) end + +local default_keys = {} + +--- Add an `awful.key` to the default client keys. +-- +-- @staticfct awful.keyboard.append_client_keybinding +-- @tparam awful.key key The key. +-- @emits client_keybinding::added +-- @emitstparam client_keybinding::added awful.key key The key. +-- @see awful.key + +function module.append_client_keybinding(key) + table.insert(default_keys, key) + + for _, c in ipairs(capi.client.get(nil, false)) do + c:append_keybinding(key) + end + + capi.client.emit_signal("client_keybinding::added", key) +end + +--- Add a `awful.key`s to the default client keys. +-- +-- @staticfct awful.keyboard.append_client_keybindings +-- @tparam table keys A table containing `awful.key` objects. +-- @emits client_keybinding::added +-- @emitstparam client_keybinding::added awful.key key The key. +-- @see awful.key +-- @see awful.keyboard.append_client_keybinding + +function module.append_client_keybindings(keys) + for _, key in ipairs(keys) do + module.append_client_keybinding(key) + end +end return module From 370e754006d051edee6e0052545fa92f35be2ded Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 16 Oct 2019 02:13:45 -0400 Subject: [PATCH 07/20] awful.mouse: Add a function to add a new `awful.button`s to clients. --- lib/awful/mouse/init.lua | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/awful/mouse/init.lua b/lib/awful/mouse/init.lua index 5e768fe76..ff737fcda 100644 --- a/lib/awful/mouse/init.lua +++ b/lib/awful/mouse/init.lua @@ -421,6 +421,22 @@ function mouse.remove_global_mousebinding(button) capi.root._remove_button(button) end +local default_buttons = {} + +--- Add an `awful.button` to the default client buttons. +-- +-- @staticfct awful.mouse.append_client_mousebinding +-- @tparam awful.button button The button. +-- @emits client_mousebinding::added +-- @emitstparam client_mousebinding::added awful.button button The button. +-- @see awful.button +-- @see awful.keyboard.append_client_keybinding + +function mouse.append_client_mousebinding(button) + table.insert(default_buttons, button) + capi.client.emit_signal("client_mousebinding::added", button) +end + for _, b in ipairs {"left", "right", "middle"} do mouse.object["is_".. b .."_mouse_button_pressed"] = function() return capi.mouse.coords().buttons[1] @@ -470,7 +486,6 @@ end) -- when button 1 is pressed. -- @staticfct mouse.coords - return mouse -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 From 80c65c51753c7acae491988f359db2530fc22b71 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 16 Oct 2019 02:27:00 -0400 Subject: [PATCH 08/20] awful.mouse: Add a `append_mousebindings` function. To preserve the symetry between the `button` and `key` API. --- lib/awful/keyboard.lua | 2 +- lib/awful/mouse/init.lua | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/awful/keyboard.lua b/lib/awful/keyboard.lua index 5c04df89f..491cf974e 100644 --- a/lib/awful/keyboard.lua +++ b/lib/awful/keyboard.lua @@ -148,6 +148,7 @@ local default_keys = {} -- @emits client_keybinding::added -- @emitstparam client_keybinding::added awful.key key The key. -- @see awful.key +-- @see awful.keyboard.append_client_keybindings function module.append_client_keybinding(key) table.insert(default_keys, key) @@ -167,7 +168,6 @@ end -- @emitstparam client_keybinding::added awful.key key The key. -- @see awful.key -- @see awful.keyboard.append_client_keybinding - function module.append_client_keybindings(keys) for _, key in ipairs(keys) do module.append_client_keybinding(key) diff --git a/lib/awful/mouse/init.lua b/lib/awful/mouse/init.lua index ff737fcda..2ddf0b37b 100644 --- a/lib/awful/mouse/init.lua +++ b/lib/awful/mouse/init.lua @@ -434,9 +434,29 @@ local default_buttons = {} function mouse.append_client_mousebinding(button) table.insert(default_buttons, button) + + for _, c in ipairs(capi.client.get(nil, false)) do + c:append_mousebinding(button) + end + capi.client.emit_signal("client_mousebinding::added", button) end +--- Add a `awful.button`s to the default client buttons. +-- +-- @staticfct awful.mouse.append_client_mousebindings +-- @tparam table buttons A table containing `awful.button` objects. +-- @see awful.button +-- @see awful.keyboard.append_client_keybinding +-- @see awful.mouse.append_client_mousebinding +-- @see awful.keyboard.append_client_keybindings + +function mouse.append_client_mousebindings(buttons) + for _, button in ipairs(buttons) do + mouse.append_client_mousebinding(button) + end +end + for _, b in ipairs {"left", "right", "middle"} do mouse.object["is_".. b .."_mouse_button_pressed"] = function() return capi.mouse.coords().buttons[1] From 7dfd32e4ba8a4d80cdae152eaf667eacd2b822b3 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 16 Oct 2019 02:33:15 -0400 Subject: [PATCH 09/20] awful.keyboard: Add a function to remove a key from the default set. --- lib/awful/keyboard.lua | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/awful/keyboard.lua b/lib/awful/keyboard.lua index 491cf974e..f415ca43d 100644 --- a/lib/awful/keyboard.lua +++ b/lib/awful/keyboard.lua @@ -173,4 +173,28 @@ function module.append_client_keybindings(keys) module.append_client_keybinding(key) end end + +--- Remove a key from the default client keys. +-- +-- @staticfct awful.keyboard.remove_client_keybinding +-- @tparam awful.key key The key. +-- @treturn boolean True if the key was removed and false if it wasn't found. +-- @see awful.keyboard.append_client_keybinding + +function module.remove_client_keybinding(key) + for k, v in ipairs(default_keys) do + if key == v then + table.remove(default_keys, k) + + for _, c in ipairs(capi.client.get(nil, false)) do + c:remove_keybinding(key) + end + + return true + end + end + + return false +end + return module From 8b6ea8243a2355faa47816dc04c4c36954d895c0 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 16 Oct 2019 02:47:16 -0400 Subject: [PATCH 10/20] awful.mouse: Add a function to remove a default client button. --- lib/awful/mouse/init.lua | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/awful/mouse/init.lua b/lib/awful/mouse/init.lua index 2ddf0b37b..67045f62f 100644 --- a/lib/awful/mouse/init.lua +++ b/lib/awful/mouse/init.lua @@ -457,6 +457,29 @@ function mouse.append_client_mousebindings(buttons) end end +--- Remove a mousebinding from the default client buttons. +-- +-- @staticfct awful.mouse.remove_client_mousebinding +-- @tparam awful.button button The button. +-- @treturn boolean True if the button was removed and false if it wasn't found. +-- @see awful.keyboard.append_client_keybinding + +function mouse.remove_client_mousebinding(button) + for k, v in ipairs(default_buttons) do + if button == v then + table.remove(default_buttons, k) + + for _, c in ipairs(capi.client.get(nil, false)) do + c:remove_mousebinding(button) + end + + return true + end + end + + return false +end + for _, b in ipairs {"left", "right", "middle"} do mouse.object["is_".. b .."_mouse_button_pressed"] = function() return capi.mouse.coords().buttons[1] From d6568993e230f47cc8eaf724274d278b4172569d Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 16 Oct 2019 02:49:05 -0400 Subject: [PATCH 11/20] awful.mouse: Add a "request::default_mousebindings" signal. `rc.lua` and the module must attach to this signal to add buttons to the default set. --- lib/awful/mouse/init.lua | 9 +++++++++ objects/client.c | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/lib/awful/mouse/init.lua b/lib/awful/mouse/init.lua index 67045f62f..0d4554a62 100644 --- a/lib/awful/mouse/init.lua +++ b/lib/awful/mouse/init.lua @@ -529,6 +529,15 @@ end) -- when button 1 is pressed. -- @staticfct mouse.coords +capi.client.connect_signal("scanning", function() + capi.client.emit_signal("request::default_mousebindings", "startup") +end) + +-- Private function to be used by `ruled.client`. +function mouse._get_client_mousebindings() + return default_buttons +end + return mouse -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/objects/client.c b/objects/client.c index e9b47a15c..734592ba3 100644 --- a/objects/client.c +++ b/objects/client.c @@ -244,6 +244,17 @@ * @signal request::urgent */ +/** Emitted during startup to gather the default client mousebindings. + * + * This signals gives a chance to all module to register new client keybindings. + * Assuming the client rules does not overwrite them with the `keys` property, + * they will be added to all clients. + * + * @signal request::default_mousebindings + * @tparam string context The reason why the signal was sent (currently always + * `startup`). +*/ + /** When a client gets tagged. * @signal tagged * @tag t The tag object. From 1f604a73c927f15242a2e866e364612367fb5393 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Mon, 18 Nov 2019 02:03:51 -0500 Subject: [PATCH 12/20] awful.key: Support multiple keys per `awful.key` objects. This allows to support the arrows, numpad or numrow using a single object. This will simplify some code, including `rc.lua`. --- lib/awful/hotkeys_popup/widget.lua | 8 ++- lib/awful/key.lua | 104 +++++++++++++++++++++++------ 2 files changed, 88 insertions(+), 24 deletions(-) diff --git a/lib/awful/hotkeys_popup/widget.lua b/lib/awful/hotkeys_popup/widget.lua index 6b4c74f09..b0ab3b76f 100644 --- a/lib/awful/hotkeys_popup/widget.lua +++ b/lib/awful/hotkeys_popup/widget.lua @@ -274,7 +274,9 @@ function widget.new(args) end table.sort( sorted_table, - function(a,b) return (a.mod or '')..a.key<(b.mod or '')..b.key end + function(a,b) + local k1, k2 = a.key or a.keys[1][1], b.key or b.keys[1][1] + return (a.mod or '')..k1<(b.mod or '')..k2 end ) target[group] = sorted_table end @@ -287,7 +289,9 @@ function widget.new(args) return end for _, data in pairs(awful.key.hotkeys) do - self:_add_hotkey(data.key, data, self._cached_awful_keys) + for _, key_pair in ipairs(data.keys) do + self:_add_hotkey(key_pair[1], data, self._cached_awful_keys) + end end self:_sort_hotkeys(self._cached_awful_keys) end diff --git a/lib/awful/key.lua b/lib/awful/key.lua index 6d8b2dcc9..864dcf494 100644 --- a/lib/awful/key.lua +++ b/lib/awful/key.lua @@ -24,6 +24,17 @@ local gobject = require("gears.object") -- @property key -- @param string +--- A group of keys. +-- +-- The valid keygroups are: +-- +-- * **numrow**: The row above the letters in the US PC-105/PC-104 keyboards +-- and its derivative. This is usually the number 1-9 followed by 0. +-- * **arrows**: The Left/Right/Top/Bottom keys usually located right of the +-- spacebar. +-- +-- @property keygroup + --- The table of modifier keys. -- -- A modifier, such as `Control` are a predetermined set of keys that can be @@ -222,7 +233,7 @@ end -- @treturn table A table with one or several key objects. -- @constructorfct awful.key -local function new_common(mod, _key, press, release, data) +local function new_common(mod, _keys, press, release, data) if type(release)=='table' then data=release release=nil @@ -230,38 +241,52 @@ local function new_common(mod, _key, press, release, data) local ret = {} local subsets = gmath.subsets(key.ignore_modifiers) - for _, set in ipairs(subsets) do - local sub_key = capi.key { - modifiers = gtable.join(mod, set), - key = _key - } + for _, key_pair in ipairs(_keys) do + for _, set in ipairs(subsets) do + local sub_key = capi.key { + modifiers = gtable.join(mod, set), + key = key_pair[1] + } - sub_key._private._legacy_convert_to = ret + sub_key._private._legacy_convert_to = ret - sub_key:connect_signal("press", function(_, ...) - if ret.on_press then - ret.on_press(...) - end - end) + sub_key:connect_signal("press", function(_, ...) + if ret.on_press then + if key_pair[2] ~= nil then + ret.on_press(key_pair[2], ...) + else + ret.on_press(...) + end + end + end) - sub_key:connect_signal("release", function(_, ...) - if ret.on_release then - ret.on_release(...) - end - end) + sub_key:connect_signal("release", function(_, ...) + if ret.on_release then + if key_pair[2] ~= nil then + ret.on_release(key_pair[2], ...) + else + ret.on_release(...) + end + end + end) - ret[#ret + 1] = sub_key + ret[#ret + 1] = sub_key + end end -- append custom userdata (like description) to a hotkey data = data and gtable.clone(data) or {} data.mod = mod - data.key = _key + data.keys = _keys data.on_press = press data.on_release = release data._is_capi_key = false + assert((not data.key) or type(data.key) == "string") table.insert(key.hotkeys, data) - data.execute = function(_) key.execute(mod, _key) end + data.execute = function(_) + assert(#_keys == 1, "key:execute() makes no sense for groups") + key.execute(mod, _keys[1]) + end -- Store the private data reverse_map[ret] = data @@ -273,19 +298,54 @@ local function new_common(mod, _key, press, release, data) return setmetatable(ret, obj_mt) end +local keygroups = { + numrow = {}, + arrows = { + {"Left" , "Left" }, + {"Right" , "Right" }, + {"Top" , "Top" }, + {"Bottom", "Bottom"}, + } +} + +-- Technically, this isn't very popular, but we have been doing this for 12 +-- years and nobody complained too loudly. +for i = 1, 10 do + table.insert(keygroups.numrow, {"#" .. i + 9, i == 10 and 0 or i}) +end + +-- Allow key objects to provide more than 1 key. +-- +-- Some "groups" like arrows, the numpad, F-keys or numrow "belong together" +local function get_keys(args) + if not args.keygroup then return {{args.key}} end + + -- Make sure nothing weird happens. + assert( + not args.key, + "Please provide either the `key` or `keygroup` property, not both" + ) + + assert(keygroups[args.keygroup], "Please provide a valid keygroup") + return keygroups[args.keygroup] +end + function key.new(args, _key, press, release, data) -- Assume this is the new constructor. if not _key then assert(not (press or release or data), "Calling awful.key() requires a key name") + + local keys = get_keys(args) + return new_common( args.modifiers, - args.key, + keys, args.on_press, args.on_release, args ) else - return new_common(args, _key, press, release, data) + return new_common(args, {{_key}}, press, release, data) end end From a53ff7418ecb780dd10998ebabe6541f0f20b6e6 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 1 Dec 2019 19:25:10 -0500 Subject: [PATCH 13/20] tests: Add more global awful.key in test-awesomerc. This forces the hotkey popup to have pages. --- tests/test-awesomerc.lua | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test-awesomerc.lua b/tests/test-awesomerc.lua index 1d7ba4f3c..07326e520 100644 --- a/tests/test-awesomerc.lua +++ b/tests/test-awesomerc.lua @@ -228,6 +228,25 @@ local steps = { return true end, + -- Add more keybindings for make sure the popup has many pages. + function() + for j=1, 10 do + for i=1, 200 do + awful.keyboard.append_global_keybinding( + awful.key { + key = "#"..(300+i), + modifiers = {}, + on_press = function() end, + description = "Fake "..i, + group = "Fake"..j, + } + ) + end + end + + return true + end, + -- Hotkeys popup should be displayed and hidden function(count) local s = awful.screen.focused() From 1921d545280c7dfbc99553a560afb42b822dbdc7 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Mon, 18 Nov 2019 02:04:48 -0500 Subject: [PATCH 14/20] rc.lua: Remove the numkey loop and use the awful.key keygroup. The result is the same. One less imperative construct in `rc.lua`. --- awesomerc.lua | 71 +++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/awesomerc.lua b/awesomerc.lua index 25b355c74..11d4cf821 100644 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -378,64 +378,63 @@ clientkeys = { } -- @DOC_NUMBER_KEYBINDINGS@ --- Bind all key numbers to tags. --- Be careful: we use keycodes to make it work on any keyboard layout. --- This should map on the top row of your keyboard, usually 1 to 9. -for i = 1, 9 do - -- View tag only. - awful.keyboard.append_global_keybinding(awful.key( - { modkey }, "#" .. i + 9, - function () + +awful.keyboard.append_global_keybindings({ + awful.key { + modifiers = { modkey }, + keygroup = "numrow", + description = "only view tag", + group = "tag", + on_press = function (index) local screen = awful.screen.focused() - local tag = screen.tags[i] + local tag = screen.tags[index] if tag then tag:view_only() end end, - {description = "view tag #"..i, group = "tag"} - )) - - -- Toggle tag display. - awful.keyboard.append_global_keybinding(awful.key( - { modkey, "Control" }, "#" .. i + 9, - function () + }, + awful.key { + modifiers = { modkey, "Control" }, + keygroup = "numrow", + description = "toggle tag", + group = "tag", + on_press = function (index) local screen = awful.screen.focused() - local tag = screen.tags[i] + local tag = screen.tags[index] if tag then awful.tag.viewtoggle(tag) end end, - {description = "toggle tag #" .. i, group = "tag"} - )) - - -- Move client to tag. - awful.keyboard.append_global_keybinding(awful.key( - { modkey, "Shift" }, "#" .. i + 9, - function () + }, + awful.key { + modifiers = { modkey, "Shift" }, + keygroup = "numrow", + description = "move focused client to tag", + group = "tag", + on_press = function (index) if client.focus then - local tag = client.focus.screen.tags[i] + local tag = client.focus.screen.tags[index] if tag then client.focus:move_to_tag(tag) end end end, - {description = "move focused client to tag #"..i, group = "tag"} - )) - - -- Toggle tag on focused client. - awful.keyboard.append_global_keybinding(awful.key( - { modkey, "Control", "Shift" }, "#" .. i + 9, - function () + }, + awful.key { + modifiers = { modkey, "Control", "Shift" }, + keygroup = "numrow", + description = "toggle focused client on tag", + group = "tag", + on_press = function (index) if client.focus then - local tag = client.focus.screen.tags[i] + local tag = client.focus.screen.tags[index] if tag then client.focus:toggle_tag(tag) end end end, - {description = "toggle focused client on tag #" .. i, group = "tag"} - )) -end + } +}) -- @DOC_CLIENT_BUTTONS@ clientbuttons = { From 44a665d381eb5c53f1ea11a58367b3bae4ff336d Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 16 Oct 2019 02:54:15 -0400 Subject: [PATCH 15/20] awful.rules: Use the `awful.client/mouse` default buttons and keys. --- lib/awful/keyboard.lua | 9 +++++++++ lib/awful/rules.lua | 9 +++++++++ objects/client.c | 11 +++++++++++ 3 files changed, 29 insertions(+) diff --git a/lib/awful/keyboard.lua b/lib/awful/keyboard.lua index f415ca43d..34792723c 100644 --- a/lib/awful/keyboard.lua +++ b/lib/awful/keyboard.lua @@ -197,4 +197,13 @@ function module.remove_client_keybinding(key) return false end +capi.client.connect_signal("scanning", function() + capi.client.emit_signal("request::default_keybindings", "context") +end) + +-- Private function to be used by `ruled.client`. +function module._get_client_keybindings() + return default_keys +end + return module diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index 4b1e86bbd..9f4a6c401 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -121,6 +121,8 @@ local protected_call = require("gears.protected_call") local aspawn = require("awful.spawn") local gdebug = require("gears.debug") local gmatcher = require("gears.matcher") +local amouse = require("awful.mouse") +local akeyboard = require("awful.keyboard") local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1) local rules = {} @@ -529,6 +531,13 @@ end -- @staticfct awful.rules.execute crules._execute = function(_, c, props, callbacks) + + -- Set the default buttons and keys + local btns = amouse._get_client_mousebindings() + local keys = akeyboard._get_client_keybindings() + props.keys = props.keys or keys + props.buttons = props.buttons or btns + -- This has to be done first, as it will impact geometry related props. if props.titlebars_enabled and (type(props.titlebars_enabled) ~= "function" or props.titlebars_enabled(c,props)) then diff --git a/objects/client.c b/objects/client.c index 734592ba3..150bc0bfa 100644 --- a/objects/client.c +++ b/objects/client.c @@ -255,6 +255,17 @@ * `startup`). */ +/** Emitted during startup to gather the default client keybindings. + * + * This signals gives a chance to all module to register new client keybindings. + * Assuming the client rules does not overwrite them with the `keys` property, + * they will be added to all clients. + * + * @signal request::default_keybindings + * @tparam string context The reason why the signal was sent (currently always + * `startup`). + */ + /** When a client gets tagged. * @signal tagged * @tag t The tag object. From 767a8e4f6d121e0fc7b9b8ac239bd24f6ced2a05 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 16 Oct 2019 02:55:35 -0400 Subject: [PATCH 16/20] rc.lua: Do not hardcode the default client buttons. This commit moves the previously hardcoded client button list to the `awful.client` default button set. This will allow modules to add their own buttons. In later commit, those default buttons will also be added dynamically to existing clients. --- awesomerc.lua | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/awesomerc.lua b/awesomerc.lua index 11d4cf821..9d0e042aa 100644 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -437,19 +437,21 @@ awful.keyboard.append_global_keybindings({ }) -- @DOC_CLIENT_BUTTONS@ -clientbuttons = { - awful.button({ }, 1, function (c) - c:emit_signal("request::activate", "mouse_click", {raise = true}) - end), - awful.button({ modkey }, 1, function (c) - c:emit_signal("request::activate", "mouse_click", {raise = true}) - awful.mouse.client.move(c) - end), - awful.button({ modkey }, 3, function (c) - c:emit_signal("request::activate", "mouse_click", {raise = true}) - awful.mouse.client.resize(c) - end), -} +client.connect_signal("request::default_mousebindings", function() + awful.mouse.append_client_mousebindings({ + awful.button({ }, 1, function (c) + c:emit_signal("request::activate", "mouse_click", {raise = true}) + end), + awful.button({ modkey }, 1, function (c) + c:emit_signal("request::activate", "mouse_click", {raise = true}) + awful.mouse.client.move(c) + end), + awful.button({ modkey }, 3, function (c) + c:emit_signal("request::activate", "mouse_click", {raise = true}) + awful.mouse.client.resize(c) + end), + }) +end) -- }}} @@ -465,7 +467,6 @@ awful.rules.rules = { focus = awful.client.focus.filter, raise = true, keys = clientkeys, - buttons = clientbuttons, screen = awful.screen.preferred, placement = awful.placement.no_overlap+awful.placement.no_offscreen } From ba1d5bf7126b7178f5c5d3f8b4840ede1ce6e755 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 16 Oct 2019 02:58:21 -0400 Subject: [PATCH 17/20] rc.lua: Stop hardcoding the client keys. Just like the previous commit for the client buttons, this allows modules to manipulate the keys independently of `rc.lua`. --- awesomerc.lua | 93 ++++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/awesomerc.lua b/awesomerc.lua index 9d0e042aa..e06910dd7 100644 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -332,51 +332,6 @@ awful.keyboard.append_global_keybindings({ {description = "select previous", group = "layout"}), }) --- @DOC_CLIENT_KEYBINDINGS@ -clientkeys = { - awful.key({ modkey, }, "f", - function (c) - c.fullscreen = not c.fullscreen - c:raise() - end, - {description = "toggle fullscreen", group = "client"}), - awful.key({ modkey, "Shift" }, "c", function (c) c:kill() end, - {description = "close", group = "client"}), - awful.key({ modkey, "Control" }, "space", awful.client.floating.toggle , - {description = "toggle floating", group = "client"}), - awful.key({ modkey, "Control" }, "Return", function (c) c:swap(awful.client.getmaster()) end, - {description = "move to master", group = "client"}), - awful.key({ modkey, }, "o", function (c) c:move_to_screen() end, - {description = "move to screen", group = "client"}), - awful.key({ modkey, }, "t", function (c) c.ontop = not c.ontop end, - {description = "toggle keep on top", group = "client"}), - awful.key({ modkey, }, "n", - function (c) - -- The client currently has the input focus, so it cannot be - -- minimized, since minimized clients can't have the focus. - c.minimized = true - end , - {description = "minimize", group = "client"}), - awful.key({ modkey, }, "m", - function (c) - c.maximized = not c.maximized - c:raise() - end , - {description = "(un)maximize", group = "client"}), - awful.key({ modkey, "Control" }, "m", - function (c) - c.maximized_vertical = not c.maximized_vertical - c:raise() - end , - {description = "(un)maximize vertically", group = "client"}), - awful.key({ modkey, "Shift" }, "m", - function (c) - c.maximized_horizontal = not c.maximized_horizontal - c:raise() - end , - {description = "(un)maximize horizontally", group = "client"}), -} - -- @DOC_NUMBER_KEYBINDINGS@ awful.keyboard.append_global_keybindings({ @@ -453,6 +408,53 @@ client.connect_signal("request::default_mousebindings", function() }) end) +-- @DOC_CLIENT_KEYBINDINGS@ +client.connect_signal("request::default_keybindings", function() + awful.keyboard.append_client_keybindings({ + awful.key({ modkey, }, "f", + function (c) + c.fullscreen = not c.fullscreen + c:raise() + end, + {description = "toggle fullscreen", group = "client"}), + awful.key({ modkey, "Shift" }, "c", function (c) c:kill() end, + {description = "close", group = "client"}), + awful.key({ modkey, "Control" }, "space", awful.client.floating.toggle , + {description = "toggle floating", group = "client"}), + awful.key({ modkey, "Control" }, "Return", function (c) c:swap(awful.client.getmaster()) end, + {description = "move to master", group = "client"}), + awful.key({ modkey, }, "o", function (c) c:move_to_screen() end, + {description = "move to screen", group = "client"}), + awful.key({ modkey, }, "t", function (c) c.ontop = not c.ontop end, + {description = "toggle keep on top", group = "client"}), + awful.key({ modkey, }, "n", + function (c) + -- The client currently has the input focus, so it cannot be + -- minimized, since minimized clients can't have the focus. + c.minimized = true + end , + {description = "minimize", group = "client"}), + awful.key({ modkey, }, "m", + function (c) + c.maximized = not c.maximized + c:raise() + end , + {description = "(un)maximize", group = "client"}), + awful.key({ modkey, "Control" }, "m", + function (c) + c.maximized_vertical = not c.maximized_vertical + c:raise() + end , + {description = "(un)maximize vertically", group = "client"}), + awful.key({ modkey, "Shift" }, "m", + function (c) + c.maximized_horizontal = not c.maximized_horizontal + c:raise() + end , + {description = "(un)maximize horizontally", group = "client"}), + }) +end) + -- }}} -- {{{ Rules @@ -466,7 +468,6 @@ awful.rules.rules = { border_color = beautiful.border_normal, focus = awful.client.focus.filter, raise = true, - keys = clientkeys, screen = awful.screen.preferred, placement = awful.placement.no_overlap+awful.placement.no_offscreen } From 5713a196020383020e31f140d7d90f4071ed9860 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 16 Oct 2019 03:04:37 -0400 Subject: [PATCH 18/20] rc.lua: Move the taglist and tasklist button to the request:: section. While this is a bit more memory intensive because it created the objects per screen, it is more self contained. If the objective is to have well defined sections that can be cut/pasted into new files, this change is a net win. --- awesomerc.lua | 78 ++++++++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 45 deletions(-) diff --git a/awesomerc.lua b/awesomerc.lua index e06910dd7..a40e977b5 100644 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -102,49 +102,6 @@ mykeyboardlayout = awful.widget.keyboardlayout() -- Create a textclock widget mytextclock = wibox.widget.textclock() --- Create a wibox for each screen and add it --- @TAGLIST_BUTTON@ -local taglist_buttons = { - awful.button({ }, 1, function(t) t:view_only() end), - awful.button({ modkey }, 1, function(t) - if client.focus then - client.focus:move_to_tag(t) - end - end), - awful.button({ }, 3, awful.tag.viewtoggle), - awful.button({ modkey }, 3, function(t) - if client.focus then - client.focus:toggle_tag(t) - end - end), - awful.button({ }, 4, function(t) awful.tag.viewnext(t.screen) end), - awful.button({ }, 5, function(t) awful.tag.viewprev(t.screen) end), -} - --- @TASKLIST_BUTTON@ -local tasklist_buttons = { - awful.button({ }, 1, function (c) - if c == client.focus then - c.minimized = true - else - c:emit_signal( - "request::activate", - "tasklist", - {raise = true} - ) - end - end), - awful.button({ }, 3, function() - awful.menu.client_list({ theme = { width = 250 } }) - end), - awful.button({ }, 4, function () - awful.client.focus.byidx(1) - end), - awful.button({ }, 5, function () - awful.client.focus.byidx(-1) - end), -} - -- @DOC_WALLPAPER@ screen.connect_signal("request::wallpaper", function(s) -- Wallpaper @@ -165,6 +122,7 @@ screen.connect_signal("request::desktop_decoration", function(s) -- Create a promptbox for each screen s.mypromptbox = awful.widget.prompt() + -- Create an imagebox widget which will contain an icon indicating which layout we're using. -- We need one layoutbox per screen. s.mylayoutbox = awful.widget.layoutbox { @@ -181,14 +139,44 @@ screen.connect_signal("request::desktop_decoration", function(s) s.mytaglist = awful.widget.taglist { screen = s, filter = awful.widget.taglist.filter.all, - buttons = taglist_buttons + buttons = { + awful.button({ }, 1, function(t) t:view_only() end), + awful.button({ modkey }, 1, function(t) + if client.focus then + client.focus:move_to_tag(t) + end + end), + awful.button({ }, 3, awful.tag.viewtoggle), + awful.button({ modkey }, 3, function(t) + if client.focus then + client.focus:toggle_tag(t) + end + end), + awful.button({ }, 4, function(t) awful.tag.viewnext(t.screen) end), + awful.button({ }, 5, function(t) awful.tag.viewprev(t.screen) end), + } } -- Create a tasklist widget s.mytasklist = awful.widget.tasklist { screen = s, filter = awful.widget.tasklist.filter.currenttags, - buttons = tasklist_buttons + buttons = { + awful.button({ }, 1, function (c) + if c == client.focus then + c.minimized = true + else + c:emit_signal( + "request::activate", + "tasklist", + {raise = true} + ) + end + end), + awful.button({ }, 3, function() awful.menu.client_list { theme = { width = 250 } } end), + awful.button({ }, 4, function() awful.client.focus.byidx( 1) end), + awful.button({ }, 5, function() awful.client.focus.byidx(-1) end), + } } -- @DOC_WIBAR@ From 38b552782537123476242cc41226bc721778be96 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 4 Dec 2019 01:37:07 -0500 Subject: [PATCH 19/20] tests: Test adding and removing key/mouse bindings on existing clients. --- tests/test-input-binding.lua | 123 +++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/tests/test-input-binding.lua b/tests/test-input-binding.lua index 75cde9360..25e2c0077 100644 --- a/tests/test-input-binding.lua +++ b/tests/test-input-binding.lua @@ -4,6 +4,8 @@ local runner = require( "_runner" ) local placement = require( "awful.placement" ) local gtable = require( "gears.table" ) local test_client = require( "_client" ) +local akeyboard = require( "awful.keyboard" ) +local amouse = require( "awful.mouse" ) local module = { key = require( "awful.key" ), @@ -249,6 +251,127 @@ for _, type_name in ipairs { "key", "button" } do end end +local exe1, exe2, exe3, exe4 = false, false, false, false +local new1, new2 = nil, nil + +-- Check that you can add new default key/mousebindings at any time. +table.insert(steps, function() + assert(#mouse.screen.clients == 0) + + new1 = module.key { + key = "a", + modifiers = {}, + on_press = function() exe1 = true end + } + + new2 = module.button { + button = 8, + modifiers = {}, + on_press = function() exe2 = true end + } + + akeyboard.append_client_keybinding(new1) + amouse.append_client_mousebinding(new2) + + -- Spawn a client. + test_client("myclient") + + return true +end) + +table.insert(steps, function() + if #mouse.screen.clients == 0 then return end + + client.focus = mouse.screen.clients[1] + + -- That should trigger the newly added keybinding. + root.fake_input("key_press" , "a") + root.fake_input("key_release", "a") + + -- Move the mouse over the client so mousebindings work. + placement.centered(mouse, {parent=client.focus}) + awesome.sync() + + -- Same thing for the mouse. + root.fake_input("button_press" , 8) + root.fake_input("button_release", 8) + + return true +end) + +table.insert(steps, function() + if (not exe1) or (not exe2) then return end + + akeyboard.remove_client_keybinding(new1) + amouse.remove_client_mousebinding(new2) + + exe1, exe2 = false, false + + return true +end) + +-- Removing is async, so wait until the next loop. +table.insert(steps, function() + root.fake_input("key_press" , "a") + root.fake_input("key_release", "a") + root.fake_input("button_press" , 8) + root.fake_input("button_release", 8) + awesome.sync() + + return true +end) + +-- Try adding key/mousebindings to existing clients. +table.insert(steps, function(count) + if count ~= 3 then return end + + assert(not exe1) + assert(not exe2) + + -- Append both types at the same time to make sure nothing overwrite + -- the list. + akeyboard.append_client_keybinding(new1) + amouse.append_client_mousebinding(new2) + + new1 = module.key { + key = "b", + modifiers = {}, + on_press = function() exe3 = true end + } + + new2 = module.button { + button = 7, + modifiers = {}, + on_press = function() exe4 = true end + } + + -- Append directly on the client. + client.focus:append_keybinding(new1) + client.focus:append_mousebinding(new2) + + return true +end) + +-- Removing is async, so wait until the next loop. +table.insert(steps, function() + + root.fake_input("key_press" , "b") + root.fake_input("key_release", "b") + root.fake_input("button_press" , 7) + root.fake_input("button_release", 7) + + return true +end) + +table.insert(steps, function() + if (not exe3) or (not exe4) then return end + + -- Note, there is no point to try remove_keybinding, it was already called + -- (indirectly) 4 steps ago. + + return true +end) + runner.run_steps(steps) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 From 288e4df49bbbaa392cc5ba6e5f83df4e3c276c32 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Thu, 5 Dec 2019 23:08:48 -0500 Subject: [PATCH 20/20] doc: Add a behavior change notification in the NEWS --- docs/89-NEWS.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/89-NEWS.md b/docs/89-NEWS.md index 920725192..73595bf79 100644 --- a/docs/89-NEWS.md +++ b/docs/89-NEWS.md @@ -57,6 +57,11 @@ This document was last updated at commit v4.3-197-g9085ed631. * `naughty.dbus` now uses Gio for talking to DBus. This is a first step in the deprecation of Awesome's own DBus bindings and could lead to behaviour changes on DBus. +* The client `keys` and `buttons` property now return `awful.key` + and `awful.buttons` objects rather than the lower level `key` and `button` + objects. If you used these low level APIs to add keys and buttons dynamically, + please migrate your code to the corresponding `:append_` and `:remove_` + client methods. # Awesome window manager framework version 4.3 changes