forked from external/yambar
397 lines
10 KiB
C
397 lines
10 KiB
C
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#define LOG_MODULE "sway-xkb"
|
|
#define LOG_ENABLE_DBG 0
|
|
#include "../bar/bar.h"
|
|
#include "../config-verify.h"
|
|
#include "../config.h"
|
|
#include "../log.h"
|
|
#include "../particles/dynlist.h"
|
|
#include "../plugin.h"
|
|
|
|
#include "i3-common.h"
|
|
#include "i3-ipc.h"
|
|
|
|
#define max(x, y) ((x) > (y) ? (x) : (y))
|
|
|
|
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 const char *
|
|
description(const struct module *mod)
|
|
{
|
|
return "sway-xkb";
|
|
}
|
|
|
|
static struct exposable *
|
|
content(struct module *mod)
|
|
{
|
|
const struct private *m = mod->private;
|
|
|
|
mtx_lock(&mod->lock);
|
|
|
|
assert(m->num_existing_inputs <= m->num_inputs);
|
|
struct exposable *particles[max(m->num_existing_inputs, 1)];
|
|
|
|
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 sock, 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 json_object *type;
|
|
if (!json_object_object_get_ex(obj, "type", &type))
|
|
return false;
|
|
if (strcmp(json_object_get_string(type), "keyboard") != 0) {
|
|
LOG_DBG("ignoring non-keyboard input '%s'", id);
|
|
continue;
|
|
}
|
|
|
|
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 && !maybe_input->exists) {
|
|
input = maybe_input;
|
|
|
|
LOG_DBG("adding: %s", id);
|
|
|
|
mtx_lock(&mod->lock);
|
|
input->exists = true;
|
|
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 sock, 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 json_object *input_type;
|
|
if (!json_object_object_get_ex(obj, "type", &input_type))
|
|
return false;
|
|
if (strcmp(json_object_get_string(input_type), "keyboard") != 0) {
|
|
LOG_DBG("ignoring non-keyboard input '%s'", id);
|
|
return true;
|
|
}
|
|
|
|
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) {
|
|
if (input->exists) {
|
|
LOG_DBG("removing: %s", id);
|
|
|
|
mtx_lock(&mod->lock);
|
|
input->exists = false;
|
|
m->num_existing_inputs--;
|
|
m->dirty = true;
|
|
mtx_unlock(&mod->lock);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (is_added) {
|
|
if (!input->exists) {
|
|
LOG_DBG("adding: %s", id);
|
|
|
|
mtx_lock(&mod->lock);
|
|
input->exists = true;
|
|
m->num_existing_inputs++;
|
|
m->dirty = true;
|
|
mtx_unlock(&mod->lock);
|
|
}
|
|
|
|
/* “fallthrough”, to query current/active 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 | SOCK_CLOEXEC, 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;
|
|
mod->description = &description;
|
|
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_unsigned},
|
|
{"left-spacing", false, &conf_verify_unsigned},
|
|
{"right-spacing", false, &conf_verify_unsigned},
|
|
{"identifiers", true, &verify_identifiers},
|
|
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
|