#include "xkb.h" #include #include #include #include #include #include #include #include #include "../../bar.h" #include "../../xcb.h" struct layout { char *name; char *symbol; }; struct layouts { ssize_t count; struct layout *layouts; }; struct private { struct particle *label; struct layouts layouts; size_t current; }; static void free_layouts(struct layouts layouts) { for (ssize_t i = 0; i < layouts.count; i++) { free(layouts.layouts[i].name); free(layouts.layouts[i].symbol); } free(layouts.layouts); } static void destroy(struct module *mod) { struct private *m = mod->private; m->label->destroy(m->label); free_layouts(m->layouts); free(m); free(mod); } static struct exposable * content(const struct module *mod) { const struct private *m = mod->private; printf("current: %zu\n", m->current); for (size_t i = 0; i < m->layouts.count; i++) printf(" %s (%s)\n", m->layouts.layouts[i].name, m->layouts.layouts[i].symbol); struct tag_set tags = { .tags = (struct tag *[]){ tag_new_string("name", m->layouts.layouts[m->current].name), tag_new_string("symbol", m->layouts.layouts[m->current].symbol)}, .count = 2, }; struct exposable *exposable = m->label->instantiate(m->label, &tags); tag_set_destroy(&tags); return exposable; } static bool xkb_enable(xcb_connection_t *conn) { xcb_generic_error_t *err; xcb_xkb_use_extension_cookie_t cookie = xcb_xkb_use_extension( conn, XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION); xcb_xkb_use_extension_reply_t *reply = xcb_xkb_use_extension_reply( conn, cookie, &err); assert(err == NULL && "xcb_xkb_use_extension() failed"); assert(reply->supported && "XKB extension not supported"); free(reply); return true; } static int get_xkb_event_base(xcb_connection_t *conn) { const struct xcb_query_extension_reply_t *reply = xcb_get_extension_data( conn, &xcb_xkb_id); if (reply == NULL) { puts("failed to get XKB extension data"); return -1; } if (!reply->present) { puts("XKB not present"); return -1; } return reply->first_event; } static struct layouts get_layouts(xcb_connection_t *conn) { struct layouts ret; xcb_generic_error_t *err; xcb_xkb_get_names_cookie_t cookie = xcb_xkb_get_names( conn, XCB_XKB_ID_USE_CORE_KBD, XCB_XKB_NAME_DETAIL_GROUP_NAMES | XCB_XKB_NAME_DETAIL_SYMBOLS); xcb_xkb_get_names_reply_t *reply = xcb_xkb_get_names_reply( conn, cookie, &err); if (err != NULL) { printf("%d: %s\n", err->error_code, "failed to get group names and symbols"); free(err); return (struct layouts){.count = -1}; } xcb_xkb_get_names_value_list_t vlist; void *buf = xcb_xkb_get_names_value_list(reply); xcb_xkb_get_names_value_list_unpack( buf, reply->nTypes, reply->indicators, reply->virtualMods, reply->groupNames, reply->nKeys, reply->nKeyAliases, reply->nRadioGroups, reply->which, &vlist); /* Number of groups (aka layouts) */ ret.count = xcb_xkb_get_names_value_list_groups_length(reply, &vlist); ret.layouts = calloc(ret.count, sizeof(ret.layouts[0])); xcb_get_atom_name_cookie_t symbols_name_cookie = xcb_get_atom_name( conn, vlist.symbolsName); xcb_get_atom_name_cookie_t group_name_cookies[ret.count]; for (ssize_t i = 0; i < ret.count; i++) group_name_cookies[i] = xcb_get_atom_name(conn, vlist.groups[i]); /* Get layout short names (e.g. "us") */ xcb_get_atom_name_reply_t *atom_name = xcb_get_atom_name_reply( conn, symbols_name_cookie, &err); if (err != NULL) { printf("%d: failed to get atom name\n", err->error_code); free(err); goto err; } char *symbols = strndup(xcb_get_atom_name_name(atom_name), xcb_get_atom_name_name_length(atom_name)); free(atom_name); /* Get layout long names (e.g. "English (US)") */ for (ssize_t i = 0; i < ret.count; i++) { atom_name = xcb_get_atom_name_reply(conn, group_name_cookies[i], &err); if (err != NULL) { printf("%d: failed to get atom name\n", err->error_code); free(err); goto err; } ret.layouts[i].name = strndup(xcb_get_atom_name_name(atom_name), xcb_get_atom_name_name_length(atom_name)); free(atom_name); } /* e.g. pc+us+inet(evdev)+group(..) */ ssize_t layout_idx = 0; for (char *tok_ctx = NULL, *tok = strtok_r(symbols, "+", &tok_ctx); tok != NULL; tok = strtok_r(NULL, "+", &tok_ctx)) { char *fname = strtok(tok, "()"); char *section __attribute__((unused)) = strtok(NULL, "()"); /* Not sure why some layouts have a ":n" suffix (where * 'n' is a number, e.g. "us:2") */ fname = strtok(fname, ":"); /* Assume all language layouts are two-letters */ if (strlen(fname) != 2) continue; /* But make sure to ignore "pc" :) */ if (strcmp(tok, "pc") == 0) continue; if (layout_idx >= ret.count) { printf("layout vs group name count mismatch: %zd > %zd\n", layout_idx + 1, ret.count); goto err; } char *sym = strdup(fname); if (sym == NULL) { printf("failed to allocate memory for symbol: %s (%d)", strerror(errno), errno); goto err; } ret.layouts[layout_idx++].symbol = sym; } if (layout_idx != ret.count) { printf("layout vs group name count mismatch: %zd != %zd\n", layout_idx, ret.count); goto err; } free(symbols); free(reply); return ret; err: free(symbols); free(reply); free_layouts(ret); return (struct layouts){.count = -1}; } static int get_current_layout(xcb_connection_t *conn) { xcb_generic_error_t *err; xcb_xkb_get_state_cookie_t cookie = xcb_xkb_get_state( conn, XCB_XKB_ID_USE_CORE_KBD); xcb_xkb_get_state_reply_t *reply = xcb_xkb_get_state_reply( conn, cookie, &err); if (err != NULL) { printf("%d: %s\n", err->error_code, "failed to get XKB state"); free(err); return -1; } int ret = reply->group; free(reply); return ret; } static bool register_for_events(xcb_connection_t *conn) { xcb_void_cookie_t cookie = xcb_xkb_select_events_checked( conn, XCB_XKB_ID_USE_CORE_KBD, ( XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY | XCB_XKB_EVENT_TYPE_STATE_NOTIFY | XCB_XKB_EVENT_TYPE_MAP_NOTIFY | XCB_XKB_EVENT_TYPE_INDICATOR_STATE_NOTIFY ), 0, ( XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY | XCB_XKB_EVENT_TYPE_STATE_NOTIFY | XCB_XKB_EVENT_TYPE_MAP_NOTIFY | XCB_XKB_EVENT_TYPE_INDICATOR_STATE_NOTIFY ), 0, 0, NULL); xcb_generic_error_t *err = xcb_request_check(conn, cookie); if (err != NULL) { printf("%d: %s\n", err->error_code, "failed to register for events"); free(err); return false; } return true; } static bool event_loop(int abort_fd, const struct bar *bar, struct private *m, xcb_connection_t *conn, int xkb_event_base) { bool ret = false; bool has_error = false; const int xcb_fd = xcb_get_file_descriptor(conn); assert(xcb_fd >= 0); while (!has_error) { struct pollfd pfds[] = { {.fd = abort_fd, .events = POLLIN }, {.fd = xcb_fd, .events = POLLIN | POLLHUP } }; /* Use poll() since xcb_wait_for_events() doesn't return on signals */ int pret = poll(pfds, sizeof(pfds) / sizeof(pfds[0]), -1); if (pfds[0].revents & POLLIN) { ret = true; break; } assert(pret == 1 && "unexpected poll(3) return value"); if (pfds[1].revents & POLLHUP) { puts("I/O error, server disconnect?"); break; } assert(pfds[1].revents & POLLIN && "POLLIN not set"); /* * Note: poll() might have returned *before* the entire event * has been received, and thus we might block here. Hopefully * not for long though... */ for (xcb_generic_event_t *_evt = xcb_wait_for_event(conn); _evt != NULL; _evt = xcb_poll_for_event(conn)) { if (_evt->response_type != xkb_event_base) { printf("non-XKB event ignored: %d\n", _evt->response_type); free(_evt); continue; } switch(_evt->pad0) { default: printf("unimplemented XKB event: %d\n", _evt->pad0); break; case XCB_XKB_NEW_KEYBOARD_NOTIFY: { int current = get_current_layout(conn); if (current == -1) { has_error = true; break; } struct layouts layouts = get_layouts(conn); if (layouts.count == -1) { has_error = true; break; } if (current < layouts.count) { free_layouts(m->layouts); m->layouts = layouts;//json_all(layouts, current); m->current = current; bar->refresh(bar); } else { /* Can happen while transitioning to a new map */ free_layouts(layouts); } //free_layouts(layouts); break; } case XCB_XKB_STATE_NOTIFY: { const xcb_xkb_state_notify_event_t *evt = (const xcb_xkb_state_notify_event_t *)_evt; if (evt->changed & XCB_XKB_STATE_PART_GROUP_STATE) { ;//json_layout_change(evt->group); m->current = evt->group; bar->refresh(bar); } break; } case XCB_XKB_MAP_NOTIFY: printf("map event unimplemented\n"); break; case XCB_XKB_INDICATOR_STATE_NOTIFY: printf("indicator state event unimplemented\n"); break; } free(_evt); } } return ret; } static bool talk_to_xkb(int abort_fd, const struct bar *bar, struct private *m, xcb_connection_t *conn) { if (!xkb_enable(conn)) return false; if (!register_for_events(conn)) return false; int xkb_event_base = get_xkb_event_base(conn); if (xkb_event_base == -1) return false; int current = get_current_layout(conn); if (current == -1) return false; struct layouts layouts = get_layouts(conn); if (layouts.count == -1) return false; if (current >= layouts.count) { printf("current layout index: %d >= %zd\n", current, layouts.count); free_layouts(layouts); return false; } //json_all(layouts, current); m->layouts = layouts; m->current = current; bar->refresh(bar); //free_layouts(layouts); #if 0 /* Signal handlers (disconnects from X) */ const struct sigaction sa = { .sa_handler = &sighandler }; if (sigaction(SIGINT, &sa, NULL) == -1 || sigaction(SIGTERM, &sa, NULL) == -1) { json_errno("failed to register signal handlers"); return false; } #endif return event_loop(abort_fd, bar, m, conn, xkb_event_base); } static int run(struct module_run_context *ctx) { //struct private *m = ctx->module->private; xcb_connection_t *conn = xcb_connect(NULL, NULL); if (conn == NULL) { puts("failed to connect to X server"); return EXIT_FAILURE; } int ret = talk_to_xkb(ctx->abort_fd, ctx->module->bar, ctx->module->private, conn) ? EXIT_SUCCESS : EXIT_FAILURE; xcb_disconnect(conn); return ret; } struct module * module_xkb(struct particle *label) { struct private *m = malloc(sizeof(*m)); m->label = label; struct module *mod = malloc(sizeof(*mod)); mod->bar = NULL; mod->private = m; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->begin_expose = &module_default_begin_expose; mod->expose = &module_default_expose; mod->end_expose = &module_default_end_expose; return mod; }