diff --git a/modules/i3-common.c b/modules/i3-common.c index 2f3e413..ec5c53c 100644 --- a/modules/i3-common.c +++ b/modules/i3-common.c @@ -265,6 +265,10 @@ i3_receive_loop(int abort_fd, int sock, pkt_handler = cbs->reply_inputs; break; + /* + * Events + */ + case I3_IPC_EVENT_WORKSPACE: pkt_handler = cbs->event_workspace; break; @@ -290,6 +294,11 @@ i3_receive_loop(int abort_fd, int sock, pkt_handler = cbs->event_tick; break; + /* Sway extensions */ + case ((1<<31) | 21): /* IPC_EVENT_INPUT */ + pkt_handler = cbs->event_input; + break; + default: LOG_ERR("unimplemented IPC reply type: %d", hdr->type); pkt_handler = NULL; diff --git a/modules/i3-common.h b/modules/i3-common.h index 40948cb..e3d94d1 100644 --- a/modules/i3-common.h +++ b/modules/i3-common.h @@ -38,6 +38,9 @@ struct i3_ipc_callbacks { i3_ipc_callback_t event_binding; i3_ipc_callback_t event_shutdown; i3_ipc_callback_t event_tick; + + /* Sway extensions */ + i3_ipc_callback_t event_input; }; bool i3_receive_loop( diff --git a/modules/meson.build b/modules/meson.build index dc6c67f..b156e18 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -19,6 +19,7 @@ deps = { 'mpd': [[], [mpd]], 'network': [[], []], 'removables': [[], [dynlist, udev]], + 'sway_xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json]], } if backend_x11 diff --git a/modules/sway_xkb.c b/modules/sway_xkb.c new file mode 100644 index 0000000..87b7db2 --- /dev/null +++ b/modules/sway_xkb.c @@ -0,0 +1,366 @@ +#include + +#define LOG_MODULE "sway-xkb" +#define LOG_ENABLE_DBG 0 +#include "../log.h" +#include "../bar/bar.h" +#include "../config-verify.h" +#include "../config.h" +#include "../particles/dynlist.h" +#include "../plugin.h" + +#include "i3-ipc.h" +#include "i3-common.h" + +struct input { + bool exists; + char *identifier; + char *layout; +}; + +struct private { + struct particle *template; + int left_spacing; + int right_spacing; + + size_t num_inputs; + size_t num_existing_inputs; + struct input *inputs; + + bool dirty; +}; + +static void +free_input(struct input *input) +{ + free(input->identifier); + free(input->layout); +} + +static void +destroy(struct module *mod) +{ + struct private *m = mod->private; + m->template->destroy(m->template); + + for (size_t i = 0; i < m->num_inputs; i++) + free_input(&m->inputs[i]); + free(m->inputs); + + free(m); + module_default_destroy(mod); +} + +static struct exposable * +content(struct module *mod) +{ + const struct private *m = mod->private; + + mtx_lock(&mod->lock); + + struct exposable *particles[m->num_existing_inputs]; + for (size_t i = 0, j = 0; i < m->num_inputs; i++) { + const struct input *input = &m->inputs[i]; + + if (!input->exists) + continue; + + struct tag_set tags = { + .tags = (struct tag *[]){ + tag_new_string(mod, "id", input->identifier), + tag_new_string(mod, "layout", input->layout), + }, + .count = 2, + }; + + particles[j++] = m->template->instantiate(m->template, &tags); + tag_set_destroy(&tags); + } + + mtx_unlock(&mod->lock); + return dynlist_exposable_new( + particles, m->num_existing_inputs, m->left_spacing, m->right_spacing); +} + +static bool +handle_input_reply(int type, const struct json_object *json, void *_mod) +{ + struct module *mod = _mod; + struct private *m = mod->private; + + assert(m->num_existing_inputs == 0); + + for (size_t i = 0; i < json_object_array_length(json); i++) { + struct json_object *obj = json_object_array_get_idx(json, i); + + struct json_object *identifier; + if (!json_object_object_get_ex(obj, "identifier", &identifier)) + return false; + + const char *id = json_object_get_string(identifier); + struct input *input = NULL; + for (size_t i = 0; i < m->num_inputs; i++) { + struct input *maybe_input = &m->inputs[i]; + if (strcmp(maybe_input->identifier, id) == 0) { + input = maybe_input; + input->exists = true; + + mtx_lock(&mod->lock); + m->num_existing_inputs++; + mtx_unlock(&mod->lock); + break; + } + } + + if (input == NULL) { + LOG_DBG("ignoring xkb_layout change for input '%s'", id); + continue; + } + + /* Get current/active layout */ + struct json_object *layout; + if (!json_object_object_get_ex( + obj, "xkb_active_layout_name", &layout)) + return false; + + const char *new_layout_str = json_object_get_string(layout); + + mtx_lock(&mod->lock); + + assert(input != NULL); + free(input->layout); + input->layout = strdup(new_layout_str); + + m->dirty = true; + mtx_unlock(&mod->lock); + } + + return true; +} + +static bool +handle_input_event(int type, const struct json_object *json, void *_mod) +{ + struct module *mod = _mod; + struct private *m = mod->private; + + struct json_object *change; + if (!json_object_object_get_ex(json, "change", &change)) + return false; + + const char *change_str = json_object_get_string(change); + bool is_layout = strcmp(change_str, "xkb_layout") == 0; + bool is_removed = strcmp(change_str, "removed") == 0; + bool is_added = strcmp(change_str, "added") == 0; + + if (!is_layout && !is_removed && !is_added) + return true; + + struct json_object *obj; + if (!json_object_object_get_ex(json, "input", &obj)) + return false; + + struct json_object *identifier; + if (!json_object_object_get_ex(obj, "identifier", &identifier)) + return false; + + const char *id = json_object_get_string(identifier); + struct input *input = NULL; + for (size_t i = 0; i < m->num_inputs; i++) { + struct input *maybe_input = &m->inputs[i]; + if (strcmp(maybe_input->identifier, id) == 0) { + input = maybe_input; + break; + } + } + + if (input == NULL) { + LOG_DBG("ignoring xkb_layout change for input '%s'", id); + return true; + } + + if (is_removed || is_added) { + mtx_lock(&mod->lock); + assert((is_removed && input->exists) || + (is_added && !input->exists)); + input->exists = !input->exists; + + m->num_existing_inputs += is_added ? 1 : -1; + m->dirty = true; + mtx_unlock(&mod->lock); + + if (is_removed) + return true; + + /* let is_added fall through, to update layout */ + } + + /* Get current/active layout */ + struct json_object *layout; + if (!json_object_object_get_ex( + obj, "xkb_active_layout_name", &layout)) + return false; + + const char *new_layout_str = json_object_get_string(layout); + + mtx_lock(&mod->lock); + + assert(input != NULL); + free(input->layout); + input->layout = strdup(new_layout_str); + + m->dirty = true; + mtx_unlock(&mod->lock); + return true; +} + +static void +burst_done(void *_mod) +{ + struct module *mod = _mod; + struct private *m = mod->private; + + if (m->dirty) { + m->dirty = false; + mod->bar->refresh(mod->bar); + } +} + +static int +run(struct module *mod) +{ + if (getenv("SWAYSOCK") == NULL) { + LOG_ERR("sway does not appear to be running"); + return 1; + } + + struct sockaddr_un addr; + if (!i3_get_socket_address(&addr)) + return 1; + + int sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) { + LOG_ERRNO("failed to create UNIX socket"); + return 1; + } + + int r = connect(sock, (const struct sockaddr *)&addr, sizeof(addr)); + if (r == -1) { + LOG_ERRNO("failed to connect to i3 socket"); + close(sock); + return 1; + } + + i3_send_pkg(sock, 100 /* IPC_GET_INPUTS */, NULL); + i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[\"input\"]"); + + static const struct i3_ipc_callbacks callbacks = { + .burst_done = &burst_done, + .reply_inputs = &handle_input_reply, + .event_input = &handle_input_event, + }; + + bool ret = i3_receive_loop(mod->abort_fd, sock, &callbacks, mod); + close(sock); + return ret ? 0 : 1; +} + +static struct module * +sway_xkb_new(struct particle *template, const char *identifiers[], + size_t num_identifiers, int left_spacing, int right_spacing) +{ + struct private *m = calloc(1, sizeof(*m)); + m->template = template; + m->left_spacing = left_spacing; + m->right_spacing = right_spacing; + + m->num_inputs = num_identifiers; + m->inputs = calloc(num_identifiers, sizeof(m->inputs[0])); + + for (size_t i = 0; i < num_identifiers; i++) { + m->inputs[i].identifier = strdup(identifiers[i]); + m->inputs[i].layout = strdup(""); + } + + struct module *mod = module_common_new(); + mod->private = m; + mod->run = &run; + mod->destroy = &destroy; + mod->content = &content; + return mod; +} + +static struct module * +from_conf(const struct yml_node *node, struct conf_inherit inherited) +{ + const struct yml_node *c = yml_get_value(node, "content"); + + const struct yml_node *spacing = yml_get_value(node, "spacing"); + const struct yml_node *left_spacing = yml_get_value(node, "left-spacing"); + const struct yml_node *right_spacing = yml_get_value(node, "right-spacing"); + + int left = spacing != NULL ? yml_value_as_int(spacing) : + left_spacing != NULL ? yml_value_as_int(left_spacing) : 0; + int right = spacing != NULL ? yml_value_as_int(spacing) : + right_spacing != NULL ? yml_value_as_int(right_spacing) : 0; + + const struct yml_node *ids = yml_get_value(node, "identifiers"); + const size_t num_ids = yml_list_length(ids); + const char *identifiers[num_ids]; + + size_t i = 0; + for (struct yml_list_iter it = yml_list_iter(ids); + it.node != NULL; + yml_list_next(&it), i++) + { + identifiers[i] = yml_value_as_string(it.node); + } + + return sway_xkb_new( + conf_to_particle(c, inherited), identifiers, num_ids, left, right); +} + +static bool +verify_identifiers(keychain_t *chain, const struct yml_node *node) +{ + if (!yml_is_list(node)) { + LOG_ERR("%s: identifiers must be a list of strings", + conf_err_prefix(chain, node)); + return false; + } + + for (struct yml_list_iter it = yml_list_iter(node); + it.node != NULL; + yml_list_next(&it)) + { + if (!conf_verify_string(chain, it.node)) + return false; + } + + return true; +} + +static bool +verify_conf(keychain_t *chain, const struct yml_node *node) +{ + static const struct attr_info attrs[] = { + {"spacing", false, &conf_verify_int}, + {"left-spacing", false, &conf_verify_int}, + {"right-spacing", false, &conf_verify_int}, + {"identifiers", true, &verify_identifiers}, + {"content", true, &conf_verify_particle}, + {"anchors", false, NULL}, + MODULE_COMMON_ATTRS, + }; + + return conf_verify_dict(chain, node, attrs); +} + +const struct module_iface module_sway_xkb_iface = { + .verify_conf = &verify_conf, + .from_conf = &from_conf, +}; + +#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) +extern const struct module_iface iface __attribute__((weak, alias("module_sway_xkb_iface"))) ; +#endif diff --git a/plugin.c b/plugin.c index 6ca4719..6eac1ec 100644 --- a/plugin.c +++ b/plugin.c @@ -40,6 +40,7 @@ EXTERN_MODULE(label); EXTERN_MODULE(mpd); EXTERN_MODULE(network); EXTERN_MODULE(removables); +EXTERN_MODULE(sway_xkb); EXTERN_MODULE(xkb); EXTERN_MODULE(xwindow); @@ -112,6 +113,7 @@ init(void) REGISTER_CORE_MODULE(mpd, mpd); REGISTER_CORE_MODULE(network, network); REGISTER_CORE_MODULE(removables, removables); + REGISTER_CORE_MODULE(sway-xkb, sway_xkb); #if defined(HAVE_PLUGIN_xkb) REGISTER_CORE_MODULE(xkb, xkb); #endif