diff --git a/awesomerc.lua b/awesomerc.lua index 25b355c7..a40e977b 100644 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -102,49 +102,6 @@ mykeyboardlayout = awful.widget.keyboardlayout() -- Create a textclock widget mytextclock = wibox.widget.textclock() --- Create a wibox for each screen and add it --- @TAGLIST_BUTTON@ -local taglist_buttons = { - awful.button({ }, 1, function(t) t:view_only() end), - awful.button({ modkey }, 1, function(t) - if client.focus then - client.focus:move_to_tag(t) - end - end), - awful.button({ }, 3, awful.tag.viewtoggle), - awful.button({ modkey }, 3, function(t) - if client.focus then - client.focus:toggle_tag(t) - end - end), - awful.button({ }, 4, function(t) awful.tag.viewnext(t.screen) end), - awful.button({ }, 5, function(t) awful.tag.viewprev(t.screen) end), -} - --- @TASKLIST_BUTTON@ -local tasklist_buttons = { - awful.button({ }, 1, function (c) - if c == client.focus then - c.minimized = true - else - c:emit_signal( - "request::activate", - "tasklist", - {raise = true} - ) - end - end), - awful.button({ }, 3, function() - awful.menu.client_list({ theme = { width = 250 } }) - end), - awful.button({ }, 4, function () - awful.client.focus.byidx(1) - end), - awful.button({ }, 5, function () - awful.client.focus.byidx(-1) - end), -} - -- @DOC_WALLPAPER@ screen.connect_signal("request::wallpaper", function(s) -- Wallpaper @@ -165,6 +122,7 @@ screen.connect_signal("request::desktop_decoration", function(s) -- Create a promptbox for each screen s.mypromptbox = awful.widget.prompt() + -- Create an imagebox widget which will contain an icon indicating which layout we're using. -- We need one layoutbox per screen. s.mylayoutbox = awful.widget.layoutbox { @@ -181,14 +139,44 @@ screen.connect_signal("request::desktop_decoration", function(s) s.mytaglist = awful.widget.taglist { screen = s, filter = awful.widget.taglist.filter.all, - buttons = taglist_buttons + buttons = { + awful.button({ }, 1, function(t) t:view_only() end), + awful.button({ modkey }, 1, function(t) + if client.focus then + client.focus:move_to_tag(t) + end + end), + awful.button({ }, 3, awful.tag.viewtoggle), + awful.button({ modkey }, 3, function(t) + if client.focus then + client.focus:toggle_tag(t) + end + end), + awful.button({ }, 4, function(t) awful.tag.viewnext(t.screen) end), + awful.button({ }, 5, function(t) awful.tag.viewprev(t.screen) end), + } } -- Create a tasklist widget s.mytasklist = awful.widget.tasklist { screen = s, filter = awful.widget.tasklist.filter.currenttags, - buttons = tasklist_buttons + buttons = { + awful.button({ }, 1, function (c) + if c == client.focus then + c.minimized = true + else + c:emit_signal( + "request::activate", + "tasklist", + {raise = true} + ) + end + end), + awful.button({ }, 3, function() awful.menu.client_list { theme = { width = 250 } } end), + awful.button({ }, 4, function() awful.client.focus.byidx( 1) end), + awful.button({ }, 5, function() awful.client.focus.byidx(-1) end), + } } -- @DOC_WIBAR@ @@ -332,125 +320,128 @@ awful.keyboard.append_global_keybindings({ {description = "select previous", group = "layout"}), }) --- @DOC_CLIENT_KEYBINDINGS@ -clientkeys = { - awful.key({ modkey, }, "f", - function (c) - c.fullscreen = not c.fullscreen - c:raise() - end, - {description = "toggle fullscreen", group = "client"}), - awful.key({ modkey, "Shift" }, "c", function (c) c:kill() end, - {description = "close", group = "client"}), - awful.key({ modkey, "Control" }, "space", awful.client.floating.toggle , - {description = "toggle floating", group = "client"}), - awful.key({ modkey, "Control" }, "Return", function (c) c:swap(awful.client.getmaster()) end, - {description = "move to master", group = "client"}), - awful.key({ modkey, }, "o", function (c) c:move_to_screen() end, - {description = "move to screen", group = "client"}), - awful.key({ modkey, }, "t", function (c) c.ontop = not c.ontop end, - {description = "toggle keep on top", group = "client"}), - awful.key({ modkey, }, "n", - function (c) - -- The client currently has the input focus, so it cannot be - -- minimized, since minimized clients can't have the focus. - c.minimized = true - end , - {description = "minimize", group = "client"}), - awful.key({ modkey, }, "m", - function (c) - c.maximized = not c.maximized - c:raise() - end , - {description = "(un)maximize", group = "client"}), - awful.key({ modkey, "Control" }, "m", - function (c) - c.maximized_vertical = not c.maximized_vertical - c:raise() - end , - {description = "(un)maximize vertically", group = "client"}), - awful.key({ modkey, "Shift" }, "m", - function (c) - c.maximized_horizontal = not c.maximized_horizontal - c:raise() - end , - {description = "(un)maximize horizontally", group = "client"}), -} - -- @DOC_NUMBER_KEYBINDINGS@ --- Bind all key numbers to tags. --- Be careful: we use keycodes to make it work on any keyboard layout. --- This should map on the top row of your keyboard, usually 1 to 9. -for i = 1, 9 do - -- View tag only. - awful.keyboard.append_global_keybinding(awful.key( - { modkey }, "#" .. i + 9, - function () + +awful.keyboard.append_global_keybindings({ + awful.key { + modifiers = { modkey }, + keygroup = "numrow", + description = "only view tag", + group = "tag", + on_press = function (index) local screen = awful.screen.focused() - local tag = screen.tags[i] + local tag = screen.tags[index] if tag then tag:view_only() end end, - {description = "view tag #"..i, group = "tag"} - )) - - -- Toggle tag display. - awful.keyboard.append_global_keybinding(awful.key( - { modkey, "Control" }, "#" .. i + 9, - function () + }, + awful.key { + modifiers = { modkey, "Control" }, + keygroup = "numrow", + description = "toggle tag", + group = "tag", + on_press = function (index) local screen = awful.screen.focused() - local tag = screen.tags[i] + local tag = screen.tags[index] if tag then awful.tag.viewtoggle(tag) end end, - {description = "toggle tag #" .. i, group = "tag"} - )) - - -- Move client to tag. - awful.keyboard.append_global_keybinding(awful.key( - { modkey, "Shift" }, "#" .. i + 9, - function () + }, + awful.key { + modifiers = { modkey, "Shift" }, + keygroup = "numrow", + description = "move focused client to tag", + group = "tag", + on_press = function (index) if client.focus then - local tag = client.focus.screen.tags[i] + local tag = client.focus.screen.tags[index] if tag then client.focus:move_to_tag(tag) end end end, - {description = "move focused client to tag #"..i, group = "tag"} - )) - - -- Toggle tag on focused client. - awful.keyboard.append_global_keybinding(awful.key( - { modkey, "Control", "Shift" }, "#" .. i + 9, - function () + }, + awful.key { + modifiers = { modkey, "Control", "Shift" }, + keygroup = "numrow", + description = "toggle focused client on tag", + group = "tag", + on_press = function (index) if client.focus then - local tag = client.focus.screen.tags[i] + local tag = client.focus.screen.tags[index] if tag then client.focus:toggle_tag(tag) end end end, - {description = "toggle focused client on tag #" .. i, group = "tag"} - )) -end + } +}) -- @DOC_CLIENT_BUTTONS@ -clientbuttons = { - awful.button({ }, 1, function (c) - c:emit_signal("request::activate", "mouse_click", {raise = true}) - end), - awful.button({ modkey }, 1, function (c) - c:emit_signal("request::activate", "mouse_click", {raise = true}) - awful.mouse.client.move(c) - end), - awful.button({ modkey }, 3, function (c) - c:emit_signal("request::activate", "mouse_click", {raise = true}) - awful.mouse.client.resize(c) - end), -} +client.connect_signal("request::default_mousebindings", function() + awful.mouse.append_client_mousebindings({ + awful.button({ }, 1, function (c) + c:emit_signal("request::activate", "mouse_click", {raise = true}) + end), + awful.button({ modkey }, 1, function (c) + c:emit_signal("request::activate", "mouse_click", {raise = true}) + awful.mouse.client.move(c) + end), + awful.button({ modkey }, 3, function (c) + c:emit_signal("request::activate", "mouse_click", {raise = true}) + awful.mouse.client.resize(c) + end), + }) +end) + +-- @DOC_CLIENT_KEYBINDINGS@ +client.connect_signal("request::default_keybindings", function() + awful.keyboard.append_client_keybindings({ + awful.key({ modkey, }, "f", + function (c) + c.fullscreen = not c.fullscreen + c:raise() + end, + {description = "toggle fullscreen", group = "client"}), + awful.key({ modkey, "Shift" }, "c", function (c) c:kill() end, + {description = "close", group = "client"}), + awful.key({ modkey, "Control" }, "space", awful.client.floating.toggle , + {description = "toggle floating", group = "client"}), + awful.key({ modkey, "Control" }, "Return", function (c) c:swap(awful.client.getmaster()) end, + {description = "move to master", group = "client"}), + awful.key({ modkey, }, "o", function (c) c:move_to_screen() end, + {description = "move to screen", group = "client"}), + awful.key({ modkey, }, "t", function (c) c.ontop = not c.ontop end, + {description = "toggle keep on top", group = "client"}), + awful.key({ modkey, }, "n", + function (c) + -- The client currently has the input focus, so it cannot be + -- minimized, since minimized clients can't have the focus. + c.minimized = true + end , + {description = "minimize", group = "client"}), + awful.key({ modkey, }, "m", + function (c) + c.maximized = not c.maximized + c:raise() + end , + {description = "(un)maximize", group = "client"}), + awful.key({ modkey, "Control" }, "m", + function (c) + c.maximized_vertical = not c.maximized_vertical + c:raise() + end , + {description = "(un)maximize vertically", group = "client"}), + awful.key({ modkey, "Shift" }, "m", + function (c) + c.maximized_horizontal = not c.maximized_horizontal + c:raise() + end , + {description = "(un)maximize horizontally", group = "client"}), + }) +end) -- }}} @@ -465,8 +456,6 @@ awful.rules.rules = { border_color = beautiful.border_normal, focus = awful.client.focus.filter, raise = true, - keys = clientkeys, - buttons = clientbuttons, screen = awful.screen.preferred, placement = awful.placement.no_overlap+awful.placement.no_offscreen } diff --git a/docs/89-NEWS.md b/docs/89-NEWS.md index 92072519..73595bf7 100644 --- a/docs/89-NEWS.md +++ b/docs/89-NEWS.md @@ -57,6 +57,11 @@ This document was last updated at commit v4.3-197-g9085ed631. * `naughty.dbus` now uses Gio for talking to DBus. This is a first step in the deprecation of Awesome's own DBus bindings and could lead to behaviour changes on DBus. +* The client `keys` and `buttons` property now return `awful.key` + and `awful.buttons` objects rather than the lower level `key` and `button` + objects. If you used these low level APIs to add keys and buttons dynamically, + please migrate your code to the corresponding `:append_` and `:remove_` + client methods. # Awesome window manager framework version 4.3 changes diff --git a/lib/awful/client.lua b/lib/awful/client.lua index 63073326..2a7c978d 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -365,6 +365,29 @@ function client.cycle(clockwise, s, stacked) end end +--- Append a keybinding. +-- +-- @method append_keybinding +-- @tparam awful.key key The key. +-- @see remove_keybinding +-- @see append_mousebinding +-- @see remove_mousebinding + +--- Remove a keybinding. +-- +-- @method remove_keybinding +-- @tparam awful.key key The key. + +--- Append a mousebinding. +-- +-- @method append_mousebinding +-- @tparam awful.button button The button. + +--- Remove a mousebinding. +-- +-- @method remove_mousebinding +-- @tparam awful.button button The button. + --- Get the master window. -- -- @legacylayout awful.client.getmaster @@ -1348,13 +1371,13 @@ object.properties._legacy_accessors(client, "buttons", "_buttons", true, functio return new_btns[1] and ( type(new_btns[1]) == "button" or new_btns[1]._is_capi_button ) or false -end, true) +end, true, true, "mousebinding") object.properties._legacy_accessors(client, "keys", "_keys", true, function(new_btns) return new_btns[1] and ( type(new_btns[1]) == "key" or new_btns[1]._is_capi_key ) or false -end, true) +end, true, true, "keybinding") --- Set the client shape. -- @property shape diff --git a/lib/awful/hotkeys_popup/widget.lua b/lib/awful/hotkeys_popup/widget.lua index 6b4c74f0..b0ab3b76 100644 --- a/lib/awful/hotkeys_popup/widget.lua +++ b/lib/awful/hotkeys_popup/widget.lua @@ -274,7 +274,9 @@ function widget.new(args) end table.sort( sorted_table, - function(a,b) return (a.mod or '')..a.key<(b.mod or '')..b.key end + function(a,b) + local k1, k2 = a.key or a.keys[1][1], b.key or b.keys[1][1] + return (a.mod or '')..k1<(b.mod or '')..k2 end ) target[group] = sorted_table end @@ -287,7 +289,9 @@ function widget.new(args) return end for _, data in pairs(awful.key.hotkeys) do - self:_add_hotkey(data.key, data, self._cached_awful_keys) + for _, key_pair in ipairs(data.keys) do + self:_add_hotkey(key_pair[1], data, self._cached_awful_keys) + end end self:_sort_hotkeys(self._cached_awful_keys) end diff --git a/lib/awful/key.lua b/lib/awful/key.lua index 6d8b2dcc..864dcf49 100644 --- a/lib/awful/key.lua +++ b/lib/awful/key.lua @@ -24,6 +24,17 @@ local gobject = require("gears.object") -- @property key -- @param string +--- A group of keys. +-- +-- The valid keygroups are: +-- +-- * **numrow**: The row above the letters in the US PC-105/PC-104 keyboards +-- and its derivative. This is usually the number 1-9 followed by 0. +-- * **arrows**: The Left/Right/Top/Bottom keys usually located right of the +-- spacebar. +-- +-- @property keygroup + --- The table of modifier keys. -- -- A modifier, such as `Control` are a predetermined set of keys that can be @@ -222,7 +233,7 @@ end -- @treturn table A table with one or several key objects. -- @constructorfct awful.key -local function new_common(mod, _key, press, release, data) +local function new_common(mod, _keys, press, release, data) if type(release)=='table' then data=release release=nil @@ -230,38 +241,52 @@ local function new_common(mod, _key, press, release, data) local ret = {} local subsets = gmath.subsets(key.ignore_modifiers) - for _, set in ipairs(subsets) do - local sub_key = capi.key { - modifiers = gtable.join(mod, set), - key = _key - } + for _, key_pair in ipairs(_keys) do + for _, set in ipairs(subsets) do + local sub_key = capi.key { + modifiers = gtable.join(mod, set), + key = key_pair[1] + } - sub_key._private._legacy_convert_to = ret + sub_key._private._legacy_convert_to = ret - sub_key:connect_signal("press", function(_, ...) - if ret.on_press then - ret.on_press(...) - end - end) + sub_key:connect_signal("press", function(_, ...) + if ret.on_press then + if key_pair[2] ~= nil then + ret.on_press(key_pair[2], ...) + else + ret.on_press(...) + end + end + end) - sub_key:connect_signal("release", function(_, ...) - if ret.on_release then - ret.on_release(...) - end - end) + sub_key:connect_signal("release", function(_, ...) + if ret.on_release then + if key_pair[2] ~= nil then + ret.on_release(key_pair[2], ...) + else + ret.on_release(...) + end + end + end) - ret[#ret + 1] = sub_key + ret[#ret + 1] = sub_key + end end -- append custom userdata (like description) to a hotkey data = data and gtable.clone(data) or {} data.mod = mod - data.key = _key + data.keys = _keys data.on_press = press data.on_release = release data._is_capi_key = false + assert((not data.key) or type(data.key) == "string") table.insert(key.hotkeys, data) - data.execute = function(_) key.execute(mod, _key) end + data.execute = function(_) + assert(#_keys == 1, "key:execute() makes no sense for groups") + key.execute(mod, _keys[1]) + end -- Store the private data reverse_map[ret] = data @@ -273,19 +298,54 @@ local function new_common(mod, _key, press, release, data) return setmetatable(ret, obj_mt) end +local keygroups = { + numrow = {}, + arrows = { + {"Left" , "Left" }, + {"Right" , "Right" }, + {"Top" , "Top" }, + {"Bottom", "Bottom"}, + } +} + +-- Technically, this isn't very popular, but we have been doing this for 12 +-- years and nobody complained too loudly. +for i = 1, 10 do + table.insert(keygroups.numrow, {"#" .. i + 9, i == 10 and 0 or i}) +end + +-- Allow key objects to provide more than 1 key. +-- +-- Some "groups" like arrows, the numpad, F-keys or numrow "belong together" +local function get_keys(args) + if not args.keygroup then return {{args.key}} end + + -- Make sure nothing weird happens. + assert( + not args.key, + "Please provide either the `key` or `keygroup` property, not both" + ) + + assert(keygroups[args.keygroup], "Please provide a valid keygroup") + return keygroups[args.keygroup] +end + function key.new(args, _key, press, release, data) -- Assume this is the new constructor. if not _key then assert(not (press or release or data), "Calling awful.key() requires a key name") + + local keys = get_keys(args) + return new_common( args.modifiers, - args.key, + keys, args.on_press, args.on_release, args ) else - return new_common(args, _key, press, release, data) + return new_common(args, {{_key}}, press, release, data) end end diff --git a/lib/awful/keyboard.lua b/lib/awful/keyboard.lua index da549c8b..34792723 100644 --- a/lib/awful/keyboard.lua +++ b/lib/awful/keyboard.lua @@ -6,7 +6,7 @@ -- @inputmodule awful.keyboard --------------------------------------------------------------------------- -local capi = {root = root, awesome = awesome} +local capi = {root = root, awesome = awesome, client = client} local module = {} --- Convert the modifiers into pc105 key names @@ -138,4 +138,72 @@ function module.remove_global_keybinding(key) capi.root._remove_key(key) end + +local default_keys = {} + +--- Add an `awful.key` to the default client keys. +-- +-- @staticfct awful.keyboard.append_client_keybinding +-- @tparam awful.key key The key. +-- @emits client_keybinding::added +-- @emitstparam client_keybinding::added awful.key key The key. +-- @see awful.key +-- @see awful.keyboard.append_client_keybindings + +function module.append_client_keybinding(key) + table.insert(default_keys, key) + + for _, c in ipairs(capi.client.get(nil, false)) do + c:append_keybinding(key) + end + + capi.client.emit_signal("client_keybinding::added", key) +end + +--- Add a `awful.key`s to the default client keys. +-- +-- @staticfct awful.keyboard.append_client_keybindings +-- @tparam table keys A table containing `awful.key` objects. +-- @emits client_keybinding::added +-- @emitstparam client_keybinding::added awful.key key The key. +-- @see awful.key +-- @see awful.keyboard.append_client_keybinding +function module.append_client_keybindings(keys) + for _, key in ipairs(keys) do + module.append_client_keybinding(key) + end +end + +--- Remove a key from the default client keys. +-- +-- @staticfct awful.keyboard.remove_client_keybinding +-- @tparam awful.key key The key. +-- @treturn boolean True if the key was removed and false if it wasn't found. +-- @see awful.keyboard.append_client_keybinding + +function module.remove_client_keybinding(key) + for k, v in ipairs(default_keys) do + if key == v then + table.remove(default_keys, k) + + for _, c in ipairs(capi.client.get(nil, false)) do + c:remove_keybinding(key) + end + + return true + end + end + + return false +end + +capi.client.connect_signal("scanning", function() + capi.client.emit_signal("request::default_keybindings", "context") +end) + +-- Private function to be used by `ruled.client`. +function module._get_client_keybindings() + return default_keys +end + return module diff --git a/lib/awful/mouse/init.lua b/lib/awful/mouse/init.lua index 5e768fe7..0d4554a6 100644 --- a/lib/awful/mouse/init.lua +++ b/lib/awful/mouse/init.lua @@ -421,6 +421,65 @@ function mouse.remove_global_mousebinding(button) capi.root._remove_button(button) end +local default_buttons = {} + +--- Add an `awful.button` to the default client buttons. +-- +-- @staticfct awful.mouse.append_client_mousebinding +-- @tparam awful.button button The button. +-- @emits client_mousebinding::added +-- @emitstparam client_mousebinding::added awful.button button The button. +-- @see awful.button +-- @see awful.keyboard.append_client_keybinding + +function mouse.append_client_mousebinding(button) + table.insert(default_buttons, button) + + for _, c in ipairs(capi.client.get(nil, false)) do + c:append_mousebinding(button) + end + + capi.client.emit_signal("client_mousebinding::added", button) +end + +--- Add a `awful.button`s to the default client buttons. +-- +-- @staticfct awful.mouse.append_client_mousebindings +-- @tparam table buttons A table containing `awful.button` objects. +-- @see awful.button +-- @see awful.keyboard.append_client_keybinding +-- @see awful.mouse.append_client_mousebinding +-- @see awful.keyboard.append_client_keybindings + +function mouse.append_client_mousebindings(buttons) + for _, button in ipairs(buttons) do + mouse.append_client_mousebinding(button) + end +end + +--- Remove a mousebinding from the default client buttons. +-- +-- @staticfct awful.mouse.remove_client_mousebinding +-- @tparam awful.button button The button. +-- @treturn boolean True if the button was removed and false if it wasn't found. +-- @see awful.keyboard.append_client_keybinding + +function mouse.remove_client_mousebinding(button) + for k, v in ipairs(default_buttons) do + if button == v then + table.remove(default_buttons, k) + + for _, c in ipairs(capi.client.get(nil, false)) do + c:remove_mousebinding(button) + end + + return true + end + end + + return false +end + for _, b in ipairs {"left", "right", "middle"} do mouse.object["is_".. b .."_mouse_button_pressed"] = function() return capi.mouse.coords().buttons[1] @@ -470,6 +529,14 @@ end) -- when button 1 is pressed. -- @staticfct mouse.coords +capi.client.connect_signal("scanning", function() + capi.client.emit_signal("request::default_mousebindings", "startup") +end) + +-- Private function to be used by `ruled.client`. +function mouse._get_client_mousebindings() + return default_buttons +end return mouse diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index 4b1e86bb..9f4a6c40 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -121,6 +121,8 @@ local protected_call = require("gears.protected_call") local aspawn = require("awful.spawn") local gdebug = require("gears.debug") local gmatcher = require("gears.matcher") +local amouse = require("awful.mouse") +local akeyboard = require("awful.keyboard") local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1) local rules = {} @@ -529,6 +531,13 @@ end -- @staticfct awful.rules.execute crules._execute = function(_, c, props, callbacks) + + -- Set the default buttons and keys + local btns = amouse._get_client_mousebindings() + local keys = akeyboard._get_client_keybindings() + props.keys = props.keys or keys + props.buttons = props.buttons or btns + -- This has to be done first, as it will impact geometry related props. if props.titlebars_enabled and (type(props.titlebars_enabled) ~= "function" or props.titlebars_enabled(c,props)) then diff --git a/lib/gears/object/properties.lua b/lib/gears/object/properties.lua index f671d98a..d1ab5a22 100644 --- a/lib/gears/object/properties.lua +++ b/lib/gears/object/properties.lua @@ -8,6 +8,7 @@ --------------------------------------------------------------------------- local gtable = require("gears.table") +local gtimer = nil --require("gears.timer") -- local gdebug = require("gears.debug") local object = {} local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1) @@ -83,6 +84,8 @@ function object.capi_index_fallback(class, args) end end + assert(type(class) ~= "function") + -- Attach the accessor methods class.set_index_miss_handler(getter) class.set_newindex_miss_handler(setter) @@ -132,7 +135,7 @@ end -- -- TO BE USED FOR DEPRECATION ONLY. -local function copy_object(obj, to_set, name, capi_name, is_object, join_if, set_empty) +local function copy_object(obj, to_set, name, capi_name, is_object, join_if, set_empty)-- luacheck: no unused local ret = gtable.clone(to_set, false) -- .buttons used to be a function taking the result of `gears.table.join`. @@ -145,12 +148,14 @@ local function copy_object(obj, to_set, name, capi_name, is_object, join_if, set -- {deprecated_in=5} -- ) - new_objs, self = is_object and new_objs or self, is_object and self or obj + if not is_object then + new_objs, self = self, obj + end + + local has_content = new_objs and next(new_objs) -- Setter - if new_objs and #new_objs > 0 then - if (not set_empty) and not next(new_objs) then return end - + if new_objs and has_content then local is_formatted = join_if(new_objs) -- Because modules may rely on :buttons taking a list of @@ -190,7 +195,8 @@ local function copy_object(obj, to_set, name, capi_name, is_object, join_if, set }) end -function object._legacy_accessors(obj, name, capi_name, is_object, join_if, set_empty) +function object._legacy_accessors(obj, name, capi_name, is_object, join_if, set_empty, delay, add_append_name) + delay = delay or add_append_name -- Some objects have a special "object" property to add more properties, but -- not all. @@ -248,21 +254,64 @@ function object._legacy_accessors(obj, name, capi_name, is_object, join_if, set_ --TODO v6 Use the original directly and drop this legacy copy - local result = is_formatted and objs - or gtable.join(unpack(objs)) + local function apply() + local result = is_formatted and objs + or gtable.join(unpack(objs)) - if is_object and capi_name then - self[capi_name](self, result) - elseif capi_name then - obj[capi_name](result) + if is_object and capi_name then + self[capi_name](self, result) + elseif capi_name then + obj[capi_name](result) + else + self._private[name.."_formatted"] = result + end + end + + -- Some properties, like keys, are expensive to set, schedule them + -- instead. + if not delay then + apply() else - self._private[name.."_formatted"] = result + if not self._private["_delayed_"..name] then + gtimer = gtimer or require("gears.timer") + gtimer.delayed_call(function() + self._private["_delayed_"..name]() + self._private["_delayed_"..name] = nil + end) + end + + self._private["_delayed_"..name] = apply end self._private[name] = copy_object( obj, objs, name, capi_name, is_object, join_if, set_empty ) end + + if add_append_name then + magic_obj["append_"..add_append_name] = function(self, obj2) + self._private[name] = self._private[name] + or magic_obj["get_"..name](self, nil) + + table.insert(self._private[name], obj2) + + magic_obj["set_"..name](self, self._private[name]) + end + + magic_obj["remove_"..add_append_name] = function(self, obj2) + self._private[name] = self._private[name] + or magic_obj["get_"..name](self, nil) + + for k, v in ipairs(self._private[name]) do + if v == obj2 then + table.remove(self._private[name], k) + break + end + end + + magic_obj["set_"..name](self, self._private[name]) + end + end end return setmetatable( object, {__call = function(_,...) object.capi_index_fallback(...) end}) diff --git a/objects/client.c b/objects/client.c index e9b47a15..150bc0bf 100644 --- a/objects/client.c +++ b/objects/client.c @@ -244,6 +244,28 @@ * @signal request::urgent */ +/** Emitted during startup to gather the default client mousebindings. + * + * This signals gives a chance to all module to register new client keybindings. + * Assuming the client rules does not overwrite them with the `keys` property, + * they will be added to all clients. + * + * @signal request::default_mousebindings + * @tparam string context The reason why the signal was sent (currently always + * `startup`). +*/ + +/** Emitted during startup to gather the default client keybindings. + * + * This signals gives a chance to all module to register new client keybindings. + * Assuming the client rules does not overwrite them with the `keys` property, + * they will be added to all clients. + * + * @signal request::default_keybindings + * @tparam string context The reason why the signal was sent (currently always + * `startup`). + */ + /** When a client gets tagged. * @signal tagged * @tag t The tag object. diff --git a/spec/awful/prompt_spec.lua b/spec/awful/prompt_spec.lua index 5d27570b..2c0f6522 100644 --- a/spec/awful/prompt_spec.lua +++ b/spec/awful/prompt_spec.lua @@ -57,6 +57,9 @@ insulate('main', function () run = function() end, stop = function() end, } + _G.client = { + connect_signal = function() end + } -- luacheck: globals string function string.wlen(self) return #self diff --git a/tests/test-awesomerc.lua b/tests/test-awesomerc.lua index 1d7ba4f3..07326e52 100644 --- a/tests/test-awesomerc.lua +++ b/tests/test-awesomerc.lua @@ -228,6 +228,25 @@ local steps = { return true end, + -- Add more keybindings for make sure the popup has many pages. + function() + for j=1, 10 do + for i=1, 200 do + awful.keyboard.append_global_keybinding( + awful.key { + key = "#"..(300+i), + modifiers = {}, + on_press = function() end, + description = "Fake "..i, + group = "Fake"..j, + } + ) + end + end + + return true + end, + -- Hotkeys popup should be displayed and hidden function(count) local s = awful.screen.focused() diff --git a/tests/test-input-binding.lua b/tests/test-input-binding.lua index 75cde936..25e2c007 100644 --- a/tests/test-input-binding.lua +++ b/tests/test-input-binding.lua @@ -4,6 +4,8 @@ local runner = require( "_runner" ) local placement = require( "awful.placement" ) local gtable = require( "gears.table" ) local test_client = require( "_client" ) +local akeyboard = require( "awful.keyboard" ) +local amouse = require( "awful.mouse" ) local module = { key = require( "awful.key" ), @@ -249,6 +251,127 @@ for _, type_name in ipairs { "key", "button" } do end end +local exe1, exe2, exe3, exe4 = false, false, false, false +local new1, new2 = nil, nil + +-- Check that you can add new default key/mousebindings at any time. +table.insert(steps, function() + assert(#mouse.screen.clients == 0) + + new1 = module.key { + key = "a", + modifiers = {}, + on_press = function() exe1 = true end + } + + new2 = module.button { + button = 8, + modifiers = {}, + on_press = function() exe2 = true end + } + + akeyboard.append_client_keybinding(new1) + amouse.append_client_mousebinding(new2) + + -- Spawn a client. + test_client("myclient") + + return true +end) + +table.insert(steps, function() + if #mouse.screen.clients == 0 then return end + + client.focus = mouse.screen.clients[1] + + -- That should trigger the newly added keybinding. + root.fake_input("key_press" , "a") + root.fake_input("key_release", "a") + + -- Move the mouse over the client so mousebindings work. + placement.centered(mouse, {parent=client.focus}) + awesome.sync() + + -- Same thing for the mouse. + root.fake_input("button_press" , 8) + root.fake_input("button_release", 8) + + return true +end) + +table.insert(steps, function() + if (not exe1) or (not exe2) then return end + + akeyboard.remove_client_keybinding(new1) + amouse.remove_client_mousebinding(new2) + + exe1, exe2 = false, false + + return true +end) + +-- Removing is async, so wait until the next loop. +table.insert(steps, function() + root.fake_input("key_press" , "a") + root.fake_input("key_release", "a") + root.fake_input("button_press" , 8) + root.fake_input("button_release", 8) + awesome.sync() + + return true +end) + +-- Try adding key/mousebindings to existing clients. +table.insert(steps, function(count) + if count ~= 3 then return end + + assert(not exe1) + assert(not exe2) + + -- Append both types at the same time to make sure nothing overwrite + -- the list. + akeyboard.append_client_keybinding(new1) + amouse.append_client_mousebinding(new2) + + new1 = module.key { + key = "b", + modifiers = {}, + on_press = function() exe3 = true end + } + + new2 = module.button { + button = 7, + modifiers = {}, + on_press = function() exe4 = true end + } + + -- Append directly on the client. + client.focus:append_keybinding(new1) + client.focus:append_mousebinding(new2) + + return true +end) + +-- Removing is async, so wait until the next loop. +table.insert(steps, function() + + root.fake_input("key_press" , "b") + root.fake_input("key_release", "b") + root.fake_input("button_press" , 7) + root.fake_input("button_release", 7) + + return true +end) + +table.insert(steps, function() + if (not exe3) or (not exe4) then return end + + -- Note, there is no point to try remove_keybinding, it was already called + -- (indirectly) 4 steps ago. + + return true +end) + runner.run_steps(steps) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80