diff --git a/lib/awful/key.lua b/lib/awful/key.lua index 87d27ab3f..ee4b5148a 100644 --- a/lib/awful/key.lua +++ b/lib/awful/key.lua @@ -9,15 +9,12 @@ -- Grab environment we need local setmetatable = setmetatable local ipairs = ipairs -local capi = { key = key, root = root } +local capi = { key = key, root = root, awesome = awesome } local gmath = require("gears.math") local gtable = require("gears.table") - - local key = { mt = {}, hotkeys = {} } - --- Modifiers to ignore. -- By default this is initialized as { "Lock", "Mod2" } -- so the Caps Lock or Num Lock modifier are not taking into account by awesome @@ -27,23 +24,53 @@ local key = { mt = {}, hotkeys = {} } key.ignore_modifiers = { "Lock", "Mod2" } --- Convert the modifiers into pc105 key names -local conversion = { - mod4 = "Super_L", - control = "Control_L", - shift = "Shift_L", - mod1 = "Alt_L", -} +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 mod A modified table. Valid modifiers are: Any, Mod1, -- Mod2, Mod3, Mod4, Mod5, Shift, Lock and Control. -- @tparam string k The key function key.execute(mod, k) + local modmap = generate_conversion_map() + local active = capi.awesome._active_modifiers + + -- 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 = conversion[v:lower()] + local m = modmap[v] if m then root.fake_input("key_press", m) end @@ -53,11 +80,18 @@ function key.execute(mod, k) root.fake_input("key_release", k) for _, v in ipairs(mod) do - local m = conversion[v:lower()] + 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 end --- Create a new key to use as binding. diff --git a/lib/awful/keygrabber.lua b/lib/awful/keygrabber.lua index 499e25a06..9b41f8de1 100644 --- a/lib/awful/keygrabber.lua +++ b/lib/awful/keygrabber.lua @@ -72,7 +72,7 @@ local gtable = require("gears.table") local gobject = require("gears.object") local gtimer = require("gears.timer") local glib = require("lgi").GLib -local capi = { keygrabber = keygrabber, root = root } +local capi = { keygrabber = keygrabber, root = root, awesome = awesome } local keygrab = {} @@ -85,17 +85,7 @@ local keygrabber = { } -- Instead of checking for every modifiers, check the key directly. ---FIXME This is slightly broken but still good enough for `mask_modkeys` -local conversion = { - Super_L = "Mod4", - Control_L = "Control", - Shift_L = "Shift", - Alt_L = "Mod1", - Super_R = "Mod4", - Control_R = "Control", - Shift_R = "Shift", - Alt_R = "Mod1", -} +local conversion = nil --BEGIN one day create a proper API to add and remove keybindings at runtime. -- Doing it this way is horrible. @@ -103,6 +93,27 @@ local conversion = { -- 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 + + 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[keysym.keysym] = mod + end + end + + return conversion +end + +capi.awesome.connect_signal("xkb::map_changed" , function() conversion = nil end) + local function add_root_keybindings(self, list) assert( list, "`add_root_keybindings` needs to be called with a list of keybindings" @@ -168,9 +179,11 @@ local function grabber(mod, key, event) end local function runner(self, modifiers, key, event) + local converted = generate_conversion_map()[key] + -- Stop the keygrabber with the `stop_key` - if key == self.stop_key - and event == self.stop_event and self.stop_key then + if (key == self.stop_key or (converted and converted == self.stop_key)) + and event == self.stop_event and self.stop_key then self:stop(key, modifiers) return false end @@ -191,7 +204,7 @@ local function runner(self, modifiers, key, event) end end - local is_modifier = conversion[key] ~= nil + local is_modifier = converted ~= nil -- Reset the inactivity timer on each events. if self._private.timer and self._private.timer.started then diff --git a/luaa.c b/luaa.c index 63ad9ed1c..bfe8eee39 100644 --- a/luaa.c +++ b/luaa.c @@ -388,6 +388,151 @@ luaA_fixups(lua_State *L) lua_setglobal(L, "selection"); } +static const char * +get_modifier_name(int map_index) +{ + switch (map_index) { + case XCB_MAP_INDEX_SHIFT: return "Shift"; + case XCB_MAP_INDEX_LOCK: return "Lock"; + case XCB_MAP_INDEX_CONTROL: return "Control"; + case XCB_MAP_INDEX_1: return "Mod1"; /* Alt */ + case XCB_MAP_INDEX_2: return "Mod2"; + case XCB_MAP_INDEX_3: return "Mod3"; + case XCB_MAP_INDEX_4: return "Mod4"; + case XCB_MAP_INDEX_5: return "Mod5"; + + } + + return 0; /* \0 */ +} + +/* Undocumented */ +/* + * The table of keybindings modifiers. + * + * Each modifier has zero to many entries depending on the keyboard layout. + * For example, `Shift` usually both has a left and right variant. Each + * modifier entry has a `keysym` and `keycode` entry. For the US PC 105 + * keyboard, it looks like: + * + * awesome.modifiers = { + * Shift = { + * {keycode = 50 , keysym = 'Shift_L' }, + * {keycode = 62 , keysym = 'Shift_R' }, + * }, + * Lock = {}, + * Control = { + * {keycode = 37 , keysym = 'Control_L' }, + * {keycode = 105, keysym = 'Control_R' }, + * }, + * Mod1 = { + * {keycode = 64 , keysym = 'Alt_L' }, + * {keycode = 108, keysym = 'Alt_R' }, + * }, + * Mod2 = { + * {keycode = 77 , keysym = 'Num_Lock' }, + * }, + * Mod3 = {}, + * Mod4 = { + * {keycode = 133, keysym = 'Super_L' }, + * {keycode = 134, keysym = 'Super_R' }, + * }, + * Mod5 = { + * {keycode = 203, keysym = 'Mode_switch'}, + * }, + * }; + * + * @tfield table modifiers + * @tparam table modifiers.Shift The Shift modifiers. + * @tparam table modifiers.Lock The Lock modifiers. + * @tparam table modifiers.Control The Control modifiers. + * @tparam table modifiers.Mod1 The Mod1 (Alt) modifiers. + * @tparam table modifiers.Mod2 The Mod2 modifiers. + * @tparam table modifiers.Mod3 The Mod3 modifiers. + * @tparam table modifiers.Mod4 The Mod4 modifiers. + * @tparam table modifiers.Mod5 The Mod5 modifiers. + */ + +/* + * Modifiers can change over time, given they are currently not tracked, just + * query them each time. Use with moderation. + */ +static int luaA_get_modifiers(lua_State *L) +{ + xcb_get_modifier_mapping_reply_t *mods = xcb_get_modifier_mapping_reply(globalconf.connection, + xcb_get_modifier_mapping(globalconf.connection), NULL); + if (!mods) + return 0; + + xcb_keycode_t *mappings = xcb_get_modifier_mapping_keycodes(mods); + struct xkb_keymap *keymap = xkb_state_get_keymap(globalconf.xkb_state); + + lua_newtable(L); + + /* This get the MAPPED modifiers, not all of them are */ + for (int i = XCB_MAP_INDEX_SHIFT; i <= XCB_MAP_INDEX_5; i++) { + lua_pushstring(L, get_modifier_name(i)); + lua_newtable(L); + + for (int j = 0; j < mods->keycodes_per_modifier; j++) { + const xkb_keysym_t *keysyms; + const xcb_keycode_t key_code = mappings[i * mods->keycodes_per_modifier + j]; + xkb_keymap_key_get_syms_by_level(keymap, key_code, 0, 0, &keysyms); + if (keysyms != NULL) { + /* The +1 because j starts at zero and Lua at 1 */ + lua_pushinteger(L, j+1); + + lua_newtable(L); + + lua_pushstring(L, "keycode"); + lua_pushinteger(L, key_code); + lua_settable(L, -3); + + /* Technically it is possible to get multiple keysyms here, + * but... we just use the first one. + */ + lua_pushstring(L, "keysym"); + char *string = key_get_keysym_name(keysyms[0]); + lua_pushstring(L, string); + p_delete(&string); + lua_settable(L, -3); + + lua_settable(L, -3); + } + } + lua_settable(L, -3); + } + + free(mods); + + return 0; +} + +/* Undocumented */ +/* + * A table with the currently active modifier names. + * + * @tfield table _active_modifiers + */ + +static int luaA_get_active_modifiers(lua_State *L) +{ + int count = 1; + lua_newtable(L); + + for (int i = XCB_MAP_INDEX_SHIFT; i <= XCB_MAP_INDEX_5; i++) { + const int active = xkb_state_mod_index_is_active (globalconf.xkb_state, i, + XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_EFFECTIVE + ); + + if (active) { + lua_pushstring(L, get_modifier_name(i)); + lua_rawseti(L,-2, count++); + } + } + + return 0; +} /** * The version of awesome. @@ -472,6 +617,18 @@ luaA_awesome_index(lua_State *L) return 1; } + if(A_STREQ(buf, "_modifiers")) + { + luaA_get_modifiers(L); + return 1; + } + + if(A_STREQ(buf, "_active_modifiers")) + { + luaA_get_active_modifiers(L); + return 1; + } + if(A_STREQ(buf, "startup_errors")) { if (globalconf.startup_errors.len == 0) diff --git a/objects/key.c b/objects/key.c index 94f73ebad..932921ca2 100644 --- a/objects/key.c +++ b/objects/key.c @@ -275,8 +275,8 @@ luaA_key_set_modifiers(lua_State *L, keyb_t *k) LUA_OBJECT_EXPORT_PROPERTY(key, keyb_t, modifiers, luaA_pushmodifiers) /* It's caller's responsibility to release the returned string. */ -static char * -get_keysym_name(xkb_keysym_t keysym) +char * +key_get_keysym_name(xkb_keysym_t keysym) { const ssize_t bufsize = 64; char *buf = p_new(char, bufsize); @@ -310,7 +310,7 @@ luaA_key_get_key(lua_State *L, keyb_t *k) } else { - char *name = get_keysym_name(k->keysym); + char *name = key_get_keysym_name(k->keysym); if(!name) return 0; lua_pushstring(L, name); @@ -322,7 +322,7 @@ luaA_key_get_key(lua_State *L, keyb_t *k) static int luaA_key_get_keysym(lua_State *L, keyb_t *k) { - char *name = get_keysym_name(k->keysym); + char *name = key_get_keysym_name(k->keysym); if(!name) return 0; lua_pushstring(L, name); diff --git a/objects/key.h b/objects/key.h index f54273679..8aa07ffe3 100644 --- a/objects/key.h +++ b/objects/key.h @@ -23,6 +23,7 @@ #define AWESOME_OBJECTS_KEY_H #include "common/luaobject.h" +#include typedef struct keyb_t { @@ -47,6 +48,8 @@ int luaA_key_array_get(lua_State *, int, key_array_t *); int luaA_pushmodifiers(lua_State *, uint16_t); uint16_t luaA_tomodifiers(lua_State *L, int ud); +char * key_get_keysym_name(xkb_keysym_t keysym); + #endif // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/examples/shims/awesome.lua b/tests/examples/shims/awesome.lua index d162fd06f..4d8f03237 100644 --- a/tests/examples/shims/awesome.lua +++ b/tests/examples/shims/awesome.lua @@ -66,6 +66,35 @@ awesome.version = "v9999" -- SVG are composited. Without it we need a root surface awesome.composite_manager_running = true +awesome._modifiers = { + Shift = { + {keycode = 50 , keysym = 'Shift_L' }, + {keycode = 62 , keysym = 'Shift_R' }, + }, + Lock = {}, + Control = { + {keycode = 37 , keysym = 'Control_L' }, + {keycode = 105, keysym = 'Control_R' }, + }, + Mod1 = { + {keycode = 64 , keysym = 'Alt_L' }, + {keycode = 108, keysym = 'Alt_R' }, + }, + Mod2 = { + {keycode = 77 , keysym = 'Num_Lock' }, + }, + Mod3 = {}, + Mod4 = { + {keycode = 133, keysym = 'Super_L' }, + {keycode = 134, keysym = 'Super_R' }, + }, + Mod5 = { + {keycode = 203, keysym = 'Mode_switch'}, + }, +} + +awesome._active_modifiers = {} + return awesome -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/examples/text/awful/keygrabber/alttab.lua b/tests/examples/text/awful/keygrabber/alttab.lua index 6a255fc69..8b983c643 100644 --- a/tests/examples/text/awful/keygrabber/alttab.lua +++ b/tests/examples/text/awful/keygrabber/alttab.lua @@ -16,7 +16,7 @@ local awful = {keygrabber = require("awful.keygrabber"), --DOC_HIDE {{"Mod1", "Shift"}, "Tab", awful.client.focus.history.select_next }, }, -- Note that it is using the key name and not the modifier name. - stop_key = "Alt_L", + stop_key = "Mod1", stop_event = "release", start_callback = awful.client.focus.history.disable_tracking, stop_callback = awful.client.focus.history.enable_tracking, diff --git a/tests/test-keyboard-layout-changes.lua b/tests/test-keyboard-layout-changes.lua index 2693a877c..ebe6adc6f 100644 --- a/tests/test-keyboard-layout-changes.lua +++ b/tests/test-keyboard-layout-changes.lua @@ -9,6 +9,10 @@ local done local timer = GLib.Timer() local steps = { + function() + assert(awesome._modifiers.Control) + return true + end, function(count) if count == 1 then -- POSIX allows us to use awk @@ -22,7 +26,11 @@ local steps = { -- Apply some limit on how long awesome may need to process 'things' return timer:elapsed() < 5 end - end + end, + function() + assert(awesome._modifiers.Control) + return true + end, } runner.run_steps(steps)