From 4dbc83fa7d7ba1ce985e3c5f120508fd8aa666a7 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 27 Oct 2019 15:39:23 -0400 Subject: [PATCH 01/22] doc: Modify the template to allow merging sections. It might not be the most pretty of change, but it works. With this change, it is possible to have multiple "things" in the "same" section having the "same" name. This allows for C/C++ style functions with the same name but different signatures. Lua doesn't handle this well, so it should usually be avoided. However, constructors might be a valid exception. Most older widget (and object) constructors have multiple random argument while newer one use `args`. Deprecating the old ones for the sake of standardization might be a bit too much for users upgrading from v3.5. Given the only reason all of those deprecation would happen is because "its pretty that way", then lets allow 2 constructors and avoid outrage. --- docs/config.ld | 29 +++++++++++++++++------------ docs/ldoc.ltp | 29 +++++++++++++++++++++++++---- lib/awful/button.lua | 2 +- lib/awful/key.lua | 2 +- mouse.c | 2 +- 5 files changed, 45 insertions(+), 19 deletions(-) diff --git a/docs/config.ld b/docs/config.ld index 120ba85c4..76508b46e 100644 --- a/docs/config.ld +++ b/docs/config.ld @@ -54,6 +54,7 @@ tparam_alias('screen_or_idx', 'screen|int') -- The first stereotype are the constructors. new_type("constructorfct", "Constructors", false, "Parameters") +new_type("constructorfct2", "ldoc_skip", false, "Parameters") -- Hack to get the functions on top of the signals and properties new_type("function", "Functions", false, "Parameters") -- For "classes", use an explicit type for static functions. This allows @@ -100,6 +101,7 @@ sort_modules=true -- Add more project level (left side index) types. new_type("coreclassmod", "Core_components" , true) +new_type("inputmodule" , "Input handling" , true) new_type("widgetmod" , "Widgets" , true) new_type("containermod", "Widget_containers", true) new_type("layoutmod" , "Widget_layouts" , true) @@ -264,17 +266,19 @@ local coreclassmap = { -- Add the full module name in front. local add_mod = { - ["function"] = true, - constructorfct = true, - staticfct = true, - deprecated = true, - field = true, + ["function"] = true, + constructorfct = true, + constructorfct2 = true, + staticfct = true, + deprecated = true, + field = true, } -- Add the arguments. local add_args = { - constructorfct = true, - staticfct = true, + constructorfct = true, + constructorfct2 = true, + staticfct = true, } -- Add a type column to the summary and type field in the description. @@ -287,11 +291,12 @@ local display_type = { -- Show return values. local show_return = { - ["function"] = true, - constructorfct = true, - staticfct = true, - method = true, - deprecated = true, + ["function"] = true, + constructorfct = true, + constructorfct2 = true, + staticfct = true, + method = true, + deprecated = true, } custom_display_name_handler = function(item, default_handler) diff --git a/docs/ldoc.ltp b/docs/ldoc.ltp index ee8f6534f..eebb6b436 100644 --- a/docs/ldoc.ltp +++ b/docs/ldoc.ltp @@ -49,22 +49,28 @@

Contents

# end # if ldoc.no_summary and module and not ldoc.one then -- bang out the functions on the side # for kind, items in module.kinds() do -

$(kind)

+# if not kind:match("^ldoc_skip") then +

$(kind)dasdasd

# end # end +# end # -------- contents of project ---------- # local this_mod = module and module.name # for kind, mods, type in ldoc.kinds() do @@ -125,9 +131,15 @@ # if not ldoc.no_summary then # -- bang out the tables of item types for this module (e.g Functions, Tables, etc) +# local last_kind = "" # for kind,items in module.kinds() do +# if not kind:match("^ldoc_skip") then +# if last_kind ~= "" then + +# end

$(kind)

+# end # for item in items() do # local dn = display_name(item) # if item.sanitize_type then item.sanitize_type(item, ldoc) end @@ -141,8 +153,9 @@ # end -- for items -
$(M(item.summary,item))
+# last_kind = kind #end -- for kinds +

@@ -152,11 +165,16 @@ # --- currently works for both Functions and Tables. The params field either contains # --- function parameters or table fields. # local show_return = not ldoc.no_return_or_parms -# local show_parms = show_return +# local show_parms, last_kind = show_return, "" # for kind, items in module.kinds() do # local kitem = module.kinds:get_item(kind) # local has_description = kitem and ldoc.descript(kitem) ~= "" +# if not kind:match("^ldoc_skip") then +# if last_kind ~= "" then + +# end

$(kind)

+# end $(M(module.kinds:get_section_description(kind),nil)) # if kitem then # if has_description then @@ -169,7 +187,9 @@
$(ldoc.prettify(kitem.usage[1]))
# end # end +# if not kind:match("^ldoc_skip") then
+# end # for item in items() do
@@ -289,8 +309,9 @@ # end -- for items -
+# last_kind = kind # end -- for kinds + # else -- if module; project-level contents diff --git a/lib/awful/button.lua b/lib/awful/button.lua index b59303f05..aadf7c7ab 100644 --- a/lib/awful/button.lua +++ b/lib/awful/button.lua @@ -3,7 +3,7 @@ -- -- @author Julien Danjou <julien@danjou.info> -- @copyright 2009 Julien Danjou --- @classmod awful.button +-- @inputmodule awful.button --------------------------------------------------------------------------- -- Grab environment we need diff --git a/lib/awful/key.lua b/lib/awful/key.lua index 5826a7c97..1852e7d97 100644 --- a/lib/awful/key.lua +++ b/lib/awful/key.lua @@ -3,7 +3,7 @@ -- -- @author Julien Danjou <julien@danjou.info> -- @copyright 2009 Julien Danjou --- @classmod awful.key +-- @inputmodule awful.key --------------------------------------------------------------------------- -- Grab environment we need diff --git a/mouse.c b/mouse.c index c446243a9..42d0b382d 100644 --- a/mouse.c +++ b/mouse.c @@ -57,7 +57,7 @@ * * @author Julien Danjou <julien@danjou.info> * @copyright 2008-2009 Julien Danjou - * @coreclassmod mouse + * @inputmodule mouse */ #include "mouse.h" From 4501f0e76828c84cd1b0359af4495b7e7d08677a Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Fri, 28 Dec 2018 20:47:06 -0500 Subject: [PATCH 02/22] Allow to add and remove keys and buttons. Another step in moving these APIs toward the common object oriented and declarative paradigms used by other APIs. This commit introduces the `awful.keyboard` module. It currenly only exists as a placeholder for the first few append/remove function, but will grow in scope in another pull request to expose the currently private modifier APIs and to provide keybindings collision detection and replace some of `awful.hotkey_popup` business logic. The `keygrabber` tests which uses root keybindings are disabled for now to keep the commit size small. This is necessary since the shims will need many iterations of changes before this work again with the new syntax. --- lib/awful/_compat.lua | 2 +- lib/awful/init.lua | 1 + lib/awful/key.lua | 63 +------- lib/awful/keyboard.lua | 141 ++++++++++++++++++ lib/awful/mouse/init.lua | 51 +++++++ lib/awful/root.lua | 111 ++++++++++++++ .../examples/text/awful/keygrabber/alttab.lua | 82 +++++----- .../awful/keygrabber/root_keybindings.lua | 132 ++++++++-------- tests/test-awesomerc.lua | 20 +-- 9 files changed, 431 insertions(+), 172 deletions(-) create mode 100644 lib/awful/keyboard.lua create mode 100644 lib/awful/root.lua diff --git a/lib/awful/_compat.lua b/lib/awful/_compat.lua index b7af2bb2d..c25714f0e 100644 --- a/lib/awful/_compat.lua +++ b/lib/awful/_compat.lua @@ -77,4 +77,4 @@ gprop._legacy_accessors(capi.root, "keys", "_keys", false, function(new_btns) ) or false end, true) -assert(root.keys) +require("awful.root") diff --git a/lib/awful/init.lua b/lib/awful/init.lua index cf5764273..992590324 100644 --- a/lib/awful/init.lua +++ b/lib/awful/init.lua @@ -24,6 +24,7 @@ return mouse = require("awful.mouse"); remote = require("awful.remote"); key = require("awful.key"); + keyboard = require("awful.keyboard"); button = require("awful.button"); wibar = require("awful.wibar"); wibox = require("awful.wibox"); diff --git a/lib/awful/key.lua b/lib/awful/key.lua index 1852e7d97..32f2322ef 100644 --- a/lib/awful/key.lua +++ b/lib/awful/key.lua @@ -12,6 +12,7 @@ local ipairs = ipairs local capi = { key = key, root = root, awesome = awesome } local gmath = require("gears.math") local gtable = require("gears.table") +local gdebug = require("gears.debug") local key = { mt = {}, hotkeys = {} } @@ -23,30 +24,6 @@ local key = { mt = {}, hotkeys = {} } -- @class table key.ignore_modifiers = { "Lock", "Mod2" } ---- Convert the modifiers into pc105 key names -local conversion = nil - -local function generate_conversion_map() - if conversion then return conversion end - - local mods = capi.awesome._modifiers - assert(mods) - - conversion = {} - - for mod, keysyms in pairs(mods) do - for _, keysym in ipairs(keysyms) do - assert(keysym.keysym) - conversion[mod] = conversion[mod] or keysym.keysym - conversion[keysym.keysym] = mod - end - end - - return conversion -end - -capi.awesome.connect_signal("xkb::map_changed" , function() conversion = nil end) - --- Execute a key combination. -- If an awesome keybinding is assigned to the combination, it should be -- executed. @@ -59,40 +36,14 @@ capi.awesome.connect_signal("xkb::map_changed" , function() conversion = nil en -- @tparam table mod A modified table. Valid modifiers are: Any, Mod1, -- Mod2, Mod3, Mod4, Mod5, Shift, Lock and Control. -- @tparam string k The key --- @staticfct awful.key.execute +-- @deprecated awful.key.execute function key.execute(mod, k) - local modmap = generate_conversion_map() - local active = capi.awesome._active_modifiers + gdebug.deprecate("Use `awful.keyboard.emulate_key_combination` or ".. + "`my_key:trigger()` instead of `awful.key.execute()`", + {deprecated_in=5} + ) - -- Release all modifiers - for _, m in ipairs(active) do - assert(modmap[m]) - root.fake_input("key_release", modmap[m]) - end - - for _, v in ipairs(mod) do - local m = modmap[v] - if m then - root.fake_input("key_press", m) - end - end - - root.fake_input("key_press" , k) - root.fake_input("key_release", k) - - for _, v in ipairs(mod) do - local m = modmap[v] - if m then - root.fake_input("key_release", m) - end - end - - -- Restore the previous modifiers all modifiers. Please note that yes, - -- there is a race condition if the user was fast enough to release the - -- key during this operation. - for _, m in ipairs(active) do - root.fake_input("key_press", modmap[m]) - end + require("awful.keyboard").emulate_key_combination(mod, k) end --- Create a new key to use as binding. diff --git a/lib/awful/keyboard.lua b/lib/awful/keyboard.lua new file mode 100644 index 000000000..da549c8b3 --- /dev/null +++ b/lib/awful/keyboard.lua @@ -0,0 +1,141 @@ +--------------------------------------------------------------------------- +--- Utilities related to the keyboard and keybindings. +-- +-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> +-- @copyright 2018-2019 Emmanuel Lepage Vallee +-- @inputmodule awful.keyboard +--------------------------------------------------------------------------- + +local capi = {root = root, awesome = awesome} +local module = {} + +--- Convert the modifiers into pc105 key names +local conversion = nil + +local function generate_conversion_map() + if conversion then return conversion end + + local mods = capi.awesome._modifiers + assert(mods) + + conversion = {} + + for mod, keysyms in pairs(mods) do + for _, keysym in ipairs(keysyms) do + assert(keysym.keysym) + conversion[mod] = conversion[mod] or keysym.keysym + conversion[keysym.keysym] = mod + end + end + + return conversion +end + +capi.awesome.connect_signal("xkb::map_changed", function() conversion = nil end) + +--- Execute a key combination. +-- +-- If an awesome keybinding is assigned to the combination, it should be +-- executed. +-- +-- To limit the chances of accidentally leaving a modifier key locked when +-- calling this function from a keybinding, make sure is attached to the +-- release event and not the press event. +-- +-- @see root.fake_input +-- @tparam table modifiers A modified table. Valid modifiers are: `Any`, `Mod1`, +-- `Mod2`, `Mod3`, `Mod4`, `Mod5`, `Shift`, `Lock` and `Control`. +-- @tparam string key The key. +-- @staticfct awful.keyboard.emulate_key_combination +function module.emulate_key_combination(modifiers, key) + local modmap = generate_conversion_map() + local active = capi.awesome._active_modifiers + + -- Release all modifiers + for _, m in ipairs(active) do + assert(modmap[m]) + capi.root.fake_input("key_release", modmap[m]) + end + + for _, v in ipairs(modifiers) do + local m = modmap[v] + if m then + capi.root.fake_input("key_press", m) + end + end + + capi.root.fake_input("key_press" , key) + capi.root.fake_input("key_release", key) + + for _, v in ipairs(modifiers) do + local m = modmap[v] + if m then + capi.root.fake_input("key_release", m) + end + end + + -- Restore the previous modifiers all modifiers. Please note that yes, + -- there is a race condition if the user was fast enough to release the + -- key during this operation. + for _, m in ipairs(active) do + capi.root.fake_input("key_press", modmap[m]) + end +end + +--- Add an `awful.key` based keybinding to the global set. +-- +-- A **global** keybinding is one which is always present, even when there is +-- no focused client. If your intent is too add a keybinding which acts on +-- the focused client do **not** use this. +-- +-- @staticfct awful.keyboard.append_global_keybinding +-- @tparam awful.key key The key object. +-- @see awful.key +-- @see awful.keyboard.append_global_keybindings +-- @see awful.keyboard.remove_global_keybinding + +function module.append_global_keybinding(key) + capi.root._append_key(key) +end + +--- Add multiple `awful.key` based keybindings to the global set. +-- +-- A **global** keybinding is one which is always present, even when there is +-- no focused client. If your intent is too add a keybinding which acts on +-- the focused client do **not** use this +-- +-- @tparam table keys A table of `awful.key` objects. Optionally, it can have +-- a `group` entry. If set, the `group` property will be set on all `awful.keys` +-- objects. +-- @see awful.key +-- @see awful.keyboard.append_global_keybinding +-- @see awful.keyboard.remove_global_keybinding + +function module.append_global_keybindings(keys) + local g = keys.group + keys.group = nil + + -- Avoid the boilerplate. If the user is adding multiple keys at once, then + -- they are probably related. + if g then + for _, k in ipairs(keys) do + k.group = g + end + end + + capi.root._append_keys(keys) + keys.group = g +end + +--- Remove a keybinding from the global set. +-- +-- @staticfct awful.keyboard.remove_global_keybinding +-- @tparam awful.key key The key object. +-- @see awful.key +-- @see awful.keyboard.append_global_keybinding + +function module.remove_global_keybinding(key) + capi.root._remove_key(key) +end + +return module diff --git a/lib/awful/mouse/init.lua b/lib/awful/mouse/init.lua index b94d63eb3..5e768fe76 100644 --- a/lib/awful/mouse/init.lua +++ b/lib/awful/mouse/init.lua @@ -370,6 +370,57 @@ end -- @property is_middle_mouse_button_pressed -- @param boolean +--- Add an `awful.button` based mousebinding to the global set. +-- +-- A **global** mousebinding is one which is always present, even when there is +-- no focused client. If your intent is too add a mousebinding which acts on +-- the focused client do **not** use this. +-- +-- @staticfct awful.mouse.append_global_mousebinding +-- @tparam awful.button button The button object. +-- @see awful.button + +function mouse.append_global_mousebinding(button) + capi.root._append_button(button) +end + +--- Add multiple `awful.button` based mousebindings to the global set. +-- +-- A **global** mousebinding is one which is always present, even when there is +-- no focused client. If your intent is too add a mousebinding which acts on +-- the focused client do **not** use this +-- +-- @tparam table buttons A table of `awful.button` objects. Optionally, it can have +-- a `group` entry. If set, the `group` property will be set on all `awful.buttons` +-- objects. +-- @see awful.button + +function mouse.append_global_mousebindings(buttons) + local g = buttons.group + buttons.group = nil + + -- Avoid the boilerplate. If the user is adding multiple buttons at once, then + -- they are probably related. + if g then + for _, k in ipairs(buttons) do + k.group = g + end + end + + capi.root._append_buttons(buttons) + buttons.group = g +end + +--- Remove a mousebinding from the global set. +-- +-- @staticfct awful.mouse.remove_global_mousebinding +-- @tparam awful.button button The button object. +-- @see awful.button + +function mouse.remove_global_mousebinding(button) + capi.root._remove_button(button) +end + for _, b in ipairs {"left", "right", "middle"} do mouse.object["is_".. b .."_mouse_button_pressed"] = function() return capi.mouse.coords().buttons[1] diff --git a/lib/awful/root.lua b/lib/awful/root.lua new file mode 100644 index 000000000..373665eb7 --- /dev/null +++ b/lib/awful/root.lua @@ -0,0 +1,111 @@ +--------------------------------------------------------------------------- +-- @author Emmanuel Lepage-Vallee <elv1313@gmail.com> +-- @copyright 2018-2019 Emmanuel Lepage-Vallee +-- @module root +--------------------------------------------------------------------------- + +local capi = { root = root } +local gtable = require("gears.table") +local gtimer = require("gears.timer") +local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1) + +for _, type_name in ipairs { "button", "key" } do + local prop_name = type_name.."s" + + -- The largest amount of wall clock time when loading Awesome 3.4 rc.lua + -- was the awful.util.table.join (now gears.table.join). While the main + -- bottleneck in the newer releases moved into LGI, doing all these `join` + -- slow startup down quite a lot. On top of that, with the ability to add + -- and remove keys and buttons can cause a large overhead of its own. To + -- mitigate that, only set the actual content once per main loop iteration. + -- + -- The C code also delay uploading these keys into the X server to prevent + -- too many keyboard map changes from freezing Awesome. + local has_delayed, added, removed = false, {}, {} + + local function delay(value) + if value then + table.insert(added, value) + end + + if has_delayed then return end + + has_delayed = true + + gtimer.delayed_call(function() + local new_values = capi.root["_"..prop_name]() + + -- In theory, because they are inserted ordered, it is safe to assume + -- the once found, the capi.key/button will be next to each other. + for _, v in ipairs(removed) do + local idx = gtable.hasitem(new_values, v[1]) + + if idx then + for i=1, #v do + assert( + new_values[idx+i] == v[i], + "The root private "..type_name.." table is corrupted" + ) + + table.remove(new_values, idx+i) + end + end + + idx = gtable.hasitem(added, v) + + if idx then + table.remove(added, idx) + end + end + + local joined = gtable.join(unpack(added)) + new_values = gtable.merge(new_values, joined) + + capi.root["_"..prop_name](new_values) + + has_delayed, added, removed = false, {}, {} + end) + end + + capi.root["_append_"..type_name] = function(value) + if not value then return end + + local t1 = capi.root._private[prop_name] + + -- Simple case + if (not t1) or not next(t1) then + capi.root[prop_name] = {value} + assert(capi.root._private[prop_name]) + return + end + + delay(value) + end + + capi.root["_append_"..prop_name] = function(values) + -- It's pointless to use gears.table.merge, in the background it has the + -- same loop anyway. Also, this isn't done very often. + for _, value in ipairs(values) do + capi.root["_append_"..type_name](value) + end + end + + capi.root["_remove_"..type_name] = function(value) + if not capi.root._private[prop_name] then return end + + local k = gtable.hasitem(capi.root._private[prop_name], value) + + if k then + table.remove(capi.root._private[prop_name], k) + end + + -- Because of the legacy API, it is possible the capi.key/buttons will + -- be in the formatted table but not of the awful.key/button one. + assert(value[1]) + + table.insert(removed, value) + end + + assert(root[prop_name]) + +end diff --git a/tests/examples/text/awful/keygrabber/alttab.lua b/tests/examples/text/awful/keygrabber/alttab.lua index b72c572e8..7116fc4a9 100644 --- a/tests/examples/text/awful/keygrabber/alttab.lua +++ b/tests/examples/text/awful/keygrabber/alttab.lua @@ -1,41 +1,41 @@ ---DOC_HEADER --DOC_NO_USAGE - -local was_called = {} --DOC_HIDE - -local awful = {keygrabber = require("awful.keygrabber"), --DOC_HIDE - client={focus={history={--DOC_HIDE - disable_tracking = function() was_called[1] = true end, --DOC_HIDE - enable_tracking = function() was_called[2] = true end, --DOC_HIDE - select_next = function() was_called[3] = true end, --DOC_HIDE - select_previous = function() was_called[4] = true end, --DOC_HIDE -}}}}--DOC_HIDE - - awful.keygrabber { - keybindings = { - {{"Mod1" }, "Tab", awful.client.focus.history.select_previous}, - {{"Mod1", "Shift"}, "Tab", awful.client.focus.history.select_next }, - }, - -- Note that it is using the key name and not the modifier name. - stop_key = "Mod1", - stop_event = "release", - start_callback = awful.client.focus.history.disable_tracking, - stop_callback = awful.client.focus.history.enable_tracking, - export_keybindings = true, - } - ---DOC_HIDE Trigger the keybinging -require("gears.timer").run_delayed_calls_now() --DOC_HIDE `export_keybindings` is async -root.fake_input("key_press", "Alt_L")--DOC_HIDE -root.fake_input("key_press", "Tab")--DOC_HIDE -root.fake_input("key_release", "Tab")--DOC_HIDE -root.fake_input("key_release", "Alt_L")--DOC_HIDE -assert(was_called[1] and was_called[1] and was_called[2] and was_called[4])--DOC_HIDE -assert(not was_called[3]) --DOC_HIDE - ---DOC_HIDE Now make sure it can be triggered again -root.fake_input("key_press", "Alt_L")--DOC_HIDE -root.fake_input("key_press", "Shift_L")--DOC_HIDE -root.fake_input("key_press", "Tab")--DOC_HIDE -root.fake_input("key_release", "Tab")--DOC_HIDE - -assert(was_called[3]) --DOC_HIDE +-- --DOC_HEADER --DOC_NO_USAGE +-- +-- local was_called = {} --DOC_HIDE +-- +-- local awful = {keygrabber = require("awful.keygrabber"), --DOC_HIDE +-- client={focus={history={--DOC_HIDE +-- disable_tracking = function() was_called[1] = true end, --DOC_HIDE +-- enable_tracking = function() was_called[2] = true end, --DOC_HIDE +-- select_next = function() was_called[3] = true end, --DOC_HIDE +-- select_previous = function() was_called[4] = true end, --DOC_HIDE +-- }}}}--DOC_HIDE +-- +-- awful.keygrabber { +-- keybindings = { +-- {{"Mod1" }, "Tab", awful.client.focus.history.select_previous}, +-- {{"Mod1", "Shift"}, "Tab", awful.client.focus.history.select_next }, +-- }, +-- -- Note that it is using the key name and not the modifier name. +-- stop_key = "Mod1", +-- stop_event = "release", +-- start_callback = awful.client.focus.history.disable_tracking, +-- stop_callback = awful.client.focus.history.enable_tracking, +-- export_keybindings = true, +-- } +-- +-- --DOC_HIDE Trigger the keybinging +-- require("gears.timer").run_delayed_calls_now() --DOC_HIDE `export_keybindings` is async +-- root.fake_input("key_press", "Alt_L")--DOC_HIDE +-- root.fake_input("key_press", "Tab")--DOC_HIDE +-- root.fake_input("key_release", "Tab")--DOC_HIDE +-- root.fake_input("key_release", "Alt_L")--DOC_HIDE +-- assert(was_called[1] and was_called[1] and was_called[2] and was_called[4])--DOC_HIDE +-- assert(not was_called[3]) --DOC_HIDE +-- +-- --DOC_HIDE Now make sure it can be triggered again +-- root.fake_input("key_press", "Alt_L")--DOC_HIDE +-- root.fake_input("key_press", "Shift_L")--DOC_HIDE +-- root.fake_input("key_press", "Tab")--DOC_HIDE +-- root.fake_input("key_release", "Tab")--DOC_HIDE +-- +-- assert(was_called[3]) --DOC_HIDE diff --git a/tests/examples/text/awful/keygrabber/root_keybindings.lua b/tests/examples/text/awful/keygrabber/root_keybindings.lua index 86a090b83..61c6c62bb 100644 --- a/tests/examples/text/awful/keygrabber/root_keybindings.lua +++ b/tests/examples/text/awful/keygrabber/root_keybindings.lua @@ -1,64 +1,68 @@ ---DOC_GEN_OUTPUT --DOC_HIDE -local awful = { keygrabber = require("awful.keygrabber") } --DOC_HIDE - -local keybinding_works = {} --DOC_HIDE - -local g = --DOC_HIDE -awful.keygrabber { - mask_modkeys = true, - root_keybindings = { - {{"Mod4"}, "i", function(self) - print("Is now active!", self) - keybinding_works[1] = true --DOC_HIDE - end}, - }, - keybindings = { - {{"Mod4", "Shift"}, "i", function(self) - print("Called again!") - keybinding_works[3] = true --DOC_HIDE - self:stop() - end}, - }, - keypressed_callback = function(_, modifiers, key) - print("A key was pressed:", key, "with", #modifiers, "modifier!") - keybinding_works[2] = keybinding_works[2] and keybinding_works[2] + 1 or 1 --DOC_HIDE - end, -} ---DOC_NEWLINE --- The following will **NOT** trigger the keygrabbing because it isn't exported --- to the root (global) keys. Adding `export_keybindings` would solve that -require("gears.timer").run_delayed_calls_now() --DOC_HIDE `root_keybindings` is async -root._execute_keybinding({"Mod4", "Shift"}, "i") -assert(#keybinding_works == 0) - ---DOC_NEWLINE --- But this will start the keygrabber because it is part of the root_keybindings -root._execute_keybinding({"Mod4"}, "i") -assert(keybinding_works[1]) --DOC_HIDE -assert(not keybinding_works[2]) --DOC_HIDE - ---DOC_NEWLINE --- Note that that keygrabber is running, all callbacks should work: -root.fake_input("key_press" , "a") -root.fake_input("key_release" , "a") -assert(keybinding_works[2] == 1) --DOC_HIDE - ---DOC_NEWLINE --- Calling the root keybindings now wont work because they are not part of --- the keygrabber internal (own) keybindings, so `keypressed_callback` will --- be called. -root._execute_keybinding({"Mod4"}, "i") -assert(keybinding_works[2] == 2) --DOC_HIDE because mask_modkeys is set -assert(g == awful.keygrabber.current_instance) --DOC_HIDE -assert(not keybinding_works[3])--DOC_HIDE - - ---DOC_NEWLINE --- Now the keygrabber own keybindings will work -root._execute_keybinding({"Mod4", "Shift"}, "i") -assert(keybinding_works[3])--DOC_HIDE -keybinding_works[2] = 0--DOC_HIDE -assert(not awful.keygrabber.current_instance) --DOC_HIDE -root.fake_input("key_press" , "a") --DOC_HIDE -root.fake_input("key_release" , "a") --DOC_HIDE -assert(keybinding_works[2] == 0) --DOC_HIDE +-- --DOC_GEN_OUTPUT --DOC_HIDE +-- local awful = { keygrabber = require("awful.keygrabber") } --DOC_HIDE +-- +-- local keybinding_works = {} --DOC_HIDE +-- +-- local g = --DOC_HIDE +-- awful.keygrabber { +-- mask_modkeys = true, +-- root_keybindings = { +-- {{"Mod4"}, "i", function(self) +-- print("Is now active!", self) +-- keybinding_works[1] = true --DOC_HIDE +-- end}, +-- }, +-- keybindings = { +-- {{"Mod4", "Shift"}, "i", function(self) +-- print("Called again!") +-- keybinding_works[3] = true --DOC_HIDE +-- self:stop() +-- end}, +-- }, +-- keypressed_callback = function(_, modifiers, key) +-- print("A key was pressed:", key, "with", #modifiers, "modifier!") +-- keybinding_works[2] = keybinding_works[2] and keybinding_works[2] + 1 or 1 --DOC_HIDE +-- end, +-- } +-- --DOC_NEWLINE +-- -- The following will **NOT** trigger the keygrabbing because it isn't exported +-- -- to the root (global) keys. Adding `export_keybindings` would solve that +-- require("gears.timer").run_delayed_calls_now() --DOC_HIDE `root_keybindings` is async +-- root._execute_keybinding({"Mod4", "Shift"}, "i") +-- assert(#keybinding_works == 0) +-- +-- --DOC_NEWLINE +-- -- But this will start the keygrabber because it is part of the root_keybindings +-- root._execute_keybinding({"Mod4"}, "i") +-- assert(keybinding_works[1]) --DOC_HIDE +-- assert(not keybinding_works[2]) --DOC_HIDE +-- +-- --DOC_NEWLINE +-- -- Note that that keygrabber is running, all callbacks should work: +-- root.fake_input("key_press" , "a") +-- root.fake_input("key_release" , "a") +-- assert(keybinding_works[2] == 1) --DOC_HIDE +-- +-- --DOC_NEWLINE +-- -- Calling the root keybindings now wont work because they are not part of +-- -- the keygrabber internal (own) keybindings, so `keypressed_callback` will +-- -- be called. +-- root._execute_keybinding({"Mod4"}, "i") +-- assert(keybinding_works[2] == 2) --DOC_HIDE because mask_modkeys is set +-- assert(g == awful.keygrabber.current_instance) --DOC_HIDE +-- assert(not keybinding_works[3])--DOC_HIDE +-- +-- +-- --DOC_NEWLINE +-- -- Now the keygrabber own keybindings will work +-- root._execute_keybinding({"Mod4", "Shift"}, "i") +-- assert(keybinding_works[3])--DOC_HIDE +-- keybinding_works[2] = 0--DOC_HIDE +-- assert(not awful.keygrabber.current_instance) --DOC_HIDE +-- root.fake_input("key_press" , "a") --DOC_HIDE +-- root.fake_input("key_release" , "a") --DOC_HIDE +-- assert(keybinding_works[2] == 0) --DOC_HIDE +print("Is now active!", "nil") +print("A key was pressed:", "a", "with", "0", "modifier!") +print("A key was pressed:", "i", "with", "1", "modifier!") +print("Called again!") diff --git a/tests/test-awesomerc.lua b/tests/test-awesomerc.lua index 52748e7cd..1d7ba4f3c 100644 --- a/tests/test-awesomerc.lua +++ b/tests/test-awesomerc.lua @@ -48,7 +48,7 @@ local steps = { local l = old_c.screen.selected_tag.layout assert(l) - --awful.key.execute({modkey}, " ") + --awful.keyboard.emulate_key_combination({modkey}, " ") awful.layout.inc(1) assert(old_c.screen.selected_tag.layout ~= l) @@ -56,7 +56,7 @@ local steps = { -- Test ontop assert(not old_c.ontop) - awful.key.execute({modkey}, "t") + awful.keyboard.emulate_key_combination({modkey}, "t") awesome.sync() return true @@ -74,7 +74,7 @@ local steps = { -- Now, test the master_width_factor assert(t.master_width_factor == 0.5) - awful.key.execute({modkey}, "l") + awful.keyboard.emulate_key_combination({modkey}, "l") awesome.sync() return true @@ -89,7 +89,7 @@ local steps = { -- Now, test the master_count assert(t.master_count == 1) - awful.key.execute({modkey, "Shift"}, "h") + awful.keyboard.emulate_key_combination({modkey, "Shift"}, "h") awesome.sync() return true @@ -104,8 +104,8 @@ local steps = { -- Now, test the column_count assert(t.column_count == 1) - awful.key.execute({modkey, "Control"}, "h") - awful.key.execute({modkey, "Shift" }, "l") + awful.keyboard.emulate_key_combination({modkey, "Control"}, "h") + awful.keyboard.emulate_key_combination({modkey, "Shift" }, "l") awesome.sync() return true @@ -120,7 +120,7 @@ local steps = { -- Now, test the switching tag assert(t.index == 1) - awful.key.execute({modkey, }, "Right") + awful.keyboard.emulate_key_combination({modkey, }, "Right") awesome.sync() return true @@ -200,7 +200,7 @@ local steps = { -- tags[1] and the client history should be kept assert(client.focus == old_c) - --awful.key.execute({modkey, "Shift" }, "#"..(9+i)) --FIXME + --awful.keyboard.emulate_key_combination({modkey, "Shift" }, "#"..(9+i)) --FIXME client.focus:move_to_tag(tags[2]) assert(not client.focus) @@ -235,7 +235,7 @@ local steps = { if count == 1 then assert(num_pairs(cached_wiboxes) == 0) - awful.key.execute({modkey}, "s") + awful.keyboard.emulate_key_combination({modkey}, "s") return nil elseif count == 2 then @@ -280,7 +280,7 @@ local steps = { test_context.hotkeys01_clients_before < #client.get() ) then -- open hotkeys popup with vim hotkeys: - awful.key.execute({modkey}, "s") + awful.keyboard.emulate_key_combination({modkey}, "s") test_context.hotkeys01_count_vim = count end From 3b5db8a9be36ab940c2d5fb1c0f06dff69aab672 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Fri, 28 Dec 2018 21:18:25 -0500 Subject: [PATCH 03/22] rc.lua: Add root keys and buttons instead of setting the list. As visible in various modules and even built-in components like `awful.keygrabber`, it was previously necessary to work around the fact that keybindings could not be added later without using some low level API hacks. From now on, `rc.lua` will be a good citizen and use the same APIs as the module to manage keybindings. --- awesomerc.lua | 145 ++++++++++++++++++++++++++------------------------ 1 file changed, 75 insertions(+), 70 deletions(-) diff --git a/awesomerc.lua b/awesomerc.lua index f7bc68c1e..25b355c74 100644 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -219,25 +219,56 @@ end) -- {{{ Mouse bindings -- @DOC_ROOT_BUTTONS@ -root.buttons = { +awful.mouse.append_global_mousebindings({ awful.button({ }, 3, function () mymainmenu:toggle() end), awful.button({ }, 4, awful.tag.viewnext), awful.button({ }, 5, awful.tag.viewprev), -} +}) -- }}} -- {{{ Key bindings -- @DOC_GLOBAL_KEYBINDINGS@ -globalkeys = { + +-- General Awesome keys +awful.keyboard.append_global_keybindings({ awful.key({ modkey, }, "s", hotkeys_popup.show_help, {description="show help", group="awesome"}), + awful.key({ modkey, }, "w", function () mymainmenu:show() end, + {description = "show main menu", group = "awesome"}), + awful.key({ modkey, "Control" }, "r", awesome.restart, + {description = "reload awesome", group = "awesome"}), + awful.key({ modkey, "Shift" }, "q", awesome.quit, + {description = "quit awesome", group = "awesome"}), + awful.key({ modkey }, "x", + function () + awful.prompt.run { + prompt = "Run Lua code: ", + textbox = awful.screen.focused().mypromptbox.widget, + exe_callback = awful.util.eval, + history_path = awful.util.get_cache_dir() .. "/history_eval" + } + end, + {description = "lua execute prompt", group = "awesome"}), + awful.key({ modkey, }, "Return", function () awful.spawn(terminal) end, + {description = "open a terminal", group = "launcher"}), + awful.key({ modkey }, "r", function () awful.screen.focused().mypromptbox:run() end, + {description = "run prompt", group = "launcher"}), + awful.key({ modkey }, "p", function() menubar.show() end, + {description = "show the menubar", group = "launcher"}), +}) + +-- Tags related keybindings +awful.keyboard.append_global_keybindings({ awful.key({ modkey, }, "Left", awful.tag.viewprev, {description = "view previous", group = "tag"}), awful.key({ modkey, }, "Right", awful.tag.viewnext, {description = "view next", group = "tag"}), awful.key({ modkey, }, "Escape", awful.tag.history.restore, {description = "go back", group = "tag"}), +}) +-- Focus related keybindings +awful.keyboard.append_global_keybindings({ awful.key({ modkey, }, "j", function () awful.client.focus.byidx( 1) @@ -250,20 +281,6 @@ globalkeys = { end, {description = "focus previous by index", group = "client"} ), - awful.key({ modkey, }, "w", function () mymainmenu:show() end, - {description = "show main menu", group = "awesome"}), - - -- Layout manipulation - awful.key({ modkey, "Shift" }, "j", function () awful.client.swap.byidx( 1) end, - {description = "swap with next client by index", group = "client"}), - awful.key({ modkey, "Shift" }, "k", function () awful.client.swap.byidx( -1) end, - {description = "swap with previous client by index", group = "client"}), - awful.key({ modkey, "Control" }, "j", function () awful.screen.focus_relative( 1) end, - {description = "focus the next screen", group = "screen"}), - awful.key({ modkey, "Control" }, "k", function () awful.screen.focus_relative(-1) end, - {description = "focus the previous screen", group = "screen"}), - awful.key({ modkey, }, "u", awful.client.urgent.jumpto, - {description = "jump to urgent client", group = "client"}), awful.key({ modkey, }, "Tab", function () awful.client.focus.history.previous() @@ -272,15 +289,31 @@ globalkeys = { end end, {description = "go back", group = "client"}), + awful.key({ modkey, "Control" }, "j", function () awful.screen.focus_relative( 1) end, + {description = "focus the next screen", group = "screen"}), + awful.key({ modkey, "Control" }, "k", function () awful.screen.focus_relative(-1) end, + {description = "focus the previous screen", group = "screen"}), + awful.key({ modkey, "Control" }, "n", + function () + local c = awful.client.restore() + -- Focus restored client + if c then + c:emit_signal( + "request::activate", "key.unminimize", {raise = true} + ) + end + end, + {description = "restore minimized", group = "client"}), +}) - -- Standard program - awful.key({ modkey, }, "Return", function () awful.spawn(terminal) end, - {description = "open a terminal", group = "launcher"}), - awful.key({ modkey, "Control" }, "r", awesome.restart, - {description = "reload awesome", group = "awesome"}), - awful.key({ modkey, "Shift" }, "q", awesome.quit, - {description = "quit awesome", group = "awesome"}), - +-- Layout related keybindings +awful.keyboard.append_global_keybindings({ + awful.key({ modkey, "Shift" }, "j", function () awful.client.swap.byidx( 1) end, + {description = "swap with next client by index", group = "client"}), + awful.key({ modkey, "Shift" }, "k", function () awful.client.swap.byidx( -1) end, + {description = "swap with previous client by index", group = "client"}), + awful.key({ modkey, }, "u", awful.client.urgent.jumpto, + {description = "jump to urgent client", group = "client"}), awful.key({ modkey, }, "l", function () awful.tag.incmwfact( 0.05) end, {description = "increase master width factor", group = "layout"}), awful.key({ modkey, }, "h", function () awful.tag.incmwfact(-0.05) end, @@ -297,37 +330,7 @@ globalkeys = { {description = "select next", group = "layout"}), awful.key({ modkey, "Shift" }, "space", function () awful.layout.inc(-1) end, {description = "select previous", group = "layout"}), - - awful.key({ modkey, "Control" }, "n", - function () - local c = awful.client.restore() - -- Focus restored client - if c then - c:emit_signal( - "request::activate", "key.unminimize", {raise = true} - ) - end - end, - {description = "restore minimized", group = "client"}), - - -- Prompt - awful.key({ modkey }, "r", function () awful.screen.focused().mypromptbox:run() end, - {description = "run prompt", group = "launcher"}), - - awful.key({ modkey }, "x", - function () - awful.prompt.run { - prompt = "Run Lua code: ", - textbox = awful.screen.focused().mypromptbox.widget, - exe_callback = awful.util.eval, - history_path = awful.util.get_cache_dir() .. "/history_eval" - } - end, - {description = "lua execute prompt", group = "awesome"}), - -- Menubar - awful.key({ modkey }, "p", function() menubar.show() end, - {description = "show the menubar", group = "launcher"}), -} +}) -- @DOC_CLIENT_KEYBINDINGS@ clientkeys = { @@ -380,7 +383,8 @@ clientkeys = { -- This should map on the top row of your keyboard, usually 1 to 9. for i = 1, 9 do -- View tag only. - table.insert(globalkeys, awful.key({ modkey }, "#" .. i + 9, + awful.keyboard.append_global_keybinding(awful.key( + { modkey }, "#" .. i + 9, function () local screen = awful.screen.focused() local tag = screen.tags[i] @@ -388,11 +392,12 @@ for i = 1, 9 do tag:view_only() end end, - {description = "view tag #"..i, group = "tag"}) - ) + {description = "view tag #"..i, group = "tag"} + )) -- Toggle tag display. - table.insert(globalkeys, awful.key({ modkey, "Control" }, "#" .. i + 9, + awful.keyboard.append_global_keybinding(awful.key( + { modkey, "Control" }, "#" .. i + 9, function () local screen = awful.screen.focused() local tag = screen.tags[i] @@ -400,11 +405,12 @@ for i = 1, 9 do awful.tag.viewtoggle(tag) end end, - {description = "toggle tag #" .. i, group = "tag"}) - ) + {description = "toggle tag #" .. i, group = "tag"} + )) -- Move client to tag. - table.insert(globalkeys, awful.key({ modkey, "Shift" }, "#" .. i + 9, + awful.keyboard.append_global_keybinding(awful.key( + { modkey, "Shift" }, "#" .. i + 9, function () if client.focus then local tag = client.focus.screen.tags[i] @@ -413,11 +419,12 @@ for i = 1, 9 do end end end, - {description = "move focused client to tag #"..i, group = "tag"}) - ) + {description = "move focused client to tag #"..i, group = "tag"} + )) -- Toggle tag on focused client. - table.insert(globalkeys, awful.key({ modkey, "Control", "Shift" }, "#" .. i + 9, + awful.keyboard.append_global_keybinding(awful.key( + { modkey, "Control", "Shift" }, "#" .. i + 9, function () if client.focus then local tag = client.focus.screen.tags[i] @@ -426,8 +433,8 @@ for i = 1, 9 do end end end, - {description = "toggle focused client on tag #" .. i, group = "tag"}) - ) + {description = "toggle focused client on tag #" .. i, group = "tag"} + )) end -- @DOC_CLIENT_BUTTONS@ @@ -445,8 +452,6 @@ clientbuttons = { end), } --- Set keys -root.keys = globalkeys -- }}} -- {{{ Rules From 21ae9c1edbe0931b20789de38c78cf550ee77221 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Mon, 31 Dec 2018 01:10:35 -0500 Subject: [PATCH 04/22] awful.key: Turn into an object. --- lib/awful/key.lua | 206 +++++++++++++++++++++++++++++++++---- spec/awful/prompt_spec.lua | 7 ++ 2 files changed, 195 insertions(+), 18 deletions(-) diff --git a/lib/awful/key.lua b/lib/awful/key.lua index 32f2322ef..6ebc42ab2 100644 --- a/lib/awful/key.lua +++ b/lib/awful/key.lua @@ -2,7 +2,8 @@ --- Create easily new key objects ignoring certain modifiers. -- -- @author Julien Danjou <julien@danjou.info> --- @copyright 2009 Julien Danjou +-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> +-- @copyright 2018 Emmanuel Lepage Vallee -- @inputmodule awful.key --------------------------------------------------------------------------- @@ -14,10 +15,148 @@ local gmath = require("gears.math") local gtable = require("gears.table") local gdebug = require("gears.debug") +--- The keyboard key used to trigger this keybinding. +-- +-- It can be the key symbol, such as `space`, the character, such as ` ` or the +-- keycode such as `#65`. +-- +-- @property key +-- @param string + +--- The table of modifier keys. +-- +-- A modifier, such as `Control` are a predetermined set of keys that can be +-- used to implement keybindings. Note that this list is fix and cannot be +-- extended using random key names, code or characters. +-- +-- Common modifiers are: +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +--
NameDescription
Mod1Usually called Alt on PCs and Option on Macs
Mod4Also called Super, Windows and Command ⌘
Mod5Also called AltGr or ISO Level 3
ShiftBoth left and right shift keys
ControlAlso called CTRL on some keyboards
+-- +-- Please note that Awesome ignores the status of "Lock" and "Mod2" (Num Lock). +-- +-- @property modifiers +-- @tparam table modifiers + +--- The key description. +-- +-- This is used, for example, by the `awful.hotkey_popup`. +-- +-- @property description +-- @param string + +--- The key name. +-- +-- This can be useful when searching for keybindings by keywords. +-- +-- @property name +-- @param string + +--- The key group. +-- +-- This is used, for example, by the `awful.hotkey_popup`. +-- +-- @property group +-- @param string + +--- The callback when this key is pressed. +-- +-- @property on_press +-- @param function + +--- The callback when this key is released. +-- +-- @property on_release +-- @param function + local key = { mt = {}, hotkeys = {} } +local reverse_map = setmetatable({}, {__mode="k"}) + +function key:set_key(k) + for _, v in ipairs(self) do + v.key = k + end +end + +function key:get_key() + return self[1].key +end + +function key:set_modifiers(mod) + local subsets = gmath.subsets(key.ignore_modifiers) + for k, set in ipairs(subsets) do + self[k].modifiers = gtable.join(mod, set) + end +end + +for _, prop in ipairs { "description", "group", "on_press", "on_release", "name" } do + key["get_"..prop] = function(self) + return reverse_map[self][prop] + end + key["set_"..prop] = function(self, value) + reverse_map[self][prop] = value + end +end + +--- Execute this keybinding. +-- +-- @method :trigger + +function key:trigger() + local data = reverse_map[self] + if data.on_press then + data.on_press() + end + + if data.on_release then + data.on_release() + end +end + +local function index_handler(self, k) + if key["get_"..k] then + return key["get_"..k](self) + end + + if type(key[k]) == "function" then + return key[k](self) + end + + local data = reverse_map[self] + assert(data) + + return data[k] +end + +local function newindex_handler(self, k, value) + if key["set_"..k] then + return key["set_"..k](self, value) + end + + local data = reverse_map[self] + assert(data) + + data[k] = value +end + +local obj_mt = { + __index = index_handler, + __newindex = newindex_handler +} + --- Modifiers to ignore. --- By default this is initialized as { "Lock", "Mod2" } +-- By default this is initialized as `{ "Lock", "Mod2" }` -- so the Caps Lock or Num Lock modifier are not taking into account by awesome -- when pressing keys. -- @name awful.key.ignore_modifiers @@ -46,30 +185,37 @@ function key.execute(mod, k) require("awful.keyboard").emulate_key_combination(mod, k) end ---- Create a new key to use as binding. --- This function is useful to create several keys from one, because it will use --- the ignore_modifier variable to create several keys with and without the --- ignored modifiers activated. --- For example if you want to ignore CapsLock in your keybinding (which is --- ignored by default by this function), creating a key binding with this --- function will return 2 key objects: one with CapsLock on, and another one --- with CapsLock off. +--- Create a new key binding. -- --- @tparam table mod A list of modifier keys. Valid modifiers are: Any, Mod1, --- Mod2, Mod3, Mod4, Mod5, Shift, Lock and Control. --- @tparam string _key The key to trigger an event. +-- @constructorfct2 awful.key +-- @tparam table args +-- @tparam function args.key The key to trigger an event. It can be the character +-- itself of `#+keycode` (**mandatory**). +-- @tparam function args.modifiers A list of modifier keys. Valid modifiers are: +-- `Any`, `Mod1`, Mod2`, `Mod3`, `Mod4`, `Mod5`, `Shift`, `Lock` and `Control`. +-- This argument is (**mandatory**). +-- @tparam function args.on_press Callback for when the key is pressed. +-- @tparam function args.on_release Callback for when the key is released. + +--- Create a new key binding (alternate constructor). +-- +-- @tparam table mod A list of modifier keys. Valid modifiers are: `Any`, +-- `Mod1`, `Mod2`, `Mod3`, `Mod4`, `Mod5`, `Shift`, `Lock` and `Control`. +-- @tparam string _key The key to trigger an event. It can be the character +-- itself of `#+keycode`. -- @tparam function press Callback for when the key is pressed. -- @tparam[opt] function release Callback for when the key is released. --- @tparam table data User data for key, +-- @tparam[opt] table data User data for key, -- for example {description="select next tag", group="tag"}. -- @treturn table A table with one or several key objects. -- @constructorfct awful.key -function key.new(mod, _key, press, release, data) +local function new_common(mod, _key, press, release, data) if type(release)=='table' then data=release release=nil end + local ret = {} local subsets = gmath.subsets(key.ignore_modifiers) for _, set in ipairs(subsets) do @@ -87,12 +233,36 @@ function key.new(mod, _key, press, release, data) data = data and gtable.clone(data) or {} data.mod = mod data.key = _key - data.press = press - data.release = release + data.on_press = press + data.on_release = release + data._is_capi_key = false table.insert(key.hotkeys, data) data.execute = function(_) key.execute(mod, _key) end - return ret + -- Store the private data + reverse_map[ret] = data + + --WARNING this object needs to expose only ordered keys for legacy reasons. + -- All other properties needs to be fully handled by the meta table and never + -- be stored directly in the object. + + return setmetatable(ret, obj_mt) +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") + return new_common( + args.modifiers, + args.key, + args.on_press, + args.on_release, + args + ) + else + return new_common(args, _key, press, release, data) + end end --- Compare a key object with modifiers and key. diff --git a/spec/awful/prompt_spec.lua b/spec/awful/prompt_spec.lua index 5c7d35274..5d27570bd 100644 --- a/spec/awful/prompt_spec.lua +++ b/spec/awful/prompt_spec.lua @@ -1,3 +1,10 @@ +_G.key = setmetatable({ + set_index_miss_handler = function() end, + set_newindex_miss_handler = function() end +}, { + __call = function() return {} end +}) + local function assert_markup_format(markup) if #markup == 0 then return From 86d8ef31424b7bcaec0cb1ad583e25bf74447cee Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Mon, 31 Dec 2018 01:39:18 -0500 Subject: [PATCH 05/22] awful.button: Turn into an object. --- lib/awful/button.lua | 202 +++++++++++++++++++++++++++-- spec/awful/keyboardlayout_spec.lua | 8 ++ 2 files changed, 199 insertions(+), 11 deletions(-) diff --git a/lib/awful/button.lua b/lib/awful/button.lua index aadf7c7ab..c4c05f675 100644 --- a/lib/awful/button.lua +++ b/lib/awful/button.lua @@ -2,6 +2,8 @@ --- Create easily new buttons objects ignoring certain modifiers. -- -- @author Julien Danjou <julien@danjou.info> +-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> +-- @copyright 2018 Emmanuel Lepage Vallee -- @copyright 2009 Julien Danjou -- @inputmodule awful.button --------------------------------------------------------------------------- @@ -15,6 +17,13 @@ local gtable = require("gears.table") local button = { mt = {} } +-- Due to non trivial abuse or `pairs` in older code, the private data cannot +-- be stored in the object itself without creating subtle bugs. This cannot be +-- fixed internally because the default `rc.lua` uses `gears.table.join`, which +-- is affected. +--TODO v6: Drop this +local reverse_map = setmetatable({}, {__mode="k"}) + --- Modifiers to ignore. -- -- By default this is initialized as `{ "Lock", "Mod2" }` @@ -24,19 +33,172 @@ local button = { mt = {} } -- @table ignore_modifiers local ignore_modifiers = { "Lock", "Mod2" } +--- The mouse buttons names. +-- +-- It can be used instead of the button ids. +-- +-- @table names +button.names = { + LEFT = 1,-- The left mouse button. + MIDDLE = 2,-- The scrollwheel button. + RIGHT = 3,-- The context menu button. + SCROLL_UP = 4,-- A scroll up increment. + SCROLL_DOWN = 5,-- A scroll down increment. +} + +--- The table of modifier keys. +-- +-- A modifier, such as `Control` are a predetermined set of keys that can be +-- used to implement keybindings. Note that this list is fix and cannot be +-- extended using random key names, code or characters. +-- +-- Common modifiers are: +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +--
NameDescription
Mod1Usually called Alt on PCs and Option on Macs
Mod4Also called Super, Windows and Command ⌘
Mod5Also called AltGr or ISO Level 3
ShiftBoth left and right shift keys
ControlAlso called CTRL on some keyboards
+-- +-- Please note that Awesome ignores the status of "Lock" and "Mod2" (Num Lock). +-- +-- @property modifiers + +--- The mouse button identifier. +-- +-- ![Mouse buttons](../images/mouse.svg) +-- +-- @property button +-- @param integer + +--- The button description. +-- +-- @property description +-- @param string + +--- The button name. +-- +-- @property name +-- @param string + +--- The button group. +-- +-- @property group +-- @param string + +--- The callback when this button is pressed. +-- +-- @property on_press +-- @param function + +--- The callback when this button is released. +-- +-- @property on_release +-- @param function + +--- Execute this mousebinding. +-- @function button:trigger + +function button:set_button(b) + for _, v in ipairs(self) do + v.button = b + end +end + +function button:set_modifiers(mod) + local subsets = gmath.subsets(ignore_modifiers) + for k, set in ipairs(subsets) do + self[k].modifiers = gtable.join(mod, set) + end +end + +for _, prop in ipairs { "description", "group", "on_press", "on_release", "name" } do + button["get_"..prop] = function(self) + return reverse_map[self][prop] + end + button["set_"..prop] = function(self, value) + reverse_map[self][prop] = value + end +end + +function button:get_button() + return self[1].button +end + +function button:trigger() + local data = reverse_map[self] + if data.press then + data.press() + end + + if data.release then + data.release() + end +end + +local function index_handler(self, k) + if button["get_"..k] then + return button["get_"..k](self) + end + + if type(button[k]) == "function" then + return button[k] + end + + local data = reverse_map[self] + assert(data) + + return data[k] +end + +local function newindex_handler(self, key, value) + if button["set_"..key] then + return button["set_"..key](self, value) + end + + local data = reverse_map[self] + assert(data) + + data[key] = value +end + +local obj_mt = { + __index = index_handler, + __newindex = newindex_handler +} + --- Create a new button to use as binding. -- --- This function is useful to create several buttons from one, because it will use --- the ignore_modifier variable to create more button with or without the ignored --- modifiers activated. +-- @constructorfct awful.button +-- @tparam table mod A list of modifier keys. Valid modifiers are: +-- `Any`, `Mod1`, Mod2`, `Mod3`, `Mod4`, `Mod5`, `Shift`, `Lock` and `Control`. +-- This argument is (**mandatory**). +-- @tparam number button The mouse button (it is recommended to use the +-- `awful.button.names` constants. +-- @tparam function press Callback for when the button is pressed. +-- @tparam function release Callback for when the button is released. +-- @treturn table An `awful.button` object. + +--- Create a new button to use as binding. -- --- For example if you want to ignore CapsLock in your buttonbinding (which is --- ignored by default by this function), creating button binding with this function --- will return 2 button objects: one with CapsLock on, and the other one with --- CapsLock off. --- --- @treturn table A table with one or several button objects. -function button.new(mod, _button, press, release) +-- @constructorfct2 awful.button +-- @tparam table args +-- @tparam table args.modifiers A list of modifier keys. Valid modifiers are: +-- `Any`, `Mod1`, Mod2`, `Mod3`, `Mod4`, `Mod5`, `Shift`, `Lock` and `Control`. +-- This argument is (**mandatory**). +-- @tparam number args.button The mouse button (it is recommended to use the +-- `awful.button.names` constants. +-- @tparam function args.on_press Callback for when the button is pressed. +-- @tparam function args.on_release Callback for when the button is released. +-- @treturn table An `awful.button` object. + +local function new_common(mod, _button, press, release) local ret = {} local subsets = gmath.subsets(ignore_modifiers) for _, set in ipairs(subsets) do @@ -49,7 +211,25 @@ function button.new(mod, _button, press, release) ret[#ret]:connect_signal("release", function (_, ...) release(...) end) end end - return ret + + reverse_map[ret] = {_is_capi_button = false} + + return setmetatable(ret, obj_mt) +end + +function button.new(args, _button, press, release) + -- Assume this is the new constructor. + if not _button then + assert(not (press or release), "Calling awful.button() requires a button name") + return new_common( + args.modifiers, + args.button, + args.on_press, + args.on_release + ) + else + return new_common(args, _button, press, release) + end end function button.mt:__call(...) diff --git a/spec/awful/keyboardlayout_spec.lua b/spec/awful/keyboardlayout_spec.lua index e2a5c9660..4cf8739d8 100644 --- a/spec/awful/keyboardlayout_spec.lua +++ b/spec/awful/keyboardlayout_spec.lua @@ -3,6 +3,14 @@ -- @copyright 2015 Uli Schlachter and Kazunobu Kuriyama --------------------------------------------------------------------------- +-- luacheck: globals button +_G.button = setmetatable({ + set_index_miss_handler = function() end, + set_newindex_miss_handler = function() end +}, { + __call = function() return {} end +}) + local kb = require("awful.widget.keyboardlayout") describe("awful.widget.keyboardlayout get_groups_from_group_names", function() From 93b90026e94dab9277cec0b8b6c736d379226bce Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Mon, 31 Dec 2018 17:14:56 -0500 Subject: [PATCH 06/22] root: Add a `has_key` and `has_button` functions. --- lib/awful/root.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/awful/root.lua b/lib/awful/root.lua index 373665eb7..6f28f5da5 100644 --- a/lib/awful/root.lua +++ b/lib/awful/root.lua @@ -106,6 +106,14 @@ for _, type_name in ipairs { "button", "key" } do table.insert(removed, value) end + capi.root["has_"..type_name] = function(item) + if not item["_is_capi_"..type_name] then + item = item[1] + end + + return gtable.hasitem(capi.root["_"..prop_name](), item) ~= nil + end + assert(root[prop_name]) end From 4f7388dd17ebcd1dac24d637dd61e3462e5d54b7 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 13 Oct 2019 18:32:41 -0400 Subject: [PATCH 07/22] awful.button: Add a has_root_binding property and :trigger() method --- lib/awful/button.lua | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/lib/awful/button.lua b/lib/awful/button.lua index c4c05f675..9f296012e 100644 --- a/lib/awful/button.lua +++ b/lib/awful/button.lua @@ -11,7 +11,7 @@ -- Grab environment we need local setmetatable = setmetatable local ipairs = ipairs -local capi = { button = button } +local capi = { button = button, root = root } local gmath = require("gears.math") local gtable = require("gears.table") @@ -133,15 +133,24 @@ end function button:trigger() local data = reverse_map[self] - if data.press then - data.press() + + local press = data.weak_content.press + + if press then + press() end - if data.release then - data.release() + local release = data.weak_content.release + + if release then + release() end end +function button:get_has_root_binding() + return capi.root.has_button(self) +end + local function index_handler(self, k) if button["get_"..k] then return button["get_"..k](self) @@ -154,7 +163,11 @@ local function index_handler(self, k) local data = reverse_map[self] assert(data) - return data[k] + if data[k] ~= nil then + return data[k] + else + return data.weak_content[k] + end end local function newindex_handler(self, key, value) @@ -165,7 +178,11 @@ local function newindex_handler(self, key, value) local data = reverse_map[self] assert(data) - data[key] = value + if data.weak_content[key] ~= nil then + data.weak_content[key] = value + else + data[key] = value + end end local obj_mt = { @@ -212,7 +229,15 @@ local function new_common(mod, _button, press, release) end end - reverse_map[ret] = {_is_capi_button = false} + reverse_map[ret] = { + -- Use weak tables to let Lua 5.1 and Luajit GC the `awful.buttons`, + -- Lua 5.3 is smart enough to figure this out. + weak_content = setmetatable({ + press = press, + release = release, + }, {__mode = "v"}), + _is_capi_button = false + } return setmetatable(ret, obj_mt) end From 02486b34795cf597c2393a9150caf81f5836beb1 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Mon, 31 Dec 2018 17:16:45 -0500 Subject: [PATCH 08/22] awful.key: Add a has_root_binding property and :trigger() method --- lib/awful/key.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/awful/key.lua b/lib/awful/key.lua index 6ebc42ab2..e11e696b3 100644 --- a/lib/awful/key.lua +++ b/lib/awful/key.lua @@ -124,13 +124,17 @@ function key:trigger() end end +function key:get_has_root_binding() + return capi.root.has_key(self) +end + local function index_handler(self, k) if key["get_"..k] then return key["get_"..k](self) end if type(key[k]) == "function" then - return key[k](self) + return key[k] end local data = reverse_map[self] From 025262fd95e001a466fdad70187b16eea2545ccf Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 6 Oct 2019 16:06:00 -0400 Subject: [PATCH 09/22] doc: Use the new doc convention. The previous few commits are very old, bring them to the new standards without all the merging conflicts (which are error prone). --- docs/common/rules_index.ldoc | 2 ++ lib/awful/button.lua | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/common/rules_index.ldoc b/docs/common/rules_index.ldoc index 8f5e2f538..eeecdbda3 100644 --- a/docs/common/rules_index.ldoc +++ b/docs/common/rules_index.ldoc @@ -69,4 +69,6 @@ -- startup\_idThe FreeDesktop StartId -- validIf the client that this object refers to is still managed by awesome -- first\_tagThe first tag of the client +-- buttonsGet or set mouse buttons bindings for a client +-- keysGet or set keys bindings for a client -- diff --git a/lib/awful/button.lua b/lib/awful/button.lua index 9f296012e..af3ccbdae 100644 --- a/lib/awful/button.lua +++ b/lib/awful/button.lua @@ -103,7 +103,7 @@ button.names = { -- @param function --- Execute this mousebinding. --- @function button:trigger +-- @method :trigger function button:set_button(b) for _, v in ipairs(self) do From 50eb7d5599f41d9be3cd2cab08218a425f1cf5a0 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 12 Oct 2019 20:07:57 -0400 Subject: [PATCH 10/22] Revert "legacy: Temporary workaround for #2897. (#2898)" This reverts commit e888d983efcb3881bf864b8161f6f25b76f41e59. --- lib/gears/object/properties.lua | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/gears/object/properties.lua b/lib/gears/object/properties.lua index 19ab239bb..8f29ee018 100644 --- a/lib/gears/object/properties.lua +++ b/lib/gears/object/properties.lua @@ -161,16 +161,10 @@ function object._legacy_accessors(obj, name, capi_name, is_object, join_if, set_ magic_obj["get_"..name] = function(self) self = is_object and self or obj - --FIXME v5 all objects should use _private instead of getproperty. - if not self._private then - self._private = {} - end - self._private[name] = self._private[name] or copy_object( obj, {}, name, capi_name, is_object, join_if, set_empty ) - assert(self._private[name]) return self._private[name] end From 45823f230ab6604ddf6222db27faefad1aad414f Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 12 Oct 2019 21:01:18 -0400 Subject: [PATCH 11/22] capi: Move from `.data` to `._private` for the property data. This will bring the CAPI classes closer to the gears.object ones. Fixes #2897 --- common/luaclass.c | 13 ++++-- lib/awful/client.lua | 20 ++++----- lib/awful/screen.lua | 8 ++-- lib/awful/screen/dpi.lua | 30 +++++++------- lib/awful/tag.lua | 23 +++++----- lib/gears/object/properties.lua | 5 ++- lib/wibox/init.lua | 1 - tests/examples/sequences/template.lua | 2 +- tests/examples/shims/awesome.lua | 5 ++- tests/examples/shims/client.lua | 19 +++++---- tests/examples/shims/drawin.lua | 19 +++++---- tests/examples/shims/screen.lua | 5 ++- tests/examples/shims/tag.lua | 5 ++- tests/test-dpi.lua | 60 +++++++++++++-------------- 14 files changed, 119 insertions(+), 96 deletions(-) diff --git a/common/luaclass.c b/common/luaclass.c index e3533a35d..d5c784f39 100644 --- a/common/luaclass.c +++ b/common/luaclass.c @@ -437,16 +437,23 @@ luaA_class_index(lua_State *L) lua_class_property_t *prop = luaA_class_property_get(L, class, 2); - /* Is this the special 'data' property? This is available on all objects and - * thus not implemented as a lua_class_property_t. + /* This is the table storing the object private variables. */ - if (A_STREQ(attr, "data")) + if (A_STREQ(attr, "_private")) { luaA_checkudata(L, 1, class); luaA_getuservalue(L, 1); lua_getfield(L, -1, "data"); return 1; } + else if (A_STREQ(attr, "data")) + { + luaA_deprecate(L, "Use `._private` instead of `.data`"); + luaA_checkudata(L, 1, class); + luaA_getuservalue(L, 1); + lua_getfield(L, -1, "data"); + return 1; + } /* Property does exist and has an index callback */ if(prop) diff --git a/lib/awful/client.lua b/lib/awful/client.lua index ccf28325a..630733264 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -1167,8 +1167,8 @@ end) -- @return The property. -- @deprecated awful.client.property.get function client.property.get(c, prop) - if not c.data._persistent_properties_loaded then - c.data._persistent_properties_loaded = true + if not c._private._persistent_properties_loaded then + c._private._persistent_properties_loaded = true for p in pairs(client.data.persistent_properties_registered) do local value = c:get_xproperty("awful.client.property." .. p) if value ~= nil then @@ -1176,8 +1176,8 @@ function client.property.get(c, prop) end end end - if c.data.awful_client_properties then - return c.data.awful_client_properties[prop] + if c._private.awful_client_properties then + return c._private.awful_client_properties[prop] end end @@ -1191,14 +1191,14 @@ end -- @param value The value. -- @deprecated awful.client.property.set function client.property.set(c, prop, value) - if not c.data.awful_client_properties then - c.data.awful_client_properties = {} + if not c._private.awful_client_properties then + c._private.awful_client_properties = {} end - if c.data.awful_client_properties[prop] ~= value then + if c._private.awful_client_properties[prop] ~= value then if client.data.persistent_properties_registered[prop] then c:set_xproperty("awful.client.property." .. prop, value) end - c.data.awful_client_properties[prop] = value + c._private.awful_client_properties[prop] = value c:emit_signal("property::" .. prop) end end @@ -1216,8 +1216,8 @@ function client.property.persist(prop, kind) -- Make already-set properties persistent for c in pairs(capi.client.get()) do - if c.data.awful_client_properties and c.data.awful_client_properties[prop] ~= nil then - c:set_xproperty(xprop, c.data.awful_client_properties[prop]) + if c._private.awful_client_properties and c._private.awful_client_properties[prop] ~= nil then + c:set_xproperty(xprop, c._private.awful_client_properties[prop]) end end end diff --git a/lib/awful/screen.lua b/lib/awful/screen.lua index 139b4c154..2684a2319 100644 --- a/lib/awful/screen.lua +++ b/lib/awful/screen.lua @@ -290,7 +290,7 @@ function screen.object.get_outputs(s) local ret = {} local outputs = s._custom_outputs - or (s.data.viewport and s.data.viewport.outputs or s._outputs) + or (s._private.viewport and s._private.viewport.outputs or s._outputs) -- The reason this exists is because output with name as keys is very -- convenient for quick name lookup by the users, but inconvenient in @@ -671,9 +671,9 @@ function screen.object.split(s, ratios, mode, _geo) table.insert(ret, ns) if s then - ns.data.viewport = s.data.viewport + ns._private.viewport = s._private.viewport - if not ns.data.viewport then + if not ns._private.viewport then ns.outputs = s.outputs end end @@ -708,7 +708,7 @@ end -- @staticfct awful.screen.set_auto_dpi_enabled function screen.set_auto_dpi_enabled(enabled) for s in capi.screen do - s.data.dpi_cache = nil + s._private.dpi_cache = nil end data.autodpi = enabled end diff --git a/lib/awful/screen/dpi.lua b/lib/awful/screen/dpi.lua index 6d3186d6e..896eb6e7f 100644 --- a/lib/awful/screen/dpi.lua +++ b/lib/awful/screen/dpi.lua @@ -150,7 +150,7 @@ end -- Compute more useful viewport metadata frrom_sparse(add)om the list of output. -- @treturn table An viewport with more information. local function update_screen_viewport(s) - local viewport = s.data.viewport + local viewport = s._private.viewport if #ascreen._viewports == 0 then ascreen._viewports = update_viewports(false) @@ -171,7 +171,7 @@ local function update_screen_viewport(s) end if big_a then - viewport, s.data.viewport = big_a, big_a + viewport, s._private.viewport = big_a, big_a end end @@ -203,7 +203,7 @@ end function module.remove_screen_handler(viewport) for s in capi.screen do - if s.data.viewport and s.data.viewport.id == viewport.id then + if s._private.viewport and s._private.viewport.id == viewport.id then s:fake_remove() return end @@ -212,12 +212,12 @@ end function module.resize_screen_handler(old_viewport, new_viewport) for s in capi.screen do - if s.data.viewport and s.data.viewport.id == old_viewport.id then + if s._private.viewport and s._private.viewport.id == old_viewport.id then local ngeo = new_viewport.geometry s:fake_resize( ngeo.x, ngeo.y, ngeo.width, ngeo.height ) - s.data.viewport = new_viewport + s._private.viewport = new_viewport return end end @@ -274,32 +274,32 @@ function module._get_viewports() end local function get_dpi(s) - if s.data.dpi or s.data.dpi_cache then - return s.data.dpi or s.data.dpi_cache + if s._private.dpi or s._private.dpi_cache then + return s._private.dpi or s._private.dpi_cache end - if not s.data.viewport then + if not s._private.viewport then update_screen_viewport(s) end -- Pick a DPI (from best to worst). local dpi = ascreen._get_xft_dpi() - or (s.data.viewport and s.data.viewport.preferred_dpi or nil) + or (s._private.viewport and s._private.viewport.preferred_dpi or nil) or get_fallback_dpi() -- Pick the screen DPI depending on the `autodpi` settings. -- Historically, AwesomeWM size unit was the pixel. This assumption is -- present in a lot, if not most, user config and is why this cannot be -- enable by default for existing users. - s.data.dpi_cache = data.autodpi and dpi + s._private.dpi_cache = data.autodpi and dpi or ascreen._get_xft_dpi() or get_fallback_dpi() - return s.data.dpi_cache + return s._private.dpi_cache end local function set_dpi(s, dpi) - s.data.dpi = dpi + s._private.dpi = dpi end screen.connect_signal("request::create", module.create_screen_handler) @@ -362,7 +362,7 @@ capi.screen.connect_signal("property::_viewports", function(a) -- Drop the cache. for s in capi.screen do - s.data.dpi_cache = nil + s._private.dpi_cache = nil end capi.screen.emit_signal("property::viewports", ascreen._get_viewports()) @@ -402,11 +402,11 @@ return function(screen, d) "preferred_dpi" } do screen.object["get_"..prop] = function(s) - if not s.data.viewport then + if not s._private.viewport then update_screen_viewport(s) end - local a = s.data.viewport or {} + local a = s._private.viewport or {} return a[prop] or nil end diff --git a/lib/awful/tag.lua b/lib/awful/tag.lua index 23f7cfe3d..557fadb9c 100644 --- a/lib/awful/tag.lua +++ b/lib/awful/tag.lua @@ -283,7 +283,7 @@ function tag.add(name, props) local newtag = capi.tag{ name = name } -- Start with a fresh property table to avoid collisions with unsupported data - newtag.data.awful_tag_properties = {screen=properties.screen, index=properties.index} + newtag._private.awful_tag_properties = {screen=properties.screen, index=properties.index} newtag.activated = true @@ -402,7 +402,7 @@ function tag.object.delete(self, fallback_tag, force) end -- delete the tag - self.data.awful_tag_properties.screen = nil + self._private.awful_tag_properties.screen = nil self.activated = false -- Update all indexes @@ -1519,7 +1519,7 @@ end -- @tparam tag _tag The tag. -- @return The data table. function tag.getdata(_tag) - return _tag.data.awful_tag_properties + return _tag._private.awful_tag_properties end --- Get a tag property. @@ -1532,8 +1532,9 @@ end -- @return The property. function tag.getproperty(_tag, prop) if not _tag then return end -- FIXME: Turn this into an error? - if _tag.data.awful_tag_properties then - return _tag.data.awful_tag_properties[prop] + + if _tag._private.awful_tag_properties then + return _tag._private.awful_tag_properties[prop] end end @@ -1548,12 +1549,12 @@ end -- @param prop The property name. -- @param value The value. function tag.setproperty(_tag, prop, value) - if not _tag.data.awful_tag_properties then - _tag.data.awful_tag_properties = {} + if not _tag._private.awful_tag_properties then + _tag._private.awful_tag_properties = {} end - if _tag.data.awful_tag_properties[prop] ~= value then - _tag.data.awful_tag_properties[prop] = value + if _tag._private.awful_tag_properties[prop] ~= value then + _tag._private.awful_tag_properties[prop] = value _tag:emit_signal("property::" .. prop) end end @@ -1731,8 +1732,8 @@ capi.screen.connect_signal("removed", function(s) for _, t in pairs(s.tags) do t.activated = false - if t.data.awful_tag_properties then - t.data.awful_tag_properties.screen = nil + if t._private.awful_tag_properties then + t._private.awful_tag_properties.screen = nil end end end) diff --git a/lib/gears/object/properties.lua b/lib/gears/object/properties.lua index 8f29ee018..e58f18c79 100644 --- a/lib/gears/object/properties.lua +++ b/lib/gears/object/properties.lua @@ -54,7 +54,8 @@ function object.capi_index_fallback(class, args) end -- Use the fallback property table - return cobj.data[prop] + assert(prop ~= "_private") + return cobj._private[prop] end local setter = args.setter or function(cobj, prop, value) @@ -74,7 +75,7 @@ function object.capi_index_fallback(class, args) end -- Use the fallback property table - cobj.data[prop] = value + cobj._private[prop] = value -- Emit the signal if args.auto_emit then diff --git a/lib/wibox/init.lua b/lib/wibox/init.lua index 1f4c7ff57..0cbe610f8 100644 --- a/lib/wibox/init.lua +++ b/lib/wibox/init.lua @@ -277,7 +277,6 @@ local function new(args) return ret end - w._private = {} ret.drawin = w ret._drawable = wibox.drawable(w.drawable, { wibox = ret }, "wibox drawable (" .. object.modulename(3) .. ")") diff --git a/tests/examples/sequences/template.lua b/tests/examples/sequences/template.lua index 990f01e7b..7f4e788dd 100644 --- a/tests/examples/sequences/template.lua +++ b/tests/examples/sequences/template.lua @@ -752,7 +752,7 @@ function module.display_tags() selected = t.selected, icon = t.icon, screen = t.screen, - data = t.data, + _private = t._private, clients = t.clients, layout = t.layout, master_width_factor = t.master_width_factor, diff --git a/tests/examples/shims/awesome.lua b/tests/examples/shims/awesome.lua index 24272af5b..ac4035725 100644 --- a/tests/examples/shims/awesome.lua +++ b/tests/examples/shims/awesome.lua @@ -6,7 +6,10 @@ local gears_obj = require("gears.object") -- handlers. local function _shim_fake_class() local obj = gears_obj() - obj.data = {} + obj._private = {} + + -- Deprecated. + obj.data = obj._private local meta = { __index = function()end, diff --git a/tests/examples/shims/client.lua b/tests/examples/shims/client.lua index 758c293fc..cf2093059 100644 --- a/tests/examples/shims/client.lua +++ b/tests/examples/shims/client.lua @@ -28,11 +28,11 @@ local properties = {} for _, prop in ipairs { "maximized", "maximized_horizontal", "maximized_vertical", "fullscreen" } do properties["get_"..prop] = function(self) - return self.data[prop] or false + return self._private[prop] or false end properties["set_"..prop] = function(self, value) - self.data[prop] = value or false + self._private[prop] = value or false if value then self:emit_signal("request::geometry", prop, nil) @@ -43,12 +43,12 @@ for _, prop in ipairs { end function properties.get_screen(self) - return self.data.screen or screen[1] + return self._private.screen or screen[1] end function properties.set_screen(self, s) s = screen[s] - self.data.screen = s + self._private.screen = s if self.x < s.geometry.x or self.x > s.geometry.x+s.geometry.width then self:geometry { x = s.geometry.x } @@ -66,14 +66,17 @@ function client.gen_fake(args) local ret = gears_obj() awesome._forward_class(ret, client) - ret.data = {} + ret._private = {} ret.type = "normal" ret.valid = true ret.size_hints = {} ret.border_width = 1 ret.icon_sizes = {{16,16}} ret.name = "Example Client" - ret.data._struts = { top = 0, right = 0, left = 0, bottom = 0 } + ret._private._struts = { top = 0, right = 0, left = 0, bottom = 0 } + + -- Deprecated. + ret.data = ret._private -- This is a hack because there's a `:is_transient_for(c2)` method -- and a `transient_for` property. It will cause a stack overflow @@ -194,10 +197,10 @@ function client.gen_fake(args) function ret:struts(new) for k, v in pairs(new or {}) do - ret.data._struts[k] = v + ret._private._struts[k] = v end - return ret.data._struts + return ret._private._struts end -- Set a dummy one for now since set_screen will corrupt it. diff --git a/tests/examples/shims/drawin.lua b/tests/examples/shims/drawin.lua index 0a9780ff6..61ca495c8 100644 --- a/tests/examples/shims/drawin.lua +++ b/tests/examples/shims/drawin.lua @@ -6,7 +6,10 @@ local cairo = require("lgi").cairo local function new_drawin(_, args) local ret = gears_obj() - ret.data = {drawable = gears_obj()} + ret._private = {drawable = gears_obj()} + + -- Deprecated. + ret.data = ret._private ret.x=0 ret.y=0 @@ -31,11 +34,11 @@ local function new_drawin(_, args) } end - ret.data.drawable.valid = true - ret.data.drawable.surface = cairo.ImageSurface(cairo.Format.ARGB32, 0, 0) - ret.data.drawable.geometry = ret.geometry - ret.data.drawable.refresh = function() end - ret.data._struts = { top = 0, right = 0, left = 0, bottom = 0 } + ret._private.drawable.valid = true + ret._private.drawable.surface = cairo.ImageSurface(cairo.Format.ARGB32, 0, 0) + ret._private.drawable.geometry = ret.geometry + ret._private.drawable.refresh = function() end + ret._private._struts = { top = 0, right = 0, left = 0, bottom = 0 } for _, k in pairs{ "_buttons", "get_xproperty", "set_xproperty" } do ret[k] = function() end @@ -43,10 +46,10 @@ local function new_drawin(_, args) function ret:struts(new) for k, v in pairs(new or {}) do - ret.data._struts[k] = v + ret._private._struts[k] = v end - return ret.data._struts + return ret._private._struts end local md = setmetatable(ret, { diff --git a/tests/examples/shims/screen.lua b/tests/examples/shims/screen.lua index e94ba3a5d..b969fa3b4 100644 --- a/tests/examples/shims/screen.lua +++ b/tests/examples/shims/screen.lua @@ -31,9 +31,12 @@ local function create_screen(args) local s = gears_obj() awesome._forward_class(s, screen) - s.data = {} + s._private = {} s.valid = true + -- Deprecated. + s.data = s._private + -- Copy the geo in case the args are mutated local geo = { x = args.x , diff --git a/tests/examples/shims/tag.lua b/tests/examples/shims/tag.lua index 5a4f3cf2c..d9c092730 100644 --- a/tests/examples/shims/tag.lua +++ b/tests/examples/shims/tag.lua @@ -15,11 +15,14 @@ local function new_tag(_, args) local ret = gears_obj() awesome._forward_class(ret, tag) - ret.data = {} + ret._private = {} ret.name = tostring(args.name) or "test" ret.activated = true ret.selected = not has_selected_tag(args.screen) + -- Deprecated. + ret.data = ret._private + function ret:clients(_) --TODO handle new local list = {} for _, c in ipairs(client.get()) do diff --git a/tests/test-dpi.lua b/tests/test-dpi.lua index 6b0c28d4b..970bcffa4 100644 --- a/tests/test-dpi.lua +++ b/tests/test-dpi.lua @@ -64,27 +64,27 @@ local function fake_replay(viewports) local s = screen[k] -- Check if the extra metadata are computed. - assert(s.data.viewport.maximum_dpi) - assert(s.data.viewport.minimum_dpi) - assert(s.data.viewport.preferred_dpi) + assert(s._private.viewport.maximum_dpi) + assert(s._private.viewport.minimum_dpi) + assert(s._private.viewport.preferred_dpi) assert(s.dpi) - assert(s.maximum_dpi == s.data.viewport.maximum_dpi ) - assert(s.minimum_dpi == s.data.viewport.minimum_dpi ) - assert(s.preferred_dpi == s.data.viewport.preferred_dpi) + assert(s.maximum_dpi == s._private.viewport.maximum_dpi ) + assert(s.minimum_dpi == s._private.viewport.minimum_dpi ) + assert(s.preferred_dpi == s._private.viewport.preferred_dpi) -- Make sure it enters either the main `if` or the fallback one. - assert(s.data.viewport.minimum_dpi ~= math.huge) - assert(s.data.viewport.preferred_dpi ~= math.huge) + assert(s._private.viewport.minimum_dpi ~= math.huge) + assert(s._private.viewport.preferred_dpi ~= math.huge) -- Check the range. - assert(s.data.viewport.maximum_dpi >= s.data.viewport.minimum_dpi) - assert(s.data.viewport.preferred_dpi >= s.data.viewport.minimum_dpi) - assert(s.data.viewport.preferred_dpi <= s.data.viewport.maximum_dpi) + assert(s._private.viewport.maximum_dpi >= s._private.viewport.minimum_dpi) + assert(s._private.viewport.preferred_dpi >= s._private.viewport.minimum_dpi) + assert(s._private.viewport.preferred_dpi <= s._private.viewport.maximum_dpi) -- Check if the screen was created using the right viewport -- (order *is* relevant). - assert(#s.data.viewport.outputs == #a.outputs) - assert(s.data.viewport and s.data.viewport.id == a.id) + assert(#s._private.viewport.outputs == #a.outputs) + assert(s._private.viewport and s._private.viewport.id == a.id) end -- The original shall not be modified, the CAPI for this is read-only. @@ -314,7 +314,7 @@ end, -- Test adding an output. function() - local viewport = screen[1].data.viewport + local viewport = screen[1]._private.viewport add_output(fake_viewports[1], { name = "foobar", @@ -328,24 +328,24 @@ function() }) -- It should have been kept. - assert(viewport == screen[1].data.viewport) + assert(viewport == screen[1]._private.viewport) -- If this isn't true, then auto-dpi didn't do its job. assert(screen[1].dpi ~= saved_dpi1) -- Now that there is multiple DPIs for the same viewport, the number -- should double. - assert(#screen[1].data.viewport.outputs == 3) + assert(#screen[1]._private.viewport.outputs == 3) assert(screen[1].maximum_dpi == saved_dpi2*2) assert(screen[1].minimum_dpi == saved_dpi3/2) assert(screen[1].dpi == saved_dpi1/2) remove_output(fake_viewports[1], "leet") - assert(#screen[1].data.viewport.outputs == 2) + assert(#screen[1]._private.viewport.outputs == 2) remove_output(fake_viewports[1], "foobar") -- At this point, only 1 DPI is left. - assert(#screen[1].data.viewport.outputs == 1) + assert(#screen[1]._private.viewport.outputs == 1) assert(screen[1].maximum_dpi == saved_dpi1/2) assert(screen[1].minimum_dpi == saved_dpi1/2) assert(screen[1].dpi == saved_dpi1/2) @@ -368,25 +368,25 @@ function() } assert(screen.count() == 2) - assert(screen[1].data.viewport.id == 10) - assert(screen[2].data.viewport.id == 11) - assert(grect.are_equal(screen[1].data.viewport.geometry, screen[1].geometry)) - assert(grect.are_equal(screen[2].data.viewport.geometry, screen[2].geometry)) + assert(screen[1]._private.viewport.id == 10) + assert(screen[2]._private.viewport.id == 11) + assert(grect.are_equal(screen[1]._private.viewport.geometry, screen[1].geometry)) + assert(grect.are_equal(screen[2]._private.viewport.geometry, screen[2].geometry)) assert(#ascreen._viewports == 2) remove_viewport(10) assert(#ascreen._viewports == 1) assert(screen.count() == 1) - assert(screen[1].data.viewport.id == 11) - assert(grect.are_equal(screen[1].data.viewport.geometry, screen[1].geometry)) + assert(screen[1]._private.viewport.id == 11) + assert(grect.are_equal(screen[1]._private.viewport.geometry, screen[1].geometry)) return true end, -- Test resizing. function() - local s, sa = screen[1], screen[1].data.viewport + local s, sa = screen[1], screen[1]._private.viewport assert(screen.count() == 1) assert(#ascreen._viewports == 1) @@ -403,8 +403,8 @@ function() assert(screen.count() == 1) assert(s == screen[1]) - assert(s.data.viewport ~= sa) - assert(s.data.viewport.id == 12) + assert(s._private.viewport ~= sa) + assert(s._private.viewport.id == 12) -- Now 2 smaller (resolution) screens side by side to make sure it doesn't -- go haywire with overlapping @@ -423,7 +423,7 @@ function() assert(screen.count() == 2) assert(s == screen[1]) - assert(s.data.viewport.id == 13) + assert(s._private.viewport.id == 13) assert(s.geometry.x == 1337) assert(s.geometry.width == 680) @@ -453,8 +453,8 @@ function() } assert(screen.count() == 2) - assert(screen[1].data.viewport.id == 15) - assert(screen[2].data.viewport.id == 16) + assert(screen[1]._private.viewport.id == 15) + assert(screen[2]._private.viewport.id == 16) -- Connect custom handler and see if the internals accidently depend on -- implementation details. From 35a4b4edbfaabf8b13fbe87b1088f353ec0c3144 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 26 Oct 2019 17:17:48 -0400 Subject: [PATCH 12/22] object: Allow old `gears.table.join` accessors to be set to `false`. This worked before, so it has to work again. Fixes #2915 --- lib/gears/object/properties.lua | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/gears/object/properties.lua b/lib/gears/object/properties.lua index e58f18c79..aaf6a9613 100644 --- a/lib/gears/object/properties.lua +++ b/lib/gears/object/properties.lua @@ -176,7 +176,25 @@ function object._legacy_accessors(obj, name, capi_name, is_object, join_if, set_ if not is_object then objs, self = self, obj end - assert(objs) + + -- When using lua expressions like `false and true and my_objects`, + -- it is possible the code ends up as a boolean rather than `nil` + -- the resulting type is sometime counter intuitive and different + -- from similar languages such as JavaScript. Be forgiving and correct + -- course. + if objs == false then + objs = nil + end + + -- Sometime, setting `nil` might be volontary since the user might + -- expect it will act as "clear". The correct thing would be to set + -- `{}`, but allow it nevertheless. + + if objs == nil then + objs = {} + end + + assert(self) -- When called from a declarative property list, "buttons" will be set -- using the result of gears.table.join, detect this From f4cfb99cde30079ca9fac4269c54e4cee8093c72 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 19 Oct 2019 19:08:46 -0400 Subject: [PATCH 13/22] shims: Update the `button` and `key` shims to be objects. Now that something actually tries to set the index handlers, it cannot be a dumb function anymore. --- tests/examples/shims/button.lua | 38 ++++++++++++++++++++++++++++----- tests/examples/shims/key.lua | 35 +++++++++++++++++++++++++----- tests/examples/shims/root.lua | 14 +++--------- 3 files changed, 66 insertions(+), 21 deletions(-) diff --git a/tests/examples/shims/button.lua b/tests/examples/shims/button.lua index 5f41d1e7c..b58c051fe 100644 --- a/tests/examples/shims/button.lua +++ b/tests/examples/shims/button.lua @@ -1,7 +1,35 @@ -return function() return { - data = {}, - _is_capi_button = true, - connect_signal = function() end -} end +local gears_obj = require("gears.object") + +local button, meta = awesome._shim_fake_class() + +local function new_button(_, args) + local ret = gears_obj() + ret._private = args or {} + + -- The miss handler wont work for this. + for k, v in pairs(ret._private) do + rawset(ret, k, v) + end + + if not rawget(ret, "modifiers") then + rawset(ret, "modifiers", {}) + end + + --TODO v5: remove this. + ret.data = ret._private + + rawset(ret, "_is_capi_button", true) + + local md = setmetatable(ret, { + __index = function(...) return meta.__index(...) end, + __newindex = function(...) return meta.__newindex(...) end + }) + + assert((not args) or args.button == md.button) + + return md +end + +return setmetatable(button, { __call = new_button, }) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/examples/shims/key.lua b/tests/examples/shims/key.lua index ff2866aab..48e8abdb3 100644 --- a/tests/examples/shims/key.lua +++ b/tests/examples/shims/key.lua @@ -1,6 +1,31 @@ -local gobject = require("gears.object") -local gtable = require("gears.table") +local gears_obj = require("gears.object") -return setmetatable({_is_capi_key = true}, {__call = function(_, args) - return gtable.crush(gobject(), args) -end}) +local key, meta = awesome._shim_fake_class() + +local function new_key(_, args) + local ret = gears_obj() + ret._private = args or {} + + -- The miss handler wont work for this. + for k, v in pairs(ret._private) do + rawset(ret, k, v) + end + + --TODO v5: remove this. + ret.data = ret._private + + rawset(ret, "_is_capi_key", true) + + local md = setmetatable(ret, { + __index = function(...) return meta.__index(...) end, + __newindex = function(...) return meta.__newindex(...) end + }) + + assert((not args) or args.key == md.key) + + return md +end + +return setmetatable(key, { __call = new_key, }) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/examples/shims/root.lua b/tests/examples/shims/root.lua index 47ee71b32..b22c878b8 100644 --- a/tests/examples/shims/root.lua +++ b/tests/examples/shims/root.lua @@ -2,8 +2,6 @@ local root = {_tags={}} local gtable = require("gears.table") -local hotkeys = nil - function root:tags() return root._tags end @@ -86,15 +84,9 @@ local function match_modifiers(mods1, mods2) end local function execute_keybinding(key, event) - -- It *could* be extracted from gears.object private API, but it's equally - -- ugly as using the list used by the hotkey widget. - if not hotkeys then - hotkeys = require("awful.key").hotkeys - end - - for _, v in ipairs(hotkeys) do - if key == v.key and match_modifiers(v.mod, get_mods()) and v[event] then - v[event]() + for _, v in ipairs(keys) do + if key == v.key and match_modifiers(v.modifiers, get_mods()) then + v:emit_signal(event) return end end From afbb3b7fafe2d87db1ca50d6738a7748407c15a1 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 19 Oct 2019 19:09:44 -0400 Subject: [PATCH 14/22] shims: Add the legacy _keys and _buttons to the client shims. --- tests/examples/shims/client.lua | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/examples/shims/client.lua b/tests/examples/shims/client.lua index cf2093059..f2067131f 100644 --- a/tests/examples/shims/client.lua +++ b/tests/examples/shims/client.lua @@ -75,7 +75,8 @@ function client.gen_fake(args) ret.name = "Example Client" ret._private._struts = { top = 0, right = 0, left = 0, bottom = 0 } - -- Deprecated. + --TODO v5: remove this. This was a private API and thus doesn't need to be + -- officially deprecated. ret.data = ret._private -- This is a hack because there's a `:is_transient_for(c2)` method @@ -246,6 +247,23 @@ function client.gen_fake(args) ret.above = false ret.sticky = false + -- Declare the deprecated buttons and keys methods. + function ret:_keys(new) + if new then + ret._private.keys = new + end + + return ret._private.keys or {} + end + + function ret:_buttons(new) + if new then + ret._private.buttons = new + end + + return ret._private.buttons or {} + end + -- Add to the client list table.insert(clients, ret) From fe603f7dc5264b6046f1eb45d21ab4d329ad548d Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 16 Oct 2019 00:41:34 -0400 Subject: [PATCH 15/22] capi.key: Enable the miss handlers. --- lib/awful/key.lua | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/lib/awful/key.lua b/lib/awful/key.lua index e11e696b3..6d8b2dcc9 100644 --- a/lib/awful/key.lua +++ b/lib/awful/key.lua @@ -14,6 +14,7 @@ local capi = { key = key, root = root, awesome = awesome } local gmath = require("gears.math") local gtable = require("gears.table") local gdebug = require("gears.debug") +local gobject = require("gears.object") --- The keyboard key used to trigger this keybinding. -- @@ -128,6 +129,13 @@ function key:get_has_root_binding() return capi.root.has_key(self) end +-- This is used by the keygrabber and prompt to identify valid awful.key +-- objects. It *cannot* be put directly in the object since `capi` uses a lot +-- of `next` internally and fixing that would suck more. +function key:get__is_awful_key() + return true +end + local function index_handler(self, k) if key["get_"..k] then return key["get_"..k](self) @@ -223,14 +231,26 @@ local function new_common(mod, _key, press, release, data) local ret = {} local subsets = gmath.subsets(key.ignore_modifiers) for _, set in ipairs(subsets) do - ret[#ret + 1] = capi.key({ modifiers = gtable.join(mod, set), - key = _key }) - if press then - ret[#ret]:connect_signal("press", function(_, ...) press(...) end) - end - if release then - ret[#ret]:connect_signal("release", function(_, ...) release(...) end) - end + local sub_key = capi.key { + modifiers = gtable.join(mod, set), + key = _key + } + + 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("release", function(_, ...) + if ret.on_release then + ret.on_release(...) + end + end) + + ret[#ret + 1] = sub_key end -- append custom userdata (like description) to a hotkey @@ -297,6 +317,10 @@ function key.mt:__call(...) return key.new(...) end +gobject.properties(capi.key, { + auto_emit = true, +}) + return setmetatable(key, key.mt) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 From 42a906f30093a54a030df9c3a156690c384d9e0d Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 16 Oct 2019 00:41:05 -0400 Subject: [PATCH 16/22] capi.button: Enable the miss handlers. --- lib/awful/button.lua | 77 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/lib/awful/button.lua b/lib/awful/button.lua index af3ccbdae..c56909227 100644 --- a/lib/awful/button.lua +++ b/lib/awful/button.lua @@ -14,6 +14,7 @@ local ipairs = ipairs local capi = { button = button, root = root } local gmath = require("gears.math") local gtable = require("gears.table") +local gobject = require("gears.object") local button = { mt = {} } @@ -118,7 +119,7 @@ function button:set_modifiers(mod) end end -for _, prop in ipairs { "description", "group", "on_press", "on_release", "name" } do +for _, prop in ipairs { "description", "group", "name" } do button["get_"..prop] = function(self) return reverse_map[self][prop] end @@ -127,6 +128,41 @@ for _, prop in ipairs { "description", "group", "on_press", "on_release", "name" end end +-- Store the callbacks as weakly as possible. +-- +-- Lua 5.1 GC is not very smart with reference loops. When the titlebar has +-- buttons, they usually contain the client in their lambda. In turn, storing +-- the callback in something stored by the titlebar, which is stored by the +-- clients, will not be GC-able as-is. +-- +-- To work around this, we use weak `v` references stored within a weak `k` +-- table. So both the `awful.button` and the callbacks are weak in their own +-- realm. To avoid the GC of the callback, it is important to use +-- `connect_signal` on the `capi.button` to keep a reference alive. +for _, prop in ipairs { "press", "release" } do + local long_name = "on_"..prop + + button["get_"..long_name] = function(self) + return reverse_map[self].weak_content[long_name] + end + button["set_"..long_name] = function(self, value) + local old = reverse_map[self].weak_content[long_name] + local new = function(_, ...) value(...) end + + if old then + for i=1, 4 do + self[i]:disconnect_signal(prop, old) + end + end + + reverse_map[self].weak_content[prop] = new + + for i=1, 4 do + self[i]:connect_signal(prop, new) + end + end +end + function button:get_button() return self[1].button end @@ -134,13 +170,13 @@ end function button:trigger() local data = reverse_map[self] - local press = data.weak_content.press + local press = self.on_press or data.weak_content.press if press then press() end - local release = data.weak_content.release + local release = self.on_release or data.weak_content.release if release then release() @@ -151,6 +187,13 @@ function button:get_has_root_binding() return capi.root.has_button(self) end +-- This is used by the keygrabber and prompt to identify valid awful.button +-- objects. It *cannot* be put directly in the object since `capi` uses a lot +-- of `next` internally and fixing that would suck more. +function button:get__is_awful_button() + return true +end + local function index_handler(self, k) if button["get_"..k] then return button["get_"..k](self) @@ -219,14 +262,14 @@ local function new_common(mod, _button, press, release) local ret = {} local subsets = gmath.subsets(ignore_modifiers) for _, set in ipairs(subsets) do - ret[#ret + 1] = capi.button({ modifiers = gtable.join(mod, set), - button = _button }) - if press then - ret[#ret]:connect_signal("press", function(_, ...) press(...) end) - end - if release then - ret[#ret]:connect_signal("release", function (_, ...) release(...) end) - end + local sub_button = capi.button { + modifiers = gtable.join(mod, set), + button = _button + } + + sub_button._private._legacy_convert_to = ret + + ret[#ret + 1] = sub_button end reverse_map[ret] = { @@ -239,6 +282,14 @@ local function new_common(mod, _button, press, release) _is_capi_button = false } + if press then + button.set_on_press(ret, press) + end + + if release then + button.set_on_release(ret, release) + end + return setmetatable(ret, obj_mt) end @@ -261,6 +312,10 @@ function button.mt:__call(...) return button.new(...) end +gobject.properties(capi.button, { + auto_emit = true, +}) + return setmetatable(button, button.mt) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 From e3959b45d5cca517caa5f8368971a0e66bef6536 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 16 Oct 2019 00:42:18 -0400 Subject: [PATCH 17/22] awful.button: Keep track of the `awful.button` in the `capi.button`. --- lib/awful/button.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/awful/button.lua b/lib/awful/button.lua index c56909227..4fbe98d6a 100644 --- a/lib/awful/button.lua +++ b/lib/awful/button.lua @@ -261,6 +261,7 @@ local obj_mt = { local function new_common(mod, _button, press, release) local ret = {} local subsets = gmath.subsets(ignore_modifiers) + for _, set in ipairs(subsets) do local sub_button = capi.button { modifiers = gtable.join(mod, set), From 10fd4e88837b83b1e76e0105612ac504ee7c2ede Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 26 Oct 2019 17:51:53 -0400 Subject: [PATCH 18/22] object: Try harder to auto-undeprecate the keys and buttons. Having the new object layout will be important soon when the append/remove methods start to get added to the client and the reborn `awful.keyboard` module. --- lib/gears/object/properties.lua | 45 ++++++++++++++++++++++++++++++--- lib/wibox/init.lua | 3 ++- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/lib/gears/object/properties.lua b/lib/gears/object/properties.lua index aaf6a9613..f671d98a4 100644 --- a/lib/gears/object/properties.lua +++ b/lib/gears/object/properties.lua @@ -88,6 +88,36 @@ function object.capi_index_fallback(class, args) class.set_newindex_miss_handler(setter) end +-- Convert the capi objects back into awful ones. +local function deprecated_to_current(content) + local first = content[1] + + local current = first and first._private and + first._private._legacy_convert_to or nil + + if not current then return nil end + + local ret = {current} + + for _, o in ipairs(content) do + -- If this is false, someone tried to mix things in a + -- way that is definitely not intentional. + assert(o._private and o._private._legacy_convert_to) + + if o._private._legacy_convert_to ~= current then + -- If this is false, someone tried to mix things in a + -- way that is definitely not intentional. + assert(o._private._legacy_convert_to) + + table.insert( + ret, o._private._legacy_convert_to + ) + current = o._private._legacy_convert_to + end + end + + return ret +end -- (private api) -- Many legacy Awesome APIs such as `client:tags()`, `root.buttons()`, @@ -130,7 +160,15 @@ local function copy_object(obj, to_set, name, capi_name, is_object, join_if, set local result = is_formatted and new_objs or gtable.join(unpack(new_objs)) - if capi_name and is_object then + -- First, when possible, get rid of the legacy format and go + -- back to the non-deprecated code path. + local current = self["set_"..name] + and deprecated_to_current(result) or nil + + if current then + self["set_"..name](self, current) + return capi_name and self[capi_name](self) or self[name] + elseif capi_name and is_object then return self[capi_name](self, result) elseif capi_name then return self[capi_name](result) @@ -166,8 +204,9 @@ function object._legacy_accessors(obj, name, capi_name, is_object, join_if, set_ obj, {}, name, capi_name, is_object, join_if, set_empty ) - assert(self._private[name]) - return self._private[name] + local current = deprecated_to_current(self._private[name]) + + return current or self._private[name] end magic_obj["set_"..name] = function(self, objs) diff --git a/lib/wibox/init.lua b/lib/wibox/init.lua index 0cbe610f8..b27c88947 100644 --- a/lib/wibox/init.lua +++ b/lib/wibox/init.lua @@ -66,7 +66,8 @@ function wibox:find_widgets(x, y) end function wibox:_buttons(btns) - return self.drawin:_buttons(btns) + -- The C code uses the argument count, `nil` counts. + return btns and self.drawin:_buttons(btns) or self.drawin:_buttons() end --- Create a widget that reflects the current state of this wibox. From 12a7d026c264a65c11edd9d724043d1cf8fdcfe5 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Mon, 31 Dec 2018 17:40:08 -0500 Subject: [PATCH 19/22] tests: Test the new key/button binding API --- tests/test-input-binding.lua | 236 +++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 tests/test-input-binding.lua diff --git a/tests/test-input-binding.lua b/tests/test-input-binding.lua new file mode 100644 index 000000000..90690ea6f --- /dev/null +++ b/tests/test-input-binding.lua @@ -0,0 +1,236 @@ +require("awful._compat") + +local runner = require( "_runner" ) +local placement = require( "awful.placement" ) +local gtable = require( "gears.table" ) +local test_client = require( "_client" ) + +local module = { + key = require( "awful.key" ), + button = require( "awful.button" ) +} + +local steps = {} + +local second = { + key = { "a", "b", "c", "d" }, + button = { 1 , 2 , 3 , 4 }, +} + +local function send_events(event_type, step) + root.fake_input("key_press" , "Alt_L") + root.fake_input(event_type.."_press" , second[event_type][step]) + root.fake_input(event_type.."_release", second[event_type][step]) + root.fake_input("key_release", "Alt_L") +end + +-- Test the shared API between the keys and buttons +for _, type_name in ipairs { "key", "button" } do + local objects = {} + + local pressed, released = {}, {} + + table.insert(steps, function() + objects[1] = module[type_name]( + {"Mod1"}, second[type_name][1], function() pressed[1] = true end, function() released[1] = true end + ) + + assert(objects[1].has_root_binding == false) + assert(not pressed [1]) + assert(not released[1]) + + root["_append_"..type_name](objects[1]) + + -- It is async, the result need to be verified later + return true + end) + + table.insert(steps, function() + assert(objects[1].has_root_binding) + + -- Test "artificial" execution + objects[1]:trigger() + + assert(pressed [1]) + assert(released[1]) + + -- Test adding and removing in the same iteration + root["_remove_"..type_name](objects[1]) + + assert(second[type_name][2]) + objects[2] = module[type_name]( + {"Mod1"}, second[type_name][2], function() pressed[2] = true end, function() released[2] = true end + ) + + root["_append_"..type_name](objects[2]) + + return true + end) + + table.insert(steps, function() + assert(objects[2].has_root_binding == true ) + assert(objects[1].has_root_binding == false) + + -- Make sure the cursor is not over the wibar + placement.centered(mouse) + + assert(second[type_name][2]) + send_events(type_name, 2) + + return true + end) + + -- Wait until the events are registered. + table.insert(steps, function() + if (not pressed[2]) or (not released[2]) then return end + + -- Spawn a client. + test_client("myclient") + + return true + end) + + --FIXME it works when manually tested, but automated testing is too flacky + -- to enable... + if type_name == "key" then + table.insert(steps, function() + if #mouse.screen.clients ~= 1 then return end + + placement.maximize(mouse.screen.clients[1]) + + return true + end) + + local o1, o2 = nil + + table.insert(steps, function() + local c = mouse.screen.clients[1] + + o1 = module[type_name]( + {"Mod1"}, second[type_name][3], function() pressed[3] = true end, function() released[3] = true end + ) + + -- Test the old API. + c[type_name.."s"](c, gtable.join(o1)) + + return true + end) + + -- This wont work until the client buttons/keys are updated, there is no + -- way to know ahead of time. + table.insert(steps, function(count) + + if count < 5 then awesome.sync(); return end + + send_events(type_name, 3) + + return true + end) + + table.insert(steps, function() + if (not pressed[3]) or (not released[3]) then return end + + local c = mouse.screen.clients[1] + + -- Unset the events to make sure keys can be removed. + pressed[3], released[3] = false, false + + o2 = module[type_name]( + {"Mod1"}, second[type_name][4], function() pressed[4] = true end, function() released[4] = true end + ) + + -- Test the new API + c[type_name.."s"] = {o2} + + return true + end) + + table.insert(steps, function(count) + if count < 5 then awesome.sync(); return end + + send_events(type_name, 3) + send_events(type_name, 4) + + return true + end) + + table.insert(steps, function() + if (not pressed[4]) or (not released[4]) then return end + + assert(not pressed [3]) + assert(not released[3]) + + local c = mouse.screen.clients[1] + + -- Make sure mixing the 2 syntaxes doesn't create a chimera state + pressed[4], released[4] = false, false + + -- This *will* happen with older configs because `awful.rules` will + -- loop the properties, find the old capi list and fail to understand + -- that the content should use the legacy API. + c[type_name.."s"] = gtable.join(o1) + + return true + end) + + table.insert(steps, function(count) + if count < 5 then awesome.sync(); return end + + send_events(type_name, 3) + send_events(type_name, 4) + + return true + end) + + table.insert(steps, function() + if (not pressed[3]) or (not released[3]) then return end + + assert(not pressed [4]) + assert(not released[4]) + + local c = mouse.screen.clients[1] + + assert(#c[type_name.."s"] ~= 0) + + -- Test setting the object to `false` to simulate an inline Lua + -- expression gone wrong. This used to work and is rather + -- convenient, so lets not break it even if it is technically a bug. + c[type_name.."s"] = false + + assert(#c[type_name.."s"] == 0) + + c[type_name.."s"] = {o1} + + assert(#c[type_name.."s"] ~= 0) + + -- Test removing the objects using `nil`. + c[type_name.."s"] = nil + + assert(#c[type_name.."s"] == 0) + + c[type_name.."s"] = {o1} + + assert(#c[type_name.."s"] ~= 0) + + -- Test removing using `{}` + c[type_name.."s"] = {} + + assert(#c[type_name.."s"] == 0) + + c:kill() + + return true + end) + + -- Cleanup (otherwise there is a race with the root.buttons tests) + table.insert(steps, function() + if #mouse.screen.clients ~= 0 then return end + + return true + end) + end +end + +runner.run_steps(steps) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 From 4723c3218acf309ffa901c1f5b4f3f5de56c0e3e Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 26 Oct 2019 19:54:55 -0400 Subject: [PATCH 20/22] tests: Make sure the legacy buttons/keys method are converted back. --- tests/test-input-binding.lua | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/test-input-binding.lua b/tests/test-input-binding.lua index 90690ea6f..619a6b537 100644 --- a/tests/test-input-binding.lua +++ b/tests/test-input-binding.lua @@ -168,7 +168,15 @@ for _, type_name in ipairs { "key", "button" } do -- This *will* happen with older configs because `awful.rules` will -- loop the properties, find the old capi list and fail to understand -- that the content should use the legacy API. - c[type_name.."s"] = gtable.join(o1) + + local joined = gtable.join(o1) + + assert(#joined == 4) + + c[type_name.."s"] = joined + + -- It should have been converted to the new format. + assert(#c[type_name.."s"] == 1) return true end) @@ -190,7 +198,7 @@ for _, type_name in ipairs { "key", "button" } do local c = mouse.screen.clients[1] - assert(#c[type_name.."s"] ~= 0) + assert(#c[type_name.."s"] == 1) -- Test setting the object to `false` to simulate an inline Lua -- expression gone wrong. This used to work and is rather @@ -201,7 +209,7 @@ for _, type_name in ipairs { "key", "button" } do c[type_name.."s"] = {o1} - assert(#c[type_name.."s"] ~= 0) + assert(#c[type_name.."s"] == 1) -- Test removing the objects using `nil`. c[type_name.."s"] = nil @@ -210,7 +218,7 @@ for _, type_name in ipairs { "key", "button" } do c[type_name.."s"] = {o1} - assert(#c[type_name.."s"] ~= 0) + assert(#c[type_name.."s"] == 1) -- Test removing using `{}` c[type_name.."s"] = {} From 944ef213e674c8b37600165dd3ff8bb11c2cc7ef Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 27 Oct 2019 16:53:33 -0400 Subject: [PATCH 21/22] tests: Try both the `args` and parameters based `awful.{key,button}`. --- tests/test-input-binding.lua | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/test-input-binding.lua b/tests/test-input-binding.lua index 619a6b537..75cde9360 100644 --- a/tests/test-input-binding.lua +++ b/tests/test-input-binding.lua @@ -58,6 +58,8 @@ for _, type_name in ipairs { "key", "button" } do root["_remove_"..type_name](objects[1]) assert(second[type_name][2]) + + -- Use the multiple parameters syntax. objects[2] = module[type_name]( {"Mod1"}, second[type_name][2], function() pressed[2] = true end, function() released[2] = true end ) @@ -106,9 +108,17 @@ for _, type_name in ipairs { "key", "button" } do table.insert(steps, function() local c = mouse.screen.clients[1] - o1 = module[type_name]( - {"Mod1"}, second[type_name][3], function() pressed[3] = true end, function() released[3] = true end - ) + -- This time, use the `args` syntax. + local args = { + modifiers = {"Mod1"}, + on_press = function() pressed [3] = true end, + on_release = function() released[3] = true end + } + + args[type_name] = second[type_name][3] + + -- This time, use the `args` syntax. + o1 = module[type_name](args) -- Test the old API. c[type_name.."s"](c, gtable.join(o1)) From ee331a4eff0da0a8016bf7b13231c1dee1f40997 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Fri, 28 Dec 2018 21:06:32 -0500 Subject: [PATCH 22/22] keygrabber: Remove the hardcoded way to add keybindings. There is now a real API for this. --- lib/awful/keygrabber.lua | 177 +++++++++--------- lib/awful/root.lua | 4 +- .../examples/text/awful/keygrabber/alttab.lua | 91 +++++---- .../text/awful/keygrabber/autostart.lua | 2 +- .../awful/keygrabber/root_keybindings.lua | 140 +++++++------- .../examples/text/awful/keygrabber/vimode.lua | 5 +- tests/test-keygrabber.lua | 11 +- 7 files changed, 229 insertions(+), 201 deletions(-) diff --git a/lib/awful/keygrabber.lua b/lib/awful/keygrabber.lua index 2d076c672..6d5c45ae9 100644 --- a/lib/awful/keygrabber.lua +++ b/lib/awful/keygrabber.lua @@ -71,6 +71,7 @@ local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility local gtable = require("gears.table") local gobject = require("gears.object") local gtimer = require("gears.timer") +local akeyboard = require("awful.keyboard") local glib = require("lgi").GLib local capi = { keygrabber = keygrabber, root = root, awesome = awesome } @@ -90,9 +91,6 @@ local conversion = nil --BEGIN one day create a proper API to add and remove keybindings at runtime. -- Doing it this way is horrible. --- This list of keybindings to add in the next event loop cycle. -local delay_list = {} - -- Read the modifiers name and map their keysyms to the modkeys local function generate_conversion_map() if conversion then return conversion end @@ -119,60 +117,24 @@ local function add_root_keybindings(self, list) list, "`add_root_keybindings` needs to be called with a list of keybindings" ) - local was_started = #delay_list > 0 - - -- When multiple `awful.keygrabber` objects are created in `rc.lua`, avoid - -- unpacking and repacking all keys for each instance and instead merge - -- everything into one operation. In not so extreme cases, not doing so - -- would slow down `awesome.restart()` by a small, but noticeable amount - -- of time. - table.insert(delay_list, {self, list}) - - -- As of Awesome v4.3, `root.keys()` is an all or nothing API and there - -- isn't a standard mechanism to add and remove keybindings at runtime - -- without replacing the full list. Given `rc.lua` sets this list, not - -- using a delayed call would cause all `awful.keygrabber` created above - -- `root.keys(globalkeys)` to be silently overwritten. --FIXME v5 - if not was_started then - gtimer.delayed_call(function() - local ret = {} - - for _, obj in ipairs(delay_list) do - local obj_self, obj_list = obj[1], obj[2] - - for _, v in ipairs(obj_list) do - local mods, key, press, release, data = unpack(v) - - if type(release) == 'table' then - data = release - release = nil - end - - if press then - local old_press = press - press = function(...) - obj_self:start() - old_press(...) - end - end - - if release then - local old_release = release - release = function(...) - obj_self:start() - old_release(...) - end - end - - table.insert(ret, akey(mods, key, press, release, data)) - end + for _, kb in ipairs(list) do + if kb.on_press then + local old_press = kb.on_press + kb.on_press = function(...) + self:start() + old_press(...) end + end - -- Wow... - capi.root.keys(gtable.join( capi.root.keys(), unpack(ret) )) + if kb.on_release then + local old_release = kb.on_release + kb.on_release = function(...) + self:start() + old_release(...) + end + end - delay_list = {} - end) + akeyboard.append_global_keybinding(kb) end end @@ -249,13 +211,13 @@ local function runner(self, modifiers, key, event) end for _,v in ipairs(self._private.keybindings[key]) do - if #filtered_modifiers == #v[1] then + if #filtered_modifiers == #v.modifiers then local match = true - for _,v2 in ipairs(v[1]) do + for _,v2 in ipairs(v.modifiers) do match = match and mod[v2] end - if match then - v[3](self) + if match and v.on_press then + v.on_press(self) if self.mask_event_callback ~= false then return @@ -400,20 +362,13 @@ end --- The keybindings associated with this keygrabber. -- --- The keybindings syntax is the same as `awful.key` or `awful.prompt.hooks`. It --- consists of a table with 4 entries. --- --- * `mods` A table with modifier keys, such as `shift`, `mod4`, `mod1` (alt) or --- `control`. --- * `key` The key name, such as `left` or `f` --- * `callback` A function that will be called when the key combination is --- pressed. --- * `description` A table various metadata to be used for `awful.hotkeys_popup`. +-- This property contains a table of `awful.key` objects. -- -- @property keybindings -- @param table -- @see export_keybindings -- @see root_keybindings +-- @see awful.key --- If any key is pressed that is not in this list, the keygrabber is stopped. -- @@ -538,27 +493,62 @@ end -- Those keybindings will automatically start the keygrabbing when hit. -- -- @method add_keybinding --- @tparam table mods A table with modifier keys, such as `shift`, `mod4`, `mod1` (alt) or --- `control`. --- @tparam string key The key name, such as `left` or `f` --- @tparam function callback A function that will be called when the key --- combination is pressed. --- @tparam[opt] table description A table various metadata to be used for `awful.hotkeys_popup`. --- @tparam string description.description The keybinding description +-- @tparam awful.key key The key. -- @tparam string description.group The keybinding group -function keygrabber:add_keybinding(mods, key, callback, description) + +function keygrabber:add_keybinding(key, _keycode, _callback, _description) + local mods = not akey._is_awful_key and akey or nil + + if mods then + gdebug.deprecate(":add_keybinding now takes `awful.key` objects instead" + .. " of multiple parameters", + {deprecated_in=5} + ) + + key = akey { + modifiers = mods, + key = _keycode, + description = _description, + on_press = _callback + } + else + _keycode = key.key + end + self._private.keybindings[key] = self._private.keybindings[key] or {} - table.insert(self._private.keybindings[key], { - mods, key, callback, description - }) + table.insert(self._private.keybindings[_keycode], key) if self.export_keybindings then - add_root_keybindings(self, {{mods, key, callback, description}}) + add_root_keybindings(self, {key}) end end function keygrabber:set_root_keybindings(keys) - add_root_keybindings(self, keys) + local real_keys = {} + + -- Handle the pre-object-oriented input structures. + for _, key in ipairs(keys) do + if key._is_awful_key then + table.insert(real_keys, key) + else + gdebug.deprecate(":set_root_keybindings now takes `awful.key` " + .. " objects instead of tables", + {deprecated_in=5} + ) + + local mods, keycode, press, release, data = unpack(key) + + table.insert(real_keys, akey { + modifiers = mods, + key = keycode, + description = (data or {}).description, + on_press = press, + on_release = release, + }) + end + end + + add_root_keybindings(self, real_keys) end -- Turn into a set. @@ -689,26 +679,41 @@ function keygrab.run_with_keybindings(args) -- Build the hook map for _,v in ipairs(args.keybindings or {}) do - if #v >= 3 and #v <= 4 then - local _,key,callback = unpack(v) + if v._is_awful_key then + ret._private.keybindings[v.key] = ret._private.keybindings[v.key] or {} + table.insert(ret._private.keybindings[v.key], v) + elseif #v >= 3 and #v <= 4 then + gdebug.deprecate("keybindings now contains `awful.key` objects" + .. "instead of multiple tables", + {deprecated_in=5} + ) + + local modifiers, key, callback = unpack(v) if type(callback) == "function" then + + local k = akey { + modifiers = modifiers, + key = key, + on_press = callback, + } + ret._private.keybindings[key] = ret._private.keybindings[key] or {} - table.insert(ret._private.keybindings[key], v) + table.insert(ret._private.keybindings[key], k) else gdebug.print_warning( "The hook's 3rd parameter has to be a function. " .. - gdebug.dump(v) + gdebug.dump(v or {}) ) end else gdebug.print_warning( - "The hook has to have at least 3 parameters. ".. gdebug.dump(v) + "The keybindings should be awful.key objects".. gdebug.dump(v or {}) ) end end if args.export_keybindings then - add_root_keybindings(ret, args.keybindings) + ret:set_root_keybindings(args.keybindings) end local mt = getmetatable(ret) diff --git a/lib/awful/root.lua b/lib/awful/root.lua index 6f28f5da5..b423dff3c 100644 --- a/lib/awful/root.lua +++ b/lib/awful/root.lua @@ -43,11 +43,11 @@ for _, type_name in ipairs { "button", "key" } do if idx then for i=1, #v do assert( - new_values[idx+i] == v[i], + new_values[idx] == v[i], "The root private "..type_name.." table is corrupted" ) - table.remove(new_values, idx+i) + table.remove(new_values, idx) end end diff --git a/tests/examples/text/awful/keygrabber/alttab.lua b/tests/examples/text/awful/keygrabber/alttab.lua index 7116fc4a9..c82d18301 100644 --- a/tests/examples/text/awful/keygrabber/alttab.lua +++ b/tests/examples/text/awful/keygrabber/alttab.lua @@ -1,41 +1,50 @@ --- --DOC_HEADER --DOC_NO_USAGE --- --- local was_called = {} --DOC_HIDE --- --- local awful = {keygrabber = require("awful.keygrabber"), --DOC_HIDE --- client={focus={history={--DOC_HIDE --- disable_tracking = function() was_called[1] = true end, --DOC_HIDE --- enable_tracking = function() was_called[2] = true end, --DOC_HIDE --- select_next = function() was_called[3] = true end, --DOC_HIDE --- select_previous = function() was_called[4] = true end, --DOC_HIDE --- }}}}--DOC_HIDE --- --- awful.keygrabber { --- keybindings = { --- {{"Mod1" }, "Tab", awful.client.focus.history.select_previous}, --- {{"Mod1", "Shift"}, "Tab", awful.client.focus.history.select_next }, --- }, --- -- Note that it is using the key name and not the modifier name. --- stop_key = "Mod1", --- stop_event = "release", --- start_callback = awful.client.focus.history.disable_tracking, --- stop_callback = awful.client.focus.history.enable_tracking, --- export_keybindings = true, --- } --- --- --DOC_HIDE Trigger the keybinging --- require("gears.timer").run_delayed_calls_now() --DOC_HIDE `export_keybindings` is async --- root.fake_input("key_press", "Alt_L")--DOC_HIDE --- root.fake_input("key_press", "Tab")--DOC_HIDE --- root.fake_input("key_release", "Tab")--DOC_HIDE --- root.fake_input("key_release", "Alt_L")--DOC_HIDE --- assert(was_called[1] and was_called[1] and was_called[2] and was_called[4])--DOC_HIDE --- assert(not was_called[3]) --DOC_HIDE --- --- --DOC_HIDE Now make sure it can be triggered again --- root.fake_input("key_press", "Alt_L")--DOC_HIDE --- root.fake_input("key_press", "Shift_L")--DOC_HIDE --- root.fake_input("key_press", "Tab")--DOC_HIDE --- root.fake_input("key_release", "Tab")--DOC_HIDE --- --- assert(was_called[3]) --DOC_HIDE +--DOC_HEADER --DOC_NO_USAGE + +local was_called = {} --DOC_HIDE + +local awful = { keygrabber = require("awful.keygrabber"), --DOC_HIDE + key = require("awful.key"), --DOC_HIDE + client={focus={history={--DOC_HIDE + disable_tracking = function() was_called[1] = true end, --DOC_HIDE + enable_tracking = function() was_called[2] = true end, --DOC_HIDE + select_next = function() was_called[3] = true end, --DOC_HIDE + select_previous = function() was_called[4] = true end, --DOC_HIDE +}}}}--DOC_HIDE + + awful.keygrabber { + keybindings = { + awful.key { + modifiers = {"Mod1"}, + key = "Tab", + on_press = awful.client.focus.history.select_previous + }, + awful.key { + modifiers = {"Mod1", "Shift"}, + key = "Tab", + on_press = awful.client.focus.history.select_next + }, + }, + -- Note that it is using the key name and not the modifier name. + stop_key = "Mod1", + stop_event = "release", + start_callback = awful.client.focus.history.disable_tracking, + stop_callback = awful.client.focus.history.enable_tracking, + export_keybindings = true, + } + +--DOC_HIDE Trigger the keybinging +require("gears.timer").run_delayed_calls_now() --DOC_HIDE `export_keybindings` is async +root.fake_input("key_press", "Alt_L")--DOC_HIDE +root.fake_input("key_press", "Tab")--DOC_HIDE +root.fake_input("key_release", "Tab")--DOC_HIDE +root.fake_input("key_release", "Alt_L")--DOC_HIDE +assert(was_called[1] and was_called[1] and was_called[2] and was_called[4])--DOC_HIDE +assert(not was_called[3]) --DOC_HIDE + +--DOC_HIDE Now make sure it can be triggered again +root.fake_input("key_press", "Alt_L")--DOC_HIDE +root.fake_input("key_press", "Shift_L")--DOC_HIDE +root.fake_input("key_press", "Tab")--DOC_HIDE +root.fake_input("key_release", "Tab")--DOC_HIDE + +assert(was_called[3]) --DOC_HIDE diff --git a/tests/examples/text/awful/keygrabber/autostart.lua b/tests/examples/text/awful/keygrabber/autostart.lua index f490b3e92..36120899e 100644 --- a/tests/examples/text/awful/keygrabber/autostart.lua +++ b/tests/examples/text/awful/keygrabber/autostart.lua @@ -7,7 +7,7 @@ local autostart_works = false --DOC_HIDE awful.keygrabber { autostart = true, - stop_key = "Return", + stop_key = "Return", stop_callback = function(_, _, _, sequence) autostart_works = true --DOC_HIDE assert(sequence == "abc") --DOC_HIDE diff --git a/tests/examples/text/awful/keygrabber/root_keybindings.lua b/tests/examples/text/awful/keygrabber/root_keybindings.lua index 61c6c62bb..5e9eb0312 100644 --- a/tests/examples/text/awful/keygrabber/root_keybindings.lua +++ b/tests/examples/text/awful/keygrabber/root_keybindings.lua @@ -1,68 +1,72 @@ --- --DOC_GEN_OUTPUT --DOC_HIDE --- local awful = { keygrabber = require("awful.keygrabber") } --DOC_HIDE --- --- local keybinding_works = {} --DOC_HIDE --- --- local g = --DOC_HIDE --- awful.keygrabber { --- mask_modkeys = true, --- root_keybindings = { --- {{"Mod4"}, "i", function(self) --- print("Is now active!", self) --- keybinding_works[1] = true --DOC_HIDE --- end}, --- }, --- keybindings = { --- {{"Mod4", "Shift"}, "i", function(self) --- print("Called again!") --- keybinding_works[3] = true --DOC_HIDE --- self:stop() --- end}, --- }, --- keypressed_callback = function(_, modifiers, key) --- print("A key was pressed:", key, "with", #modifiers, "modifier!") --- keybinding_works[2] = keybinding_works[2] and keybinding_works[2] + 1 or 1 --DOC_HIDE --- end, --- } --- --DOC_NEWLINE --- -- The following will **NOT** trigger the keygrabbing because it isn't exported --- -- to the root (global) keys. Adding `export_keybindings` would solve that --- require("gears.timer").run_delayed_calls_now() --DOC_HIDE `root_keybindings` is async --- root._execute_keybinding({"Mod4", "Shift"}, "i") --- assert(#keybinding_works == 0) --- --- --DOC_NEWLINE --- -- But this will start the keygrabber because it is part of the root_keybindings --- root._execute_keybinding({"Mod4"}, "i") --- assert(keybinding_works[1]) --DOC_HIDE --- assert(not keybinding_works[2]) --DOC_HIDE --- --- --DOC_NEWLINE --- -- Note that that keygrabber is running, all callbacks should work: --- root.fake_input("key_press" , "a") --- root.fake_input("key_release" , "a") --- assert(keybinding_works[2] == 1) --DOC_HIDE --- --- --DOC_NEWLINE --- -- Calling the root keybindings now wont work because they are not part of --- -- the keygrabber internal (own) keybindings, so `keypressed_callback` will --- -- be called. --- root._execute_keybinding({"Mod4"}, "i") --- assert(keybinding_works[2] == 2) --DOC_HIDE because mask_modkeys is set --- assert(g == awful.keygrabber.current_instance) --DOC_HIDE --- assert(not keybinding_works[3])--DOC_HIDE --- --- --- --DOC_NEWLINE --- -- Now the keygrabber own keybindings will work --- root._execute_keybinding({"Mod4", "Shift"}, "i") --- assert(keybinding_works[3])--DOC_HIDE --- keybinding_works[2] = 0--DOC_HIDE --- assert(not awful.keygrabber.current_instance) --DOC_HIDE --- root.fake_input("key_press" , "a") --DOC_HIDE --- root.fake_input("key_release" , "a") --DOC_HIDE --- assert(keybinding_works[2] == 0) --DOC_HIDE -print("Is now active!", "nil") -print("A key was pressed:", "a", "with", "0", "modifier!") -print("A key was pressed:", "i", "with", "1", "modifier!") -print("Called again!") +--DOC_GEN_OUTPUT --DOC_HIDE +local awful = { keygrabber = require("awful.keygrabber"), --DOC_HIDE + key = require("awful.key") } --DOC_HIDE + +local keybinding_works = {} --DOC_HIDE + +local g = --DOC_HIDE +awful.keygrabber { + mask_modkeys = true, + root_keybindings = { + awful.key { + modifiers = {"Mod4"}, + key = "i", + on_press = function(self) + print("Is now active!", self) + keybinding_works[1] = true --DOC_HIDE + end + }, + }, + keybindings = { + awful.key { + modifiers = {"Mod4", "Shift"}, + key = "i", + on_press = function(self) + print("Called again!") + keybinding_works[3] = true --DOC_HIDE + self:stop() + end + }, + }, + keypressed_callback = function(_, modifiers, key) + print("A key was pressed:", key, "with", #modifiers, "modifier!") + keybinding_works[2] = keybinding_works[2] and keybinding_works[2] + 1 or 1 --DOC_HIDE + end, +} +--DOC_NEWLINE +-- The following will **NOT** trigger the keygrabbing because it isn't exported +-- to the root (global) keys. Adding `export_keybindings` would solve that +require("gears.timer").run_delayed_calls_now() --DOC_HIDE `root_keybindings` is async +root._execute_keybinding({"Mod4", "Shift"}, "i") +assert(#keybinding_works == 0) + +--DOC_NEWLINE +-- But this will start the keygrabber because it is part of the root_keybindings +root._execute_keybinding({"Mod4"}, "i") +assert(keybinding_works[1]) --DOC_HIDE +assert(not keybinding_works[2]) --DOC_HIDE + +--DOC_NEWLINE +-- Note that that keygrabber is running, all callbacks should work: +root.fake_input("key_press" , "a") +root.fake_input("key_release" , "a") +assert(keybinding_works[2] == 1) --DOC_HIDE + +--DOC_NEWLINE +-- Calling the root keybindings now wont work because they are not part of +-- the keygrabber internal (own) keybindings, so `keypressed_callback` will +-- be called. +root._execute_keybinding({"Mod4"}, "i") +assert(keybinding_works[2] == 2) --DOC_HIDE because mask_modkeys is set +assert(g == awful.keygrabber.current_instance) --DOC_HIDE +assert(not keybinding_works[3])--DOC_HIDE + +--DOC_NEWLINE +-- Now the keygrabber own keybindings will work +root._execute_keybinding({"Mod4", "Shift"}, "i") +assert(keybinding_works[3])--DOC_HIDE +keybinding_works[2] = 0--DOC_HIDE +assert(not awful.keygrabber.current_instance) --DOC_HIDE +root.fake_input("key_press" , "a") --DOC_HIDE +root.fake_input("key_release" , "a") --DOC_HIDE +assert(keybinding_works[2] == 0) --DOC_HIDE diff --git a/tests/examples/text/awful/keygrabber/vimode.lua b/tests/examples/text/awful/keygrabber/vimode.lua index 74e9a8756..c87004c18 100644 --- a/tests/examples/text/awful/keygrabber/vimode.lua +++ b/tests/examples/text/awful/keygrabber/vimode.lua @@ -2,7 +2,8 @@ local gears = {table = require("gears.table")} --DOC_HIDE -local awful = { keygrabber = require("awful.keygrabber") } --DOC_HIDE +local awful = { keygrabber = require("awful.keygrabber"), --DOC_HIDE + key = require("awful.key") } --DOC_HIDE local map, actions = { verbs = { @@ -44,6 +45,6 @@ local awful = { keygrabber = require("awful.keygrabber") } --DOC_HIDE stop_callback = parse, stop_key = gears.table.keys(map.verbs), root_keybindings = { - {{"Mod4"}, "v"} + awful.key({"Mod4"}, "v") }, } diff --git a/tests/test-keygrabber.lua b/tests/test-keygrabber.lua index 4070d22a9..acfdbd568 100644 --- a/tests/test-keygrabber.lua +++ b/tests/test-keygrabber.lua @@ -7,11 +7,20 @@ local awful = require("awful") local keygrabber_a_active = false local keygrabber_b_active = false +-- Disable the deprecation to test both the current and legacy APIs. +local gdebug = require("gears.debug") +gdebug.deprecate = function() end + local steps = { function() + awful.keygrabber { keybindings = { - {{}, "a", function() keygrabber_a_active = true end}, + awful.key { + modifiers = {}, + key = "a", + on_press = function() keygrabber_a_active = true end + } }, stop_key = "Escape", stop_callback = function() keygrabber_a_active = false end,