From 996f6785a885349855a1233113ba092816175843 Mon Sep 17 00:00:00 2001 From: Aleksey Fedotov Date: Mon, 23 Feb 2015 19:08:24 +0300 Subject: [PATCH 1/2] xkb: implementation of keyboard layout switched This patch provides functions to get/set current keyboard layout. Current implementation doesn't support any configuration of layout, it's a merely a layout indicator and switcher, however layout configuration can be set by tools like setxkbmap or by any third-party tools. --- CMakeLists.txt | 1 + awesome.c | 4 + awesomeConfig.cmake | 1 + docs/capi/awesome.lua.in | 13 +++ event.c | 14 +++ luaa.c | 6 ++ xkb.c | 185 +++++++++++++++++++++++++++++++++++++++ xkb.h | 36 ++++++++ 8 files changed, 260 insertions(+) create mode 100644 xkb.c create mode 100644 xkb.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3609687af..5aac6c4f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,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 0d8cb6a53..e07a79e05 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" @@ -499,6 +500,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 e2bb98c49..c2b889dfe 100644 --- a/awesomeConfig.cmake +++ b/awesomeConfig.cmake @@ -140,6 +140,7 @@ pkg_check_modules(AWESOME_REQUIRED REQUIRED xcb-util>=0.3.8 xcb-keysyms>=0.3.4 xcb-icccm>=0.3.8 + xcb-xkb>=1.11 cairo-xcb libstartup-notification-1.0>=0.10 xproto>=7.0.15 diff --git a/docs/capi/awesome.lua.in b/docs/capi/awesome.lua.in index 2b27b2b46..5afbcad75 100644 --- a/docs/capi/awesome.lua.in +++ b/docs/capi/awesome.lua.in @@ -75,3 +75,16 @@ -- @param name A string with the event name. -- @param ... Signal arguments. -- @function emit_signal + +--- Switch keyboard layout group. +-- @param num keyboard layout number, integer from 0 to 3 +-- @function xkb_set_layout_group + +--- Get current keyboard layout group. +-- @return current keyboard layout number +-- @function xkb_get_layout_group + + +--- Get description of configured layouts +-- @return String with description of configured layouts +-- @function xkb_get_group_names diff --git a/event.c b/event.c index c0a8bfae0..4461ea00d 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" @@ -42,6 +43,7 @@ #include #include #include +#include #define DO_EVENT_HOOK_CALLBACK(type, xcbtype, xcbeventprefix, arraytype, match) \ static void \ @@ -880,6 +882,7 @@ void event_handle(xcb_generic_event_t *event) static uint8_t randr_screen_change_notify = 0; static uint8_t shape_notify = 0; + static uint8_t xkb_notify = 0; if(randr_screen_change_notify == 0) { @@ -898,10 +901,21 @@ void event_handle(xcb_generic_event_t *event) shape_notify = shape_query->first_event + XCB_SHAPE_NOTIFY; } + if(xkb_notify == 0) + { + /* check for xkb extension */ + const xcb_query_extension_reply_t *xkb_query; + xkb_query = xcb_get_extension_data(globalconf.connection, &xcb_xkb_id); + if(xkb_query->present) + xkb_notify = xkb_query->first_event; + } + if (response_type == randr_screen_change_notify) event_handle_randr_screen_change_notify((void *) event); if (response_type == shape_notify) event_handle_shape_notify((void *) event); + if (response_type == xkb_notify) + event_handle_xkb_notify((void *) event); } // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/luaa.c b/luaa.c index bfd1b6b3b..2c16152c4 100644 --- a/luaa.c +++ b/luaa.c @@ -37,6 +37,7 @@ #include "selection.h" #include "spawn.h" #include "systray.h" +#include "xkb.h" #include #include @@ -359,6 +360,9 @@ luaA_init(xdgHandle* xdg) { "get_xproperty", luaA_get_xproperty }, { "__index", luaA_awesome_index }, { "__newindex", luaA_default_newindex }, + { "xkb_set_layout_group", luaA_xkb_set_layout_group}, + { "xkb_get_layout_group", luaA_xkb_get_layout_group}, + { "xkb_get_group_names", luaA_xkb_get_group_names}, { NULL, NULL } }; @@ -473,6 +477,8 @@ luaA_init(xdgHandle* xdg) signal_add(&global_signals, "debug::newindex::miss"); signal_add(&global_signals, "systray::update"); signal_add(&global_signals, "wallpaper_changed"); + signal_add(&global_signals, "xkb::map_changed"); + signal_add(&global_signals, "xkb::group_changed"); signal_add(&global_signals, "refresh"); signal_add(&global_signals, "startup"); signal_add(&global_signals, "exit"); diff --git a/xkb.c b/xkb.c new file mode 100644 index 000000000..ae5f872fe --- /dev/null +++ b/xkb.c @@ -0,0 +1,185 @@ +/* + * xkb.c - keyboard layout control functions + * + * Copyright © 2015 Aleksey Fedotov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "xkb.h" +#include "globalconf.h" +#include + + +/* \brief switch keyboard layout + * \param L The Lua VM state. + * \return The number of elements pushed on stack. + * \luastack + * \lparam layout number, integer from 0 to 3 + */ +int +luaA_xkb_set_layout_group(lua_State *L) +{ + unsigned group = luaL_checkinteger(L, 1); + xcb_xkb_latch_lock_state (globalconf.connection, XCB_XKB_ID_USE_CORE_KBD, + 0, 0, true, group, 0, 0, 0); + return 0; +} + +/* \brief get current layout number + * \param L The Lua VM state. + * \return The number of elements pushed on stack. + * \luastack + * \lreturn current layout number, integer from 0 to 3 + */ +int +luaA_xkb_get_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); + if (!state_r) + { + free(state_r); + return 0; + } + lua_pushinteger(L, state_r->group); + free(state_r); + return 1; +} + +/* \brief get layout short names + * \param L The Lua VM state. + * \return The number of elements pushed on stack. + * \luastack + * \lreturn string describing current layout settings, \ + * example: 'pc+us+de:2+inet(evdev)+group(alt_shift_toggle)+ctrl(nocaps)' + */ +int +luaA_xkb_get_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"); + return 0; + } + + 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); + if (!atom_name_r) { + luaA_warn(L, "Failed to get atom symbols name"); + free(name_r); + return 0; + } + + const char *name = xcb_get_atom_name_name(atom_name_r); + size_t name_len = xcb_get_atom_name_name_length(atom_name_r); + lua_pushlstring(L, name, name_len); + + free(atom_name_r); + free(name_r); + return 1; +} + +/** The xkb notify event handler. + * \param event The event. + */ +void event_handle_xkb_notify(xcb_generic_event_t* event) +{ + lua_State *L = globalconf_get_lua_State(); + /* The pad0 field of xcb_generic_event_t contains the event sub-type, + * unfortunately xkb doesn't provide a usable struct for getting this in a + * nicer way*/ + switch (event->pad0) + { + case XCB_XKB_NEW_KEYBOARD_NOTIFY: + { + xcb_xkb_new_keyboard_notify_event_t *new_keyboard_event = (void*)event; + if (new_keyboard_event->changed & XCB_XKB_NKN_DETAIL_KEYCODES) + { + signal_object_emit(L, &global_signals, "xkb::map_changed", 0); + } + break; + } + case XCB_XKB_NAMES_NOTIFY: + { + signal_object_emit(L, &global_signals, "xkb::map_changed", 0); + break; + } + case XCB_XKB_STATE_NOTIFY: + { + xcb_xkb_state_notify_event_t *state_notify_event = (void*)event; + if (state_notify_event->changed & XCB_XKB_STATE_PART_GROUP_STATE) + { + lua_pushnumber(L, state_notify_event->group); + signal_object_emit(L, &global_signals, "xkb::group_changed", 1); + } + break; + } + } +} + +/** Initialize XKB support + */ + +void xkb_init(void) +{ + /* check that XKB extension present in this X server */ + 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("Xkb extension not present"); + } + + /* 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 || !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); + +} diff --git a/xkb.h b/xkb.h new file mode 100644 index 000000000..1a401ce2d --- /dev/null +++ b/xkb.h @@ -0,0 +1,36 @@ +/* + * xkb.h - Keyboard layout manager header + * + * Copyright © 2015 Aleksey Fedotov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef AWESOME_XKB_H +#define AWESOME_XKB_H + +#include +#include + +void event_handle_xkb_notify(xcb_generic_event_t* event); +void xkb_init(void); + +int luaA_xkb_set_layout_group(lua_State *L); +int luaA_xkb_get_layout_group(lua_State *L); +int luaA_xkb_get_group_names(lua_State *L); + + +#endif From d9b1d5490cd0ef18578d969153ecab1f9e1f53a5 Mon Sep 17 00:00:00 2001 From: Aleksey Fedotov Date: Mon, 23 Feb 2015 22:30:02 +0300 Subject: [PATCH 2/2] xkb: Added widget to display current keyboard layout Widget shows current keyboard layout short name (usually two letters, for example 'us', 'de'). Indicator will be hidden if only one layout configured. Layout can be switched either by clicking on widget or by function keyboardlayout.next_layout() --- awesomerc.lua.in | 6 ++ lib/awful/widget/init.lua.in | 1 + lib/awful/widget/keyboardlayout.lua.in | 104 +++++++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 lib/awful/widget/keyboardlayout.lua.in diff --git a/awesomerc.lua.in b/awesomerc.lua.in index ce647de82..89dc8f9e2 100755 --- a/awesomerc.lua.in +++ b/awesomerc.lua.in @@ -107,6 +107,10 @@ mylauncher = awful.widget.launcher({ image = beautiful.awesome_icon, menubar.utils.terminal = terminal -- Set the terminal for applications that require it -- }}} +-- Keyboard map indicator and switcher +mykeyboardlayout = awful.widget.keyboardlayout() +-- }}} + -- {{{ Wibox -- Create a textclock widget mytextclock = awful.widget.textclock() @@ -189,6 +193,8 @@ for s = 1, screen.count() do -- Widgets that are aligned to the right local right_layout = wibox.layout.fixed.horizontal() + right_layout:add(mykeyboardlayout) + if s == 1 then right_layout:add(wibox.widget.systray()) end right_layout:add(mytextclock) right_layout:add(mylayoutbox[s]) diff --git a/lib/awful/widget/init.lua.in b/lib/awful/widget/init.lua.in index 0d61b94cf..17b3a6823 100644 --- a/lib/awful/widget/init.lua.in +++ b/lib/awful/widget/init.lua.in @@ -18,6 +18,7 @@ return graph = require("awful.widget.graph"); layoutbox = require("awful.widget.layoutbox"); textclock = require("awful.widget.textclock"); + keyboardlayout = require("awful.widget.keyboardlayout"); } -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/awful/widget/keyboardlayout.lua.in b/lib/awful/widget/keyboardlayout.lua.in new file mode 100644 index 000000000..ff4f1ab1d --- /dev/null +++ b/lib/awful/widget/keyboardlayout.lua.in @@ -0,0 +1,104 @@ +--------------------------------------------------------------------------- +-- @author Aleksey Fedotov <lexa@cfotr.com> +-- @copyright 2015 Aleksey Fedotov +-- @release @AWESOME_VERSION@ +--------------------------------------------------------------------------- + +local capi = {awesome = awesome, + awful = awful} +local setmetatable = setmetatable +local os = os +local textbox = require("wibox.widget.textbox") +local button = require("awful.button") +local util = require("awful.util") +local widget_base = require("wibox.widget.base") + +--- Keyboard Layout widget. +-- awful.widget.keyboardlayout +local keyboardlayout = { mt = {} } + +-- Callback for updaing current layout +local function update_status (keyboardlayout) + keyboardlayout.current = awesome.xkb_get_layout_group(); + local text = "" + if (#keyboardlayout.layout > 0) then + text = (" " .. keyboardlayout.layout[keyboardlayout.current] .. " ") + end + keyboardlayout.widget:set_text(text) +end + +-- Callback for updating list of layouts +local function update_layout(keyboardlayout) + keyboardlayout.layout = {}; + local group_names = awesome.xkb_get_group_names(); + +-- typical layout string looks like "pc+us+ru:2+de:3+ba:4+inet" +-- and we want to get only three mathes: "us", "ru:2", "de:3" "ba:4" +-- also please note, that numbers of groups reported by xkb_get_group_names +-- is greater by one of the real group number + + + local first_group = string.match(group_names, "+(%a+)"); + if (not first_group) then + error ("Failed to get list of keyboard groups"); + return; + end + keyboardlayout.layout[0] = first_group; + + for name, number_str in string.gmatch(group_names, "+(%a+):(%d)") do + group = tonumber(number_str); + keyboardlayout.layout[group - 1] = name; + end + update_status(keyboardlayout) +end + +--- Create a keyboard layout widget. It shows current keyboard layout name in a textbox. +-- @return A keyboard layout widget. +function keyboardlayout.new() + local widget = textbox() + local keyboardlayout = widget_base.make_widget(widget) + + keyboardlayout.widget = widget + + update_layout(keyboardlayout); + + keyboardlayout.next_layout = function() + new_layout = (keyboardlayout.current + 1) % (#keyboardlayout.layout + 1) + keyboardlayout.set_layout(new_layout) + end + + keyboardlayout.set_layout = function(group_number) + if (0 > group_number) or (group_number > #keyboardlayout.layout) then + error("Invalid group number: " .. group_number .. + "expected number from 0 to " .. #keyboardlayout.layout) + return; + end + awesome.xkb_set_layout_group(group_number); + end + + -- callback for processing layout changes + capi.awesome.connect_signal("xkb::map_changed", + function () update_layout(keyboardlayout) end) + capi.awesome.connect_signal("xkb::group_changed", + function () update_status(keyboardlayout) end); + + -- Mouse bindings + keyboardlayout:buttons( + util.table.join(button({ }, 1, keyboardlayout.next_layout)) + ) + + return keyboardlayout +end + +local _instance = nil; + +function keyboardlayout.mt:__call(...) + if _instance == nil then + _instance = keyboardlayout.new(...) + end + return _instance +end + +return setmetatable(keyboardlayout, keyboardlayout.mt) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80