Mirror of Awesome WM window manager
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

xkb.c 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. /*
  2. * xkb.c - keyboard layout control functions
  3. *
  4. * Copyright © 2015 Aleksey Fedotov <lexa@cfotr.com>
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along
  17. * with this program; if not, write to the Free Software Foundation, Inc.,
  18. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  19. *
  20. */
  21. /**
  22. * @module awesome
  23. */
  24. #include "xkb.h"
  25. #include "globalconf.h"
  26. #include "xwindow.h"
  27. #include "objects/client.h"
  28. #include "common/atoms.h"
  29. #include <xcb/xkb.h>
  30. #include <xkbcommon/xkbcommon.h>
  31. #include <xkbcommon/xkbcommon-x11.h>
  32. /**
  33. * Switch keyboard layout.
  34. *
  35. * @staticfct xkb_set_layout_group
  36. * @tparam integer num keyboard layout number, integer from 0 to 3
  37. */
  38. int
  39. luaA_xkb_set_layout_group(lua_State *L)
  40. {
  41. unsigned group = luaL_checkinteger(L, 1);
  42. if (!globalconf.have_xkb)
  43. {
  44. luaA_warn(L, "XKB not supported");
  45. return 0;
  46. }
  47. xcb_xkb_latch_lock_state (globalconf.connection, XCB_XKB_ID_USE_CORE_KBD,
  48. 0, 0, true, group, 0, 0, 0);
  49. return 0;
  50. }
  51. /**
  52. * Get current layout number.
  53. *
  54. * @staticfct xkb_get_layout_group
  55. * @treturn integer num Current layout number, integer from 0 to 3.
  56. */
  57. int
  58. luaA_xkb_get_layout_group(lua_State *L)
  59. {
  60. if (!globalconf.have_xkb)
  61. {
  62. luaA_warn(L, "XKB not supported");
  63. return 0;
  64. }
  65. xcb_xkb_get_state_cookie_t state_c;
  66. state_c = xcb_xkb_get_state_unchecked (globalconf.connection,
  67. XCB_XKB_ID_USE_CORE_KBD);
  68. xcb_xkb_get_state_reply_t* state_r;
  69. state_r = xcb_xkb_get_state_reply (globalconf.connection,
  70. state_c, NULL);
  71. if (!state_r)
  72. {
  73. free(state_r);
  74. return 0;
  75. }
  76. lua_pushinteger(L, state_r->group);
  77. free(state_r);
  78. return 1;
  79. }
  80. /**
  81. * Get layout short names.
  82. *
  83. * @staticfct xkb_get_group_names
  84. * @treturn string A string describing the current layout settings,
  85. * e.g.: 'pc+us+de:2+inet(evdev)+group(alt_shift_toggle)+ctrl(nocaps)'
  86. */
  87. int
  88. luaA_xkb_get_group_names(lua_State *L)
  89. {
  90. if (!globalconf.have_xkb)
  91. {
  92. luaA_warn(L, "XKB not supported");
  93. return 0;
  94. }
  95. xcb_xkb_get_names_cookie_t name_c;
  96. name_c = xcb_xkb_get_names_unchecked (globalconf.connection,
  97. XCB_XKB_ID_USE_CORE_KBD,
  98. XCB_XKB_NAME_DETAIL_SYMBOLS);
  99. xcb_xkb_get_names_reply_t* name_r;
  100. name_r = xcb_xkb_get_names_reply (globalconf.connection, name_c, NULL);
  101. if (!name_r)
  102. {
  103. luaA_warn(L, "Failed to get xkb symbols name");
  104. return 0;
  105. }
  106. xcb_xkb_get_names_value_list_t name_list;
  107. void *buffer = xcb_xkb_get_names_value_list(name_r);
  108. xcb_xkb_get_names_value_list_unpack (
  109. buffer, name_r->nTypes, name_r->indicators,
  110. name_r->virtualMods, name_r->groupNames, name_r->nKeys,
  111. name_r->nKeyAliases, name_r->nRadioGroups, name_r->which,
  112. &name_list);
  113. xcb_get_atom_name_cookie_t atom_name_c;
  114. atom_name_c = xcb_get_atom_name_unchecked(globalconf.connection, name_list.symbolsName);
  115. xcb_get_atom_name_reply_t *atom_name_r;
  116. atom_name_r = xcb_get_atom_name_reply(globalconf.connection, atom_name_c, NULL);
  117. if (!atom_name_r) {
  118. luaA_warn(L, "Failed to get atom symbols name");
  119. free(name_r);
  120. return 0;
  121. }
  122. const char *name = xcb_get_atom_name_name(atom_name_r);
  123. size_t name_len = xcb_get_atom_name_name_length(atom_name_r);
  124. lua_pushlstring(L, name, name_len);
  125. free(atom_name_r);
  126. free(name_r);
  127. return 1;
  128. }
  129. static bool
  130. fill_rmlvo_from_root(struct xkb_rule_names *xkb_names)
  131. {
  132. xcb_get_property_reply_t *prop_reply = xcb_get_property_reply(globalconf.connection,
  133. xcb_get_property_unchecked(globalconf.connection, false, globalconf.screen->root, _XKB_RULES_NAMES, XCB_GET_PROPERTY_TYPE_ANY, 0, UINT_MAX),
  134. NULL);
  135. if (!prop_reply)
  136. return false;
  137. if (prop_reply->value_len == 0)
  138. {
  139. p_delete(&prop_reply);
  140. return false;
  141. }
  142. const char *walk = (const char *) xcb_get_property_value(prop_reply);
  143. unsigned int remaining = xcb_get_property_value_length(prop_reply);
  144. for (int i = 0; i < 5 && remaining > 0; i++)
  145. {
  146. const int len = strnlen(walk, remaining);
  147. switch (i) {
  148. case 0:
  149. xkb_names->rules = strndup(walk, len);
  150. break;
  151. case 1:
  152. xkb_names->model = strndup(walk, len);
  153. break;
  154. case 2:
  155. xkb_names->layout = strndup(walk, len);
  156. break;
  157. case 3:
  158. xkb_names->variant = strndup(walk, len);
  159. break;
  160. case 4:
  161. xkb_names->options = strndup(walk, len);
  162. break;
  163. }
  164. remaining -= len + 1;
  165. walk = &walk[len + 1];
  166. }
  167. p_delete(&prop_reply);
  168. return true;
  169. }
  170. /** Fill globalconf.xkb_state based on connection and context
  171. */
  172. static void
  173. xkb_fill_state(void)
  174. {
  175. xcb_connection_t *conn = globalconf.connection;
  176. int32_t device_id = -1;
  177. if (globalconf.have_xkb)
  178. {
  179. device_id = xkb_x11_get_core_keyboard_device_id(conn);
  180. if (device_id == -1)
  181. warn("Failed while getting XKB device id");
  182. }
  183. if (device_id != -1)
  184. {
  185. struct xkb_keymap *xkb_keymap = xkb_x11_keymap_new_from_device(
  186. globalconf.xkb_ctx,
  187. conn,
  188. device_id,
  189. XKB_KEYMAP_COMPILE_NO_FLAGS);
  190. if (!xkb_keymap)
  191. fatal("Failed while getting XKB keymap from device");
  192. globalconf.xkb_state = xkb_x11_state_new_from_device(xkb_keymap,
  193. conn,
  194. device_id);
  195. if (!globalconf.xkb_state)
  196. fatal("Failed while getting XKB state from device");
  197. /* xkb_keymap is no longer referenced directly; decreasing refcount */
  198. xkb_keymap_unref(xkb_keymap);
  199. }
  200. else
  201. {
  202. struct xkb_rule_names names = { NULL, NULL, NULL, NULL, NULL };
  203. if (!fill_rmlvo_from_root(&names))
  204. warn("Could not get _XKB_RULES_NAMES from root window, falling back to defaults.");
  205. struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_names(globalconf.xkb_ctx, &names, 0);
  206. globalconf.xkb_state = xkb_state_new(xkb_keymap);
  207. if (!globalconf.xkb_state)
  208. fatal("Failed while creating XKB state");
  209. /* xkb_keymap is no longer referenced directly; decreasing refcount */
  210. xkb_keymap_unref(xkb_keymap);
  211. p_delete(&names.rules);
  212. p_delete(&names.model);
  213. p_delete(&names.layout);
  214. p_delete(&names.variant);
  215. p_delete(&names.options);
  216. }
  217. }
  218. /** Loads xkb context, state and keymap to globalconf.
  219. * These variables should be freed by xkb_free_keymap() afterwards
  220. */
  221. static void
  222. xkb_init_keymap(void)
  223. {
  224. globalconf.xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
  225. if (!globalconf.xkb_ctx)
  226. fatal("Failed while getting XKB context");
  227. xkb_fill_state();
  228. }
  229. /** Frees xkb context, state and keymap from globalconf.
  230. * This should be used when these variables will not be used anymore
  231. */
  232. static void
  233. xkb_free_keymap(void)
  234. {
  235. xkb_state_unref(globalconf.xkb_state);
  236. xkb_context_unref(globalconf.xkb_ctx);
  237. }
  238. /** Rereads the state of keyboard from X.
  239. * This call should be used after receiving NewKeyboardNotify or MapNotify,
  240. * as written in http://xkbcommon.org/doc/current/group__x11.html
  241. */
  242. static void
  243. xkb_reload_keymap(void)
  244. {
  245. assert(globalconf.have_xkb);
  246. xkb_state_unref(globalconf.xkb_state);
  247. xkb_fill_state();
  248. /* Free and then allocate the key symbols */
  249. xcb_key_symbols_free(globalconf.keysyms);
  250. globalconf.keysyms = xcb_key_symbols_alloc(globalconf.connection);
  251. /* Regrab key bindings on the root window */
  252. xcb_screen_t *s = globalconf.screen;
  253. xwindow_grabkeys(s->root, &globalconf.keys);
  254. /* Regrab key bindings on clients */
  255. foreach(_c, globalconf.clients)
  256. {
  257. client_t *c = *_c;
  258. xwindow_grabkeys(c->window, &c->keys);
  259. if (c->nofocus_window)
  260. xwindow_grabkeys(c->nofocus_window, &c->keys);
  261. }
  262. }
  263. static gboolean
  264. xkb_refresh(gpointer unused)
  265. {
  266. lua_State *L = globalconf_get_lua_State();
  267. globalconf.xkb_update_pending = false;
  268. if (globalconf.xkb_reload_keymap)
  269. xkb_reload_keymap();
  270. if (globalconf.xkb_map_changed)
  271. signal_object_emit(L, &global_signals, "xkb::map_changed", 0);
  272. if (globalconf.xkb_group_changed)
  273. signal_object_emit(L, &global_signals, "xkb::group_changed", 0);
  274. globalconf.xkb_reload_keymap = false;
  275. globalconf.xkb_map_changed = false;
  276. globalconf.xkb_group_changed = false;
  277. return G_SOURCE_REMOVE;
  278. }
  279. static void
  280. xkb_schedule_refresh(void)
  281. {
  282. if (globalconf.xkb_update_pending)
  283. return;
  284. globalconf.xkb_update_pending = true;
  285. g_idle_add_full(G_PRIORITY_LOW, xkb_refresh, NULL, NULL);
  286. }
  287. /** The xkb notify event handler.
  288. * \param event The event.
  289. */
  290. void
  291. event_handle_xkb_notify(xcb_generic_event_t* event)
  292. {
  293. assert(globalconf.have_xkb);
  294. /* The pad0 field of xcb_generic_event_t contains the event sub-type,
  295. * unfortunately xkb doesn't provide a usable struct for getting this in a
  296. * nicer way*/
  297. switch (event->pad0)
  298. {
  299. case XCB_XKB_NEW_KEYBOARD_NOTIFY:
  300. {
  301. xcb_xkb_new_keyboard_notify_event_t *new_keyboard_event = (void*)event;
  302. globalconf.xkb_reload_keymap = true;
  303. if (new_keyboard_event->changed & XCB_XKB_NKN_DETAIL_KEYCODES)
  304. globalconf.xkb_map_changed = true;
  305. xkb_schedule_refresh();
  306. break;
  307. }
  308. case XCB_XKB_MAP_NOTIFY:
  309. {
  310. globalconf.xkb_reload_keymap = true;
  311. globalconf.xkb_map_changed = true;
  312. xkb_schedule_refresh();
  313. break;
  314. }
  315. case XCB_XKB_STATE_NOTIFY:
  316. {
  317. xcb_xkb_state_notify_event_t *state_notify_event = (void*)event;
  318. xkb_state_update_mask(globalconf.xkb_state,
  319. state_notify_event->baseMods,
  320. state_notify_event->latchedMods,
  321. state_notify_event->lockedMods,
  322. state_notify_event->baseGroup,
  323. state_notify_event->latchedGroup,
  324. state_notify_event->lockedGroup);
  325. if (state_notify_event->changed & XCB_XKB_STATE_PART_GROUP_STATE)
  326. {
  327. globalconf.xkb_group_changed = true;
  328. xkb_schedule_refresh();
  329. }
  330. break;
  331. }
  332. }
  333. }
  334. /** Initialize XKB support
  335. * This call allocates resources, that should be freed by calling xkb_free()
  336. */
  337. void
  338. xkb_init(void)
  339. {
  340. globalconf.xkb_update_pending = false;
  341. globalconf.xkb_reload_keymap = false;
  342. globalconf.xkb_map_changed = false;
  343. globalconf.xkb_group_changed = false;
  344. int success_xkb = xkb_x11_setup_xkb_extension(globalconf.connection,
  345. XKB_X11_MIN_MAJOR_XKB_VERSION,
  346. XKB_X11_MIN_MINOR_XKB_VERSION,
  347. 0,
  348. NULL,
  349. NULL,
  350. NULL,
  351. NULL);
  352. globalconf.have_xkb = success_xkb;
  353. if (!success_xkb) {
  354. warn("XKB not found or not supported");
  355. xkb_init_keymap();
  356. return;
  357. }
  358. uint16_t map = XCB_XKB_EVENT_TYPE_STATE_NOTIFY | XCB_XKB_EVENT_TYPE_MAP_NOTIFY | XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY;
  359. //
  360. // These maps are provided to allow key remapping,
  361. // that could be used in awesome
  362. //
  363. uint16_t map_parts = XCB_XKB_MAP_PART_KEY_TYPES |
  364. XCB_XKB_MAP_PART_KEY_SYMS |
  365. XCB_XKB_MAP_PART_MODIFIER_MAP |
  366. XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
  367. XCB_XKB_MAP_PART_KEY_ACTIONS |
  368. XCB_XKB_MAP_PART_KEY_BEHAVIORS |
  369. XCB_XKB_MAP_PART_VIRTUAL_MODS |
  370. XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP;
  371. /* Enable detectable auto-repeat, but ignore failures */
  372. xcb_discard_reply(globalconf.connection,
  373. xcb_xkb_per_client_flags(globalconf.connection,
  374. XCB_XKB_ID_USE_CORE_KBD,
  375. XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT,
  376. XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT,
  377. 0,
  378. 0,
  379. 0)
  380. .sequence);
  381. xcb_xkb_select_events(globalconf.connection,
  382. XCB_XKB_ID_USE_CORE_KBD,
  383. map,
  384. 0,
  385. map,
  386. map_parts,
  387. map_parts,
  388. 0);
  389. /* load keymap to use when resolving keypresses */
  390. xkb_init_keymap();
  391. }
  392. /** Frees resources allocated by xkb_init()
  393. */
  394. void
  395. xkb_free(void)
  396. {
  397. if (globalconf.have_xkb)
  398. // unsubscribe from all events
  399. xcb_xkb_select_events(globalconf.connection,
  400. XCB_XKB_ID_USE_CORE_KBD,
  401. 0,
  402. 0,
  403. 0,
  404. 0,
  405. 0,
  406. 0);
  407. xkb_free_keymap();
  408. }
  409. // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80