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 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/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/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/_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/button.lua b/lib/awful/button.lua index b59303f05..4fbe98d6a 100644 --- a/lib/awful/button.lua +++ b/lib/awful/button.lua @@ -2,19 +2,29 @@ --- 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 --- @classmod awful.button +-- @inputmodule awful.button --------------------------------------------------------------------------- -- 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") +local gobject = require("gears.object") 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,38 +34,289 @@ local button = { mt = {} } -- @table ignore_modifiers local ignore_modifiers = { "Lock", "Mod2" } ---- Create a new button to use as binding. +--- The mouse buttons names. -- --- 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. +-- It can be used instead of the button ids. -- --- 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. +-- @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. -- --- @treturn table A table with one or several button objects. -function button.new(mod, _button, press, release) - local ret = {} +-- 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. +-- @method :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 _, 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) + for k, set in ipairs(subsets) do + self[k].modifiers = gtable.join(mod, set) + end +end + +for _, prop in ipairs { "description", "group", "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 + +-- 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 - if release then - ret[#ret]:connect_signal("release", function (_, ...) release(...) end) + + reverse_map[self].weak_content[prop] = new + + for i=1, 4 do + self[i]:connect_signal(prop, new) end end - return ret +end + +function button:get_button() + return self[1].button +end + +function button:trigger() + local data = reverse_map[self] + + local press = self.on_press or data.weak_content.press + + if press then + press() + end + + local release = self.on_release or data.weak_content.release + + if release then + release() + end +end + +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) + end + + if type(button[k]) == "function" then + return button[k] + end + + local data = reverse_map[self] + assert(data) + + if data[k] ~= nil then + return data[k] + else + return data.weak_content[k] + end +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) + + if data.weak_content[key] ~= nil then + data.weak_content[key] = value + else + data[key] = value + end +end + +local obj_mt = { + __index = index_handler, + __newindex = newindex_handler +} + +--- Create a new button to use as binding. +-- +-- @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. +-- +-- @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 + 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] = { + -- 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 + } + + 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 + +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(...) 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 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/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 5826a7c97..6d8b2dcc9 100644 --- a/lib/awful/key.lua +++ b/lib/awful/key.lua @@ -2,8 +2,9 @@ --- Create easily new key objects ignoring certain modifiers. -- -- @author Julien Danjou <julien@danjou.info> --- @copyright 2009 Julien Danjou --- @classmod awful.key +-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> +-- @copyright 2018 Emmanuel Lepage Vallee +-- @inputmodule awful.key --------------------------------------------------------------------------- -- Grab environment we need @@ -12,41 +13,168 @@ 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 gobject = require("gears.object") + +--- 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 + +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) + end + + if type(key[k]) == "function" then + return key[k] + 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 -- @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,89 +187,106 @@ 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. --- 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 - 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 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. @@ -172,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 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/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/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..b423dff3c --- /dev/null +++ b/lib/awful/root.lua @@ -0,0 +1,119 @@ +--------------------------------------------------------------------------- +-- @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] == v[i], + "The root private "..type_name.." table is corrupted" + ) + + table.remove(new_values, idx) + 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 + + 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 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 19ab239bb..f671d98a4 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 @@ -87,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()`, @@ -129,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) @@ -161,18 +200,13 @@ 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 ) + local current = deprecated_to_current(self._private[name]) - assert(self._private[name]) - return self._private[name] + return current or self._private[name] end magic_obj["set_"..name] = function(self, objs) @@ -181,7 +215,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 diff --git a/lib/wibox/init.lua b/lib/wibox/init.lua index 1f4c7ff57..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. @@ -277,7 +278,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/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" 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() 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 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/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/client.lua b/tests/examples/shims/client.lua index 758c293fc..f2067131f 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,18 @@ 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 } + + --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 -- and a `transient_for` property. It will cause a stack overflow @@ -194,10 +198,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. @@ -243,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) 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/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 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/examples/text/awful/keygrabber/alttab.lua b/tests/examples/text/awful/keygrabber/alttab.lua index b72c572e8..c82d18301 100644 --- a/tests/examples/text/awful/keygrabber/alttab.lua +++ b/tests/examples/text/awful/keygrabber/alttab.lua @@ -2,7 +2,8 @@ local was_called = {} --DOC_HIDE -local awful = {keygrabber = require("awful.keygrabber"), --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 @@ -12,8 +13,16 @@ local awful = {keygrabber = require("awful.keygrabber"), --DOC_HIDE awful.keygrabber { keybindings = { - {{"Mod1" }, "Tab", awful.client.focus.history.select_previous}, - {{"Mod1", "Shift"}, "Tab", awful.client.focus.history.select_next }, + 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", 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 86a090b83..5e9eb0312 100644 --- a/tests/examples/text/awful/keygrabber/root_keybindings.lua +++ b/tests/examples/text/awful/keygrabber/root_keybindings.lua @@ -1,5 +1,6 @@ --DOC_GEN_OUTPUT --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 keybinding_works = {} --DOC_HIDE @@ -7,17 +8,25 @@ 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}, + awful.key { + modifiers = {"Mod4"}, + key = "i", + on_press = 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}, + 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!") @@ -52,7 +61,6 @@ 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") 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-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 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. diff --git a/tests/test-input-binding.lua b/tests/test-input-binding.lua new file mode 100644 index 000000000..75cde9360 --- /dev/null +++ b/tests/test-input-binding.lua @@ -0,0 +1,254 @@ +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]) + + -- 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 + ) + + 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] + + -- 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)) + + 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. + + 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) + + 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"] == 1) + + -- 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"] == 1) + + -- 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"] == 1) + + -- 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 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,