layout.c 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. #include <err.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <xcb/xcb.h>
  5. #include <xcb/xkb.h>
  6. #include <xcb/xcb_event.h>
  7. #include "cvector.h"
  8. #include "assert_msg.h"
  9. #define XKB_VERSION XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION
  10. #define LEN(S) (sizeof(S) / sizeof *(S))
  11. #define EVENT_CAST(TYPE, TO, FROM) TYPE *TO = (TYPE *)(FROM)
  12. typedef struct WinLayout {
  13. xcb_window_t id;
  14. uint8_t layout;
  15. } WinLayout;
  16. static void handle_events(xcb_connection_t *c);
  17. static void xkb_init(xcb_connection_t *c);
  18. static xcb_atom_t get_active_window_atom(xcb_connection_t *c);
  19. static xcb_window_t get_active_window(xcb_connection_t *c);
  20. static void set_keyboard_layout(xcb_connection_t *c, uint8_t layout);
  21. static uint8_t get_keyboard_layout(xcb_connection_t *c);
  22. static xcb_atom_t NET_ACTIVE_WINDOW_ATOM;
  23. int
  24. main(void)
  25. {
  26. int screen = 0;
  27. uint32_t mask;
  28. /* xcb */
  29. xcb_connection_t * c;
  30. xcb_screen_t * scr;
  31. xcb_screen_iterator_t iter;
  32. const xcb_setup_t * setup;
  33. c = xcb_connect(NULL, &screen);
  34. assert_msg("connection error", !xcb_connection_has_error(c));
  35. xkb_init(c);
  36. setup = xcb_get_setup(c);
  37. iter = xcb_setup_roots_iterator(setup);
  38. NET_ACTIVE_WINDOW_ATOM = get_active_window_atom(c);
  39. for (int i = 0; i < screen; i -= (~0L))
  40. xcb_screen_next(&iter);
  41. scr = iter.data;
  42. mask = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY
  43. | XCB_EVENT_MASK_PROPERTY_CHANGE;
  44. xcb_change_window_attributes_checked(c,
  45. scr->root,
  46. XCB_CW_EVENT_MASK,
  47. &mask);
  48. xcb_flush(c);
  49. handle_events(c);
  50. return 0;
  51. }
  52. static void
  53. handle_events(xcb_connection_t *c)
  54. {
  55. uint8_t layout;
  56. xcb_generic_event_t *e;
  57. cvector_vector_type(WinLayout) wins = NULL;
  58. xcb_window_t previous_win_id = get_active_window(c);
  59. while (!!(e = xcb_wait_for_event(c))) {
  60. switch (e->response_type & ~0x80) {
  61. case XCB_PROPERTY_NOTIFY: {
  62. EVENT_CAST(xcb_property_notify_event_t, ev, e);
  63. if (ev->atom != NET_ACTIVE_WINDOW_ATOM) break;
  64. xcb_window_t active = get_active_window(c);
  65. /* after closing window `previous_win_id` == 0 */
  66. if (!!previous_win_id) {
  67. layout = get_keyboard_layout(c);
  68. for (size_t i = 0; i < cvector_size(wins); i++)
  69. if (wins[i].id == previous_win_id) {
  70. wins[i].layout = layout;
  71. goto endif;
  72. }
  73. WinLayout win = { .id = previous_win_id,
  74. .layout = layout };
  75. cvector_push_back(wins, win);
  76. endif:
  77. NULL;
  78. }
  79. layout = 0;
  80. for (size_t i = 0; i < cvector_size(wins); i++)
  81. if (wins[i].id == active) {
  82. layout = wins[i].layout;
  83. break;
  84. }
  85. set_keyboard_layout(c, layout);
  86. previous_win_id = active;
  87. break;
  88. }
  89. case XCB_DESTROY_NOTIFY: {
  90. EVENT_CAST(xcb_destroy_notify_event_t, ev, e);
  91. for (size_t i = 0; i < cvector_size(wins); i++)
  92. if (wins[i].id == ev->window) {
  93. cvector_erase(wins, i);
  94. previous_win_id = 0;
  95. break;
  96. }
  97. break;
  98. }
  99. }
  100. free(e);
  101. }
  102. cvector_free(wins);
  103. }
  104. static xcb_window_t
  105. get_active_window(xcb_connection_t *c)
  106. {
  107. xcb_window_t win;
  108. xcb_get_input_focus_reply_t *focus_reply;
  109. focus_reply =
  110. xcb_get_input_focus_reply(c, xcb_get_input_focus(c), NULL);
  111. assert_msg("cannot get focused window", !!focus_reply);
  112. win = focus_reply->focus;
  113. free(focus_reply);
  114. return win;
  115. }
  116. static uint8_t
  117. get_keyboard_layout(xcb_connection_t *c)
  118. {
  119. uint8_t group;
  120. xcb_xkb_get_state_reply_t *kbd_state;
  121. kbd_state = xcb_xkb_get_state_reply(
  122. c,
  123. xcb_xkb_get_state(c, XCB_XKB_ID_USE_CORE_KBD),
  124. NULL);
  125. assert_msg("cannot gat keyboard layout", !!kbd_state);
  126. group = kbd_state->group;
  127. free(kbd_state);
  128. return group;
  129. }
  130. void
  131. set_keyboard_layout(xcb_connection_t *c, uint8_t layout)
  132. {
  133. xcb_xkb_latch_lock_state(c,
  134. XCB_XKB_ID_USE_CORE_KBD,
  135. 0, /* affectModLocks */
  136. 0, /* modLocks */
  137. 1, /* lockGroup */
  138. layout,
  139. 0, /* affectModLatches */
  140. 0, /* latchGroup */
  141. 0 /* groupLatch */
  142. );
  143. assert_msg("failed to set keyboard layout",
  144. layout == get_keyboard_layout(c));
  145. }
  146. static void
  147. xkb_init(xcb_connection_t *c)
  148. {
  149. uint8_t supported;
  150. xcb_xkb_use_extension_reply_t *reply;
  151. reply =
  152. xcb_xkb_use_extension_reply(c,
  153. xcb_xkb_use_extension(c, XKB_VERSION),
  154. NULL);
  155. supported = reply->supported;
  156. free(reply);
  157. assert_msg("XKB extension in not supported by server", !!supported);
  158. }
  159. static xcb_atom_t
  160. get_active_window_atom(xcb_connection_t *c)
  161. {
  162. xcb_atom_t atom;
  163. xcb_intern_atom_reply_t *reply;
  164. xcb_intern_atom_cookie_t cookie;
  165. static const char atom_name[] = "_NET_ACTIVE_WINDOW";
  166. static const char error_msg[] =
  167. "failed to get '_NET_ACTIVE_WINDOW' atom";
  168. cookie = xcb_intern_atom(c, 1, LEN(atom_name) - 1, atom_name);
  169. reply = xcb_intern_atom_reply(c, cookie, NULL);
  170. assert_msg(error_msg, !!reply);
  171. assert_msg(error_msg, (atom = reply->atom) != XCB_ATOM_NONE);
  172. free(reply);
  173. return atom;
  174. }