module/xkb: implement indicator state

For now, we only expose caps-, num- and scroll-lock, as booleans.
This commit is contained in:
Daniel Eklöf 2019-01-19 12:19:45 +01:00
parent 7bb849bdf4
commit 925dfb736a

View file

@ -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;