yambar/modules/xkb/xkb.c
Daniel Eklöf a3eb7ebc08 bar: wait for all modules to have started up before continuing
After starting all the module threads, wait for all modules to have
signalled "ready" before continuing.

This will allow modules to do initial setup, and knowing that
content() will *not* be called until they've signalled "ready".
2018-12-19 19:41:25 +01:00

466 lines
12 KiB
C

#include "xkb.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <xcb/xcb.h>
#include <xcb/xkb.h>
#define LOG_MODULE "xkb"
#include "../../log.h"
#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);
module_default_destroy(mod);
}
static struct exposable *
content(struct module *mod)
{
const struct private *m = mod->private;
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) {
LOG_ERR("failed to get XKB extension data");
return -1;
}
if (!reply->present) {
LOG_ERR("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) {
LOG_ERR("failed to get group names and symbols (%d)", err->error_code);
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) {
LOG_ERR("failed to get atom name (%d)", 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) {
LOG_ERR("failed to get atom name (%d)", 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) {
LOG_ERR("layout vs group name count mismatch: %zd > %zd",
layout_idx + 1, ret.count);
goto err;
}
char *sym = strdup(fname);
ret.layouts[layout_idx++].symbol = sym;
}
if (layout_idx != ret.count) {
LOG_ERR("layout vs group name count mismatch: %zd != %zd",
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) {
LOG_ERR("failed to get XKB state (%d)", err->error_code);
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) {
LOG_ERR("failed to register for events (%d)", err->error_code);
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) {
LOG_WARN("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) {
LOG_WARN("non-XKB event ignored: %d", _evt->response_type);
free(_evt);
continue;
}
switch(_evt->pad0) {
default:
LOG_WARN("unimplemented XKB event: %d", _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:
LOG_WARN("map event unimplemented");
break;
case XCB_XKB_INDICATOR_STATE_NOTIFY:
LOG_WARN("indicator state event unimplemented");
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) {
LOG_ERR("current layout index: %d >= %zd", 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) {
LOG_ERR("failed to connect to X server");
return EXIT_FAILURE;
}
/* TODO: move this to later */
write(ctx->ready_fd, &(uint64_t){1}, sizeof(uint64_t));
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;
m->current = 0;
m->layouts.count = 0;
m->layouts.layouts = NULL;
struct module *mod = module_common_new();
mod->private = m;
mod->run = &run;
mod->destroy = &destroy;
mod->content = &content;
return mod;
}