yambar/modules/xwindow.c
Daniel Eklöf 9944a8f972 modules: don't assume module content is a dictionary
This is done by having each module implement a top-level verifier
function.
2019-01-13 11:54:57 +01:00

339 lines
8.9 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <threads.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <xcb/xcb.h>
#include <xcb/xcb_event.h>
#define LOG_MODULE "xkb"
#include "../log.h"
#include "../bar.h"
#include "../config.h"
#include "../xcb.h"
struct private {
/* Accessed from bar thread only */
struct particle *label;
/* Accessed from both our thread, and the bar thread */
char *application;
char *title;
/* Accessed from our thread only */
xcb_connection_t *conn;
xcb_window_t root_win;
xcb_window_t monitor_win;
xcb_window_t active_win;
};
static void
update_active_window(struct private *m)
{
if (m->active_win != 0) {
xcb_change_window_attributes(
m->conn, m->active_win, XCB_CW_EVENT_MASK,
(const uint32_t []){XCB_EVENT_MASK_NO_EVENT});
m->active_win = 0;
}
xcb_get_property_cookie_t c = xcb_get_property(
m->conn, 0, m->root_win, _NET_ACTIVE_WINDOW, XCB_ATOM_WINDOW, 0, 32);
xcb_generic_error_t *e;
xcb_get_property_reply_t *r = xcb_get_property_reply(m->conn, c, &e);
if (e != NULL) {
free(e);
free(r);
return;
}
assert(sizeof(m->active_win) == xcb_get_property_value_length(r));
memcpy(&m->active_win, xcb_get_property_value(r), sizeof(m->active_win));
free(r);
if (m->active_win != 0) {
xcb_change_window_attributes(
m->conn, m->active_win, XCB_CW_EVENT_MASK,
(const uint32_t []){XCB_EVENT_MASK_PROPERTY_CHANGE});
}
}
static void
update_application(struct module *mod)
{
struct private *m = mod->private;
mtx_lock(&mod->lock);
free(m->application);
m->application = NULL;
mtx_unlock(&mod->lock);
if (m->active_win == 0)
return;
xcb_get_property_cookie_t c = xcb_get_property(
m->conn, 0, m->active_win, _NET_WM_PID, XCB_ATOM_CARDINAL, 0, 32);
xcb_generic_error_t *e;
xcb_get_property_reply_t *r = xcb_get_property_reply(m->conn, c, &e);
if (e != NULL) {
free(e);
free(r);
return;
}
if (xcb_get_property_value_length(r) == 0) {
free(r);
return;
}
uint32_t pid;
assert(xcb_get_property_value_length(r) == sizeof(pid));
memcpy(&pid, xcb_get_property_value(r), sizeof(pid));
free(r);
char path[1024];
snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
int fd = open(path, O_RDONLY);
if (fd == -1)
return;
char cmd[1024] = {0};
ssize_t bytes = read(fd, cmd, sizeof(cmd) - 1);
close(fd);
if (bytes == -1)
return;
mtx_lock(&mod->lock);
m->application = strdup(basename(cmd));
mtx_unlock(&mod->lock);
}
static void
update_title(struct module *mod)
{
struct private *m = mod->private;
mtx_lock(&mod->lock);
free(m->title);
m->title = NULL;
mtx_unlock(&mod->lock);
if (m->active_win == 0)
return;
xcb_get_property_cookie_t c1 = xcb_get_property(
m->conn, 0, m->active_win, _NET_WM_VISIBLE_NAME, UTF8_STRING, 0, 1000);
xcb_get_property_cookie_t c2 = xcb_get_property(
m->conn, 0, m->active_win, _NET_WM_NAME, UTF8_STRING, 0, 1000);
xcb_get_property_cookie_t c3 = xcb_get_property(
m->conn, 0, m->active_win, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 1000);
xcb_generic_error_t *e1, *e2, *e3;
xcb_get_property_reply_t *r1 = xcb_get_property_reply(m->conn, c1, &e1);
xcb_get_property_reply_t *r2 = xcb_get_property_reply(m->conn, c2, &e2);
xcb_get_property_reply_t *r3 = xcb_get_property_reply(m->conn, c3, &e3);
const char *title;
int title_len;
if (e1 == NULL && xcb_get_property_value_length(r1) > 0) {
title = xcb_get_property_value(r1);
title_len = xcb_get_property_value_length(r1);
} else if (e2 == NULL && xcb_get_property_value_length(r2) > 0) {
title = xcb_get_property_value(r2);
title_len = xcb_get_property_value_length(r2);
} else if (e3 == NULL && xcb_get_property_value_length(r3) > 0) {
title = xcb_get_property_value(r3);
title_len = xcb_get_property_value_length(r3);
} else {
title = NULL;
title_len = 0;
}
if (title_len > 0) {
mtx_lock(&mod->lock);
m->title = malloc(title_len + 1);
memcpy(m->title, title, title_len);
m->title[title_len] = '\0';
mtx_unlock(&mod->lock);
}
free(e1);
free(e2);
free(e3);
free(r1);
free(r2);
free(r3);
}
static int
run(struct module_run_context *ctx)
{
struct module *mod = ctx->module;
struct private *m = mod->private;
m->conn = xcb_connect(NULL, NULL);
if (m->conn == NULL) {
LOG_ERR("failed to connect to X");
module_signal_ready(ctx);
return 1;
}
const xcb_setup_t *setup = xcb_get_setup(m->conn);
xcb_screen_t *screen = xcb_setup_roots_iterator(setup).data;
m->root_win = screen->root;
/* Need a window(?) to be able to process events */
m->monitor_win = xcb_generate_id(m->conn);
xcb_create_window(m->conn, screen->root_depth, m->monitor_win, screen->root,
-1, -1, 1, 1,
0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual,
XCB_CW_OVERRIDE_REDIRECT, (const uint32_t []){1});
xcb_map_window(m->conn, m->monitor_win);
/* Register for property changes on root window. This allows us to
* catch e.g. window switches etc */
xcb_change_window_attributes(
m->conn, screen->root, XCB_CW_EVENT_MASK,
(const uint32_t []){XCB_EVENT_MASK_PROPERTY_CHANGE});
xcb_flush(m->conn);
update_active_window(m);
update_application(mod);
update_title(mod);
module_signal_ready(ctx);
int xcb_fd = xcb_get_file_descriptor(m->conn);
while (true) {
struct pollfd fds[] = {{.fd = ctx->abort_fd, .events = POLLIN},
{.fd = xcb_fd, .events = POLLIN}};
poll(fds, 2, -1);
if (fds[0].revents & POLLIN)
break;
for (xcb_generic_event_t *_e = xcb_wait_for_event(m->conn);
_e != NULL;
_e = xcb_poll_for_event(m->conn))
{
switch (XCB_EVENT_RESPONSE_TYPE(_e)) {
case XCB_PROPERTY_NOTIFY: {
xcb_property_notify_event_t *e = (xcb_property_notify_event_t *)_e;
if (e->atom == _NET_ACTIVE_WINDOW ||
e->atom == _NET_CURRENT_DESKTOP)
{
/* Active desktop and/or window changed */
update_active_window(m);
update_application(mod);
update_title(mod);
mod->bar->refresh(mod->bar);
} else if (e->atom == _NET_WM_VISIBLE_NAME ||
e->atom == _NET_WM_NAME ||
e->atom == XCB_ATOM_WM_NAME)
{
assert(e->window == m->active_win);
update_title(mod);
mod->bar->refresh(mod->bar);
}
break;
}
case 0: break; /* error */
}
free(_e);
}
}
xcb_destroy_window(m->conn, m->monitor_win);
xcb_disconnect(m->conn);
return 0;
}
static struct exposable *
content(struct module *mod)
{
struct private *m = mod->private;
mtx_lock(&mod->lock);
struct tag_set tags = {
.tags = (struct tag *[]){
tag_new_string(mod, "application", m->application),
tag_new_string(mod, "title", m->title),
},
.count = 2,
};
mtx_unlock(&mod->lock);
struct exposable *exposable = m->label->instantiate(m->label, &tags);
tag_set_destroy(&tags);
return exposable;
}
static void
destroy(struct module *mod)
{
struct private *m = mod->private;
m->label->destroy(m->label);
free(m->application);
free(m->title);
free(m);
module_default_destroy(mod);
}
static struct module *
xwindow_new(struct particle *label)
{
struct private *m = calloc(1, sizeof(*m));
m->label = label;
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, const struct font *parent_font)
{
const struct yml_node *c = yml_get_value(node, "content");
return xwindow_new(conf_to_particle(c, parent_font));
}
static bool
verify_conf(keychain_t *chain, const struct yml_node *node)
{
static const struct attr_info attrs[] = {
{"content", true, &conf_verify_particle},
{"anchors", false, NULL},
{NULL, false, NULL}
};
return conf_verify_dict(chain, node, attrs);
}
const struct module_info plugin_info = {
.verify_conf = &verify_conf,
.from_conf = &from_conf,
};