Hello list. I would like you to present an implementation of keyboard layout switcher. First, why bothering to implement layout switcher in awesome? Because current solution(presented here: http://awesome.naquadah.org/wiki/Change_keyboard_maps ) is bad!
Currently for switching keyboard layout exists two ways, either is by using utility "setxkbmap" or by using third party tools such as xkb-switch or xxkb. The first approach works well, however call of external process every time when you are switching layout may be slow (it especially noticeable when you are using per-window layout), also such approach can break behaviour of other map switching tools. Second approach have a lack of configurability and doesn't have a communication with awesome, so you can't use more sophisticated behaviour. Patch in attachment provides three function: //set current group set_xkb_layout_group(layout_number) //get current group get_xkb_layout_group(); //get string with description of active groups get_xkb_group_names(); Also patch contains a changes in default awesome config, which enables keyboard indicator and binds global hotkey "Alt" + "Shift_R" to layout switch. But it also quite easy to implement per-window layout. P.S. I'm a newby in awesome development, so please point me to errors in my patch.
From ad996b81891dbd709a30abda7f44bf53bebde9df Mon Sep 17 00:00:00 2001 From: lexa <l...@cfotr.com> Date: Sat, 24 May 2014 13:37:37 +0400 Subject: [PATCH] xkb: initial implementation of xkb group switcher current implementation allows to get/set current group (language/keyboard layout). It is compatibile with any other keyboard switcher. --- CMakeLists.txt | 1 + awesome.c | 4 ++ awesomeConfig.cmake | 1 + awesomerc.lua.in | 60 ++++++++++++++++++++- event.c | 5 ++ globalconf.h | 2 + xkb.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++ xkb.h | 22 ++++++++ 8 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 xkb.c create mode 100644 xkb.h diff --git a/CMakeLists.txt b/CMakeLists.txt index af22bb8..b416bde 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ set(AWE_SRCS ${SOURCE_DIR}/strut.c ${SOURCE_DIR}/systray.c ${SOURCE_DIR}/xwindow.c + ${SOURCE_DIR}/xkb.c ${SOURCE_DIR}/common/atoms.c ${SOURCE_DIR}/common/backtrace.c ${SOURCE_DIR}/common/buffer.c diff --git a/awesome.c b/awesome.c index 3931558..f273f98 100644 --- a/awesome.c +++ b/awesome.c @@ -26,6 +26,7 @@ #include "common/backtrace.h" #include "common/version.h" #include "common/xutil.h" +#include "xkb.h" #include "dbus.h" #include "event.h" #include "ewmh.h" @@ -488,6 +489,9 @@ main(int argc, char **argv) /* init spawn (sn) */ spawn_init(); + /* init xkb */ + xkb_init(); + /* The default GC is just a newly created associated with a window with * depth globalconf.default_depth */ xcb_window_t tmp_win = xcb_generate_id(globalconf.connection); diff --git a/awesomeConfig.cmake b/awesomeConfig.cmake index c305dc5..c862245 100644 --- a/awesomeConfig.cmake +++ b/awesomeConfig.cmake @@ -144,6 +144,7 @@ pkg_check_modules(AWESOME_REQUIRED REQUIRED xcb-util>=0.3.8 xcb-keysyms>=0.3.4 xcb-icccm>=0.3.8 + xcb-xkb cairo-xcb libstartup-notification-1.0>=0.10 xproto>=7.0.15 diff --git a/awesomerc.lua.in b/awesomerc.lua.in index 2d61b31..cc293a4 100755 --- a/awesomerc.lua.in +++ b/awesomerc.lua.in @@ -107,6 +107,61 @@ mylauncher = awful.widget.launcher({ image = beautiful.awesome_icon, menubar.utils.terminal = terminal -- Set the terminal for applications that require it -- }}} +-- {{{ Keyboard map indicator and changer +kbdcfg = {} +kbdcfg.widget = wibox.widget.textbox() + +kbdcfg.update_status = function (current_group) + kbdcfg.current = current_group; + kbdcfg.widget:set_text(" " .. kbdcfg.layout[kbdcfg.current] .. " ") +end + +kbdcfg.switch = function(group_num) + if (group_num ~= kbdcfg.current) then + set_xkb_layout_group(group_num); + end +end + +kbdcfg.next_layout = function () + current = (kbdcfg.current + 1) % (#kbdcfg.layout + 1) + kbdcfg.switch(current) +end + +kbdcfg.update_layout = function () + kbdcfg.layout = {}; + group_names = get_xkb_group_names(); + +-- typical layout string looks like "pc+us+ru:2+de:3+ba:4+inet" +-- and want to get only three mathes: "us", "ru:2", "de:3" "ba:4" +-- also please note, that numbers of groups which is reported bt get_xkb_group_names +-- is greater by one of the real group number + + + first_group = string.match(group_names, "+(%a+)"); + if (not first_group) then + warn ("Failed to get list of keyboard group"); + return; + end + kbdcfg.layout[0] = first_group; + + for name, number_str in string.gmatch(group_names, "+(%a+):(%d)") do + number = tonumber(number_str); + kbdcfg.layout[number - 1] = name; + end +end + +kbdcfg.update_layout(); +kbdcfg.update_status(get_xkb_layout_group()); + +awesome.connect_signal("xkb::map_changed", kbdcfg.update_layout); +awesome.connect_signal("xkb::group_changed", kbdcfg.update_status); + +-- Mouse bindings +kbdcfg.widget:buttons( + awful.util.table.join(awful.button({ }, 1, function () kbdcfg.next_layout() end)) +) +-- }}} + -- {{{ Wibox -- Create a textclock widget mytextclock = awful.widget.textclock() @@ -189,6 +244,7 @@ for s = 1, screen.count() do -- Widgets that are aligned to the right local right_layout = wibox.layout.fixed.horizontal() + right_layout:add(kbdcfg.widget) if s == 1 then right_layout:add(wibox.widget.systray()) end right_layout:add(mytextclock) right_layout:add(mylayoutbox[s]) @@ -270,7 +326,9 @@ globalkeys = awful.util.table.join( awful.util.getdir("cache") .. "/history_eval") end), -- Menubar - awful.key({ modkey }, "p", function() menubar.show() end) + awful.key({ modkey }, "p", function() menubar.show() end), + -- Alt + Right Shift switches the current keyboard layout + awful.key({ "Mod1" }, "Shift_R", function () kbdcfg.next_layout() end) ) clientkeys = awful.util.table.join( diff --git a/event.c b/event.c index 50511e0..3979b95 100644 --- a/event.c +++ b/event.c @@ -32,6 +32,7 @@ #include "mousegrabber.h" #include "luaa.h" #include "systray.h" +#include "xkb.h" #include "objects/screen.h" #include "common/atoms.h" #include "common/xutil.h" @@ -857,6 +858,10 @@ void event_handle(xcb_generic_event_t *event) #undef EVENT } + //xkb event number is not constant, so we can't add it to 'switch' above + if (response_type == globalconf.xkb_event_number) { + event_handle_xkb(event); + } static uint8_t randr_screen_change_notify = 0; static uint8_t shape_notify = 0; diff --git a/globalconf.h b/globalconf.h index 9bc1c41..3264c90 100644 --- a/globalconf.h +++ b/globalconf.h @@ -142,6 +142,8 @@ typedef struct tag_array_t tags; /** List of registered xproperties */ xproperty_array_t xproperties; + /** number of xcb event which marks xkb event */ + uint8_t xkb_event_number; } awesome_t; extern awesome_t globalconf; diff --git a/xkb.c b/xkb.c new file mode 100644 index 0000000..1088064 --- /dev/null +++ b/xkb.c @@ -0,0 +1,148 @@ +#include "xkb.h" +#include <xcb/xcb_atom.h> +#include <xcb/xkb.h> +#include <xcb/xcb.h> +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> + +int +luaA_set_xkb_layout_group(lua_State *L) +{ + unsigned group = lua_tonumber(L, 1); + xcb_xkb_latch_lock_state (globalconf.connection, XCB_XKB_ID_USE_CORE_KBD, \ + 0, 0, true, group, 0, 0, 0); + xcb_flush (globalconf.connection); + return 0; +} + +int +luaA_get_xkb_layout_group(lua_State *L) +{ + xcb_xkb_get_state_cookie_t state_c; + state_c = xcb_xkb_get_state_unchecked (globalconf.connection, \ + XCB_XKB_ID_USE_CORE_KBD); + xcb_xkb_get_state_reply_t* state_r; + state_r = xcb_xkb_get_state_reply (globalconf.connection, \ + state_c, NULL); + lua_pushinteger(L, state_r->group); + free(state_r); + return 1; +} + +int +luaA_get_xkb_group_names(lua_State *L) +{ + xcb_xkb_get_names_cookie_t name_c; + name_c = xcb_xkb_get_names_unchecked (globalconf.connection, \ + XCB_XKB_ID_USE_CORE_KBD, \ + XCB_XKB_NAME_DETAIL_SYMBOLS); + xcb_xkb_get_names_reply_t* name_r; + name_r = xcb_xkb_get_names_reply (globalconf.connection, name_c, NULL); + + if (!name_r) + { + luaA_warn(L, "Failed to get xkb symbols name"); + free(name_r); + return 1; + } + + xcb_xkb_get_names_value_list_t name_list; + void *buffer = xcb_xkb_get_names_value_list(name_r); + xcb_xkb_get_names_value_list_unpack ( + buffer, name_r->nTypes, name_r->indicators, + name_r->virtualMods, name_r->groupNames, name_r->nKeys, + name_r->nKeyAliases, name_r->nRadioGroups, name_r->which, + &name_list); + + xcb_get_atom_name_cookie_t atom_name_c; + atom_name_c = xcb_get_atom_name_unchecked(globalconf.connection, name_list.symbolsName); + xcb_get_atom_name_reply_t *atom_name_r; + atom_name_r = xcb_get_atom_name_reply(globalconf.connection, atom_name_c, NULL); + char *symbols_name = xcb_get_atom_name_name(atom_name_r); + + lua_pushstring(L, symbols_name); + free(atom_name_r); + free(name_r); + return 1; +} + +struct _xcb_xkb_event_t { + uint8_t response_type; + uint8_t xkbType; +}; + +void event_handle_xkb(xcb_generic_event_t* event) +{ + struct _xcb_xkb_event_t *xkb_event = (void *)event; + switch (xkb_event->xkbType) + { + case XCB_XKB_NEW_KEYBOARD_NOTIFY: { + xcb_xkb_new_keyboard_notify_event_t *new_keyboard_event = (void *)xkb_event; + if (new_keyboard_event->changed & XCB_XKB_NKN_DETAIL_KEYCODES) { + int arg_cnt = luaA_get_xkb_group_names(globalconf.L); + signal_object_emit(globalconf.L, &global_signals, "xkb::map_changed", arg_cnt); + } + break; + } + case XCB_XKB_NAMES_NOTIFY: { + int arg_cnt = luaA_get_xkb_group_names(globalconf.L); + signal_object_emit(globalconf.L, &global_signals, "xkb::map_changed", arg_cnt); + break; + } + case XCB_XKB_STATE_NOTIFY: { + xcb_xkb_state_notify_event_t *state_notify_event = (void*)xkb_event; + if (state_notify_event->changed & XCB_XKB_STATE_PART_GROUP_STATE) { + lua_pushnumber(globalconf.L, state_notify_event->group); + signal_object_emit(globalconf.L, &global_signals, "xkb::group_changed", 1); + } + break; + } + } +} + + +void xkb_init(void) +{ + /* check xkb version */ + xcb_xkb_use_extension_cookie_t ext_cookie = xcb_xkb_use_extension(globalconf.connection, 1, 0); + xcb_xkb_use_extension_reply_t *ext_reply = xcb_xkb_use_extension_reply (globalconf.connection, ext_cookie, NULL); + if (! ext_reply->supported) + { + fatal("Required xkb extension is not supported"); + } + unsigned int map = XCB_XKB_EVENT_TYPE_STATE_NOTIFY | XCB_XKB_EVENT_TYPE_MAP_NOTIFY | XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY; + xcb_xkb_select_events_checked(globalconf.connection, + XCB_XKB_ID_USE_CORE_KBD, + map, + 0, + map, + 0, + 0, + 0); + + /* get first_event number in order to properly handle events */ + const xcb_query_extension_reply_t *xkb_r; + xkb_r = xcb_get_extension_data(globalconf.connection, &xcb_xkb_id); + if (!xkb_r || !xkb_r->present) + { + fatal("Required xkb extension failed to load !"); + } + + globalconf.xkb_event_number = xkb_r->first_event; + + /* register some functions for lua interface*/ + /* layout switching */ + lua_pushcfunction(globalconf.L, luaA_set_xkb_layout_group); + lua_setglobal(globalconf.L, "set_xkb_layout_group"); + /* short layout */ + lua_pushcfunction(globalconf.L, luaA_get_xkb_group_names); + lua_setglobal(globalconf.L, "get_xkb_group_names"); + + /* short layout */ + lua_pushcfunction(globalconf.L, luaA_get_xkb_layout_group); + lua_setglobal(globalconf.L, "get_xkb_layout_group"); + + signal_add(&global_signals, "xkb::map_changed"); + signal_add(&global_signals, "xkb::group_changed"); +} diff --git a/xkb.h b/xkb.h new file mode 100644 index 0000000..3cd8d29 --- /dev/null +++ b/xkb.h @@ -0,0 +1,22 @@ +#ifndef AWESOME_XKB_H +#define AWESOME_XKB_H + + +#include "common/luaobject.h" + +#include "globalconf.h" + +#include <lua.h> + +//set current group +int luaA_set_xkb_layout_group(lua_State *L); +//get current group +int luaA_get_xkb_layout_group(lua_State *L); +//get string with description of active groups +int luaA_get_xkb_group_names(lua_State *L); + +void event_handle_xkb(xcb_generic_event_t* event); + +void xkb_init(void); + +#endif -- 1.9.0