diff --git a/modules/xkb.c b/modules/xkb.c index 133ea57..9a9b279 100644 --- a/modules/xkb.c +++ b/modules/xkb.c @@ -21,32 +21,51 @@ struct layout { }; struct layouts { - ssize_t count; + size_t count; struct layout *layouts; }; +struct indicators { + size_t count; + char **names; +}; + struct private { struct particle *label; + struct indicators indicators; struct layouts layouts; size_t current; + + bool caps_lock; + bool num_lock; + bool scroll_lock; }; static void free_layouts(struct layouts layouts) { - for (ssize_t i = 0; i < layouts.count; i++) { + for (size_t i = 0; i < layouts.count; i++) { free(layouts.layouts[i].name); free(layouts.layouts[i].symbol); } free(layouts.layouts); } +static void +free_indicators(struct indicators indicators) +{ + for (size_t i = 0; i < indicators.count; i++) + free(indicators.names[i]); + free(indicators.names); +} + static void destroy(struct module *mod) { struct private *m = mod->private; m->label->destroy(m->label); free_layouts(m->layouts); + free_indicators(m->indicators); free(m); module_default_destroy(mod); } @@ -70,8 +89,11 @@ content(struct module *mod) .tags = (struct tag *[]){ tag_new_string(mod, "name", name), tag_new_string(mod, "symbol", symbol), + tag_new_bool(mod, "caps_lock", m->caps_lock), + tag_new_bool(mod, "num_lock", m->num_lock), + tag_new_bool(mod, "scroll_lock", m->scroll_lock), }, - .count = 2, + .count = 5, }; mtx_unlock(&mod->lock); @@ -92,8 +114,19 @@ xkb_enable(xcb_connection_t *conn) 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"); + if (err != NULL) { + LOG_ERR("failed to query for XKB extension: %s", xcb_error(err)); + free(err); + free(reply); + return false; + } + + if (!reply->supported) { + LOG_ERR("XKB extension is not supported"); + free(reply); + return false; + } + free(reply); return true; } @@ -117,25 +150,25 @@ get_xkb_event_base(xcb_connection_t *conn) return reply->first_event; } -static struct layouts -get_layouts(xcb_connection_t *conn) +static bool +get_layouts_and_indicators(xcb_connection_t *conn, struct layouts *layouts, + struct indicators *indicators) { - 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_NAME_DETAIL_SYMBOLS | + XCB_XKB_NAME_DETAIL_INDICATOR_NAMES); xcb_xkb_get_names_reply_t *reply = xcb_xkb_get_names_reply( conn, cookie, &err); if (err != NULL) { - LOG_ERR("failed to get group names and symbols: %s", xcb_error(err)); + LOG_ERR("failed to get layouts and indicators: %s", xcb_error(err)); free(err); - return (struct layouts){.count = -1}; + return false; } xcb_xkb_get_names_value_list_t vlist; @@ -146,47 +179,78 @@ get_layouts(xcb_connection_t *conn) 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])); + layouts->count = xcb_xkb_get_names_value_list_groups_length(reply, &vlist); + layouts->layouts = calloc(layouts->count, sizeof(layouts->layouts[0])); + + /* Number of indicators */ + indicators->count = xcb_xkb_get_names_value_list_indicator_names_length( + reply, &vlist); + indicators->names = calloc(indicators->count, sizeof(indicators->names[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++) + xcb_get_atom_name_cookie_t group_name_cookies[layouts->count]; + for (size_t i = 0; i < layouts->count; i++) group_name_cookies[i] = xcb_get_atom_name(conn, vlist.groups[i]); + xcb_get_atom_name_cookie_t indicator_cookies[indicators->count]; + for (size_t i = 0; i < indicators->count; i++) + indicator_cookies[i] = xcb_get_atom_name(conn, vlist.indicatorNames[i]); + char *symbols = NULL; /* 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) { - LOG_ERR("failed to get atom name: %s", xcb_error(err)); + LOG_ERR("failed to get 'symbols' atom name: %s", xcb_error(err)); free(err); goto err; } - symbols = strndup(xcb_get_atom_name_name(atom_name), - xcb_get_atom_name_name_length(atom_name)); + symbols = strndup( + xcb_get_atom_name_name(atom_name), + xcb_get_atom_name_name_length(atom_name)); + LOG_DBG("symbols: %s", symbols); free(atom_name); /* Get layout long names (e.g. "English (US)") */ - for (ssize_t i = 0; i < ret.count; i++) { + for (size_t i = 0; i < layouts->count; i++) { atom_name = xcb_get_atom_name_reply(conn, group_name_cookies[i], &err); if (err != NULL) { - LOG_ERR("failed to get atom name: %s", xcb_error(err)); + LOG_ERR("failed to get 'group' atom name: %s", xcb_error(err)); free(err); goto err; } - ret.layouts[i].name = strndup(xcb_get_atom_name_name(atom_name), - xcb_get_atom_name_name_length(atom_name)); + layouts->layouts[i].name = strndup( + xcb_get_atom_name_name(atom_name), + xcb_get_atom_name_name_length(atom_name)); + + LOG_DBG("layout #%zd: long name: %s", i, layouts->layouts[i].name); + free(atom_name); + } + + /* Indicator names e.g. "Caps Lock", "Num Lock" */ + for (size_t i = 0; i < indicators->count; i++) { + atom_name = xcb_get_atom_name_reply(conn, indicator_cookies[i], &err); + if (err != NULL) { + LOG_ERR("failed to get 'indicator' atom name: %s", xcb_error(err)); + free(err); + goto err; + } + + indicators->names[i] = strndup( + xcb_get_atom_name_name(atom_name), + xcb_get_atom_name_name_length(atom_name)); + + LOG_DBG("indicator #%zd: %s", i, indicators->names[i]); free(atom_name); } /* e.g. pc+us+inet(evdev)+group(..) */ - ssize_t layout_idx = 0; + size_t layout_idx = 0; for (char *tok_ctx = NULL, *tok = strtok_r(symbols, "+", &tok_ctx); tok != NULL; tok = strtok_r(NULL, "+", &tok_ctx)) { @@ -206,32 +270,33 @@ get_layouts(xcb_connection_t *conn) if (strcmp(tok, "pc") == 0) continue; - if (layout_idx >= ret.count) { + if (layout_idx >= layouts->count) { LOG_ERR("layout vs group name count mismatch: %zd > %zd", - layout_idx + 1, ret.count); + layout_idx + 1, layouts->count); goto err; } char *sym = strdup(fname); - ret.layouts[layout_idx++].symbol = sym; + layouts->layouts[layout_idx++].symbol = sym; + LOG_DBG("layout #%zd: short name: %s", layout_idx - 1, sym); } - if (layout_idx != ret.count) { + if (layout_idx != layouts->count) { LOG_ERR("layout vs group name count mismatch: %zd != %zd", - layout_idx, ret.count); + layout_idx, layouts->count); goto err; } free(symbols); free(reply); - - return ret; + return true; err: free(symbols); free(reply); - free_layouts(ret); - return (struct layouts){.count = -1}; + free_layouts(*layouts); + free_indicators(*indicators); + return false; } static int @@ -255,6 +320,28 @@ get_current_layout(xcb_connection_t *conn) return ret; } +static uint32_t +get_indicator_state(xcb_connection_t *conn) +{ + xcb_generic_error_t *err; + xcb_xkb_get_indicator_state_cookie_t cookie = xcb_xkb_get_indicator_state( + conn, XCB_XKB_ID_USE_CORE_KBD); + xcb_xkb_get_indicator_state_reply_t *reply = xcb_xkb_get_indicator_state_reply( + conn, cookie, &err); + + if (err != NULL) { + LOG_ERR("failed to get indicator state: %s", xcb_error(err)); + free(err); + return (uint32_t)-1; + } + + uint32_t state = reply->state; + LOG_DBG("indicator state: 0x%08x", state); + + free(reply); + return state; +} + static bool register_for_events(xcb_connection_t *conn) { @@ -345,8 +432,9 @@ event_loop(struct module *mod, xcb_connection_t *conn, int xkb_event_base) break; } - struct layouts layouts = get_layouts(conn); - if (layouts.count == -1) { + struct layouts layouts; + struct indicators indicators; + if (!get_layouts_and_indicators(conn, &layouts, &indicators)) { has_error = true; break; } @@ -354,15 +442,15 @@ event_loop(struct module *mod, xcb_connection_t *conn, int xkb_event_base) if (current < layouts.count) { mtx_lock(&mod->lock); free_layouts(m->layouts); - m->layouts = layouts; m->current = current; + m->layouts = layouts; + m->indicators = indicators; mtx_unlock(&mod->lock); bar->refresh(bar); } else { /* Can happen while transitioning to a new map */ - mtx_lock(&mod->lock); free_layouts(layouts); - mtx_unlock(&mod->lock); + free_indicators(indicators); } break; @@ -386,10 +474,50 @@ event_loop(struct module *mod, xcb_connection_t *conn, int xkb_event_base) LOG_WARN("map event unimplemented"); break; - case XCB_XKB_INDICATOR_STATE_NOTIFY: - LOG_WARN("indicator state event unimplemented"); + case XCB_XKB_INDICATOR_STATE_NOTIFY: { + const xcb_xkb_indicator_state_notify_event_t *evt = + (const xcb_xkb_indicator_state_notify_event_t *)_evt; + +#if 0 + size_t idx = __builtin_ctz(evt->stateChanged); + LOG_ERR("%zu", idx); + if (idx < m->indicators.count) + LOG_ERR("%s", m->indicators.names[idx]); +#endif + + /* TODO: bit count evt->stateChanged instead */ + bool need_refresh = false; + for (size_t i = 0; i < m->indicators.count; i++) { + bool changed = (evt->stateChanged >> i) & 1; + if (!changed) + continue; + + bool enabled = (evt->state >> i) & 1; + LOG_DBG("%s: %s", m->indicators.names[i], + enabled ? "enabled" : "disabled"); + + const char *name = m->indicators.names[i]; + bool is_caps = strcasecmp(name, "caps lock") == 0; + bool is_num = strcasecmp(name, "num lock") == 0; + + if (is_caps || is_num) { + mtx_lock(&mod->lock); + + if (is_caps) + m->caps_lock = enabled; + else if (is_num) + m->num_lock = enabled; + + mtx_unlock(&mod->lock); + need_refresh = true; + } + } + + if (need_refresh) + bar->refresh(bar); break; } + } free(_evt); } @@ -417,19 +545,54 @@ talk_to_xkb(struct module *mod, xcb_connection_t *conn) if (current == -1) return false; - struct layouts layouts = get_layouts(conn); - if (layouts.count == -1) + /* Bitmask, one bit for every indicator available */ + uint32_t indicator_state = get_indicator_state(conn); + if (indicator_state == (uint32_t)-1) + return false; + + struct layouts layouts; + struct indicators indicators; + if (!get_layouts_and_indicators(conn, &layouts, &indicators)) return false; if (current >= layouts.count) { LOG_ERR("current layout index: %d >= %zd", current, layouts.count); free_layouts(layouts); + free_indicators(indicators); return false; } + bool caps_lock = false, num_lock = false, scroll_lock = false; + + /* Lookup initial state of caps-, num- and scroll lock */ + for (size_t i = 0; i < indicators.count; i++) { + const char *name = indicators.names[i]; + bool enabled = (indicator_state >> i) & 1; + + bool is_caps = strcasecmp(name, "caps lock") == 0; + bool is_num = strcasecmp(name, "num lock") == 0; + bool is_scroll = strcasecmp(name, "scroll lock") == 0; + + if (!(is_caps || is_num || is_scroll)) + continue; + + if (is_caps) + caps_lock = enabled; + else if (is_num) + num_lock = enabled; + else if (is_scroll) + scroll_lock = enabled; + + LOG_DBG("%s: %s", name, enabled ? "enabled" : "disabled"); + } + mtx_lock(&mod->lock); m->layouts = layouts; m->current = current; + m->indicators = indicators; + m->caps_lock = caps_lock; + m->num_lock = num_lock; + m->scroll_lock = scroll_lock; mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); @@ -459,6 +622,9 @@ xkb_new(struct particle *label) m->current = 0; m->layouts.count = 0; m->layouts.layouts = NULL; + m->caps_lock = false; + m->num_lock = false; + m->scroll_lock = false; struct module *mod = module_common_new(); mod->private = m;