Convert from Xlib xcb
This will make it easier to convert this project to Go/xgb later, even though the SYNC extension isn't currently supported there. So far unresolved: error handling.
This commit is contained in:
395
wdmtg.c
395
wdmtg.c
@@ -28,14 +28,12 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <X11/extensions/sync.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/sync.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "gui.h"
|
||||
#include "compound-text.h"
|
||||
|
||||
// --- Utilities ---------------------------------------------------------------
|
||||
|
||||
@@ -67,6 +65,22 @@ get_xdg_config_dirs(void)
|
||||
return (GStrv) g_ptr_array_free(paths, false);
|
||||
}
|
||||
|
||||
static GString *
|
||||
latin1_to_utf8(const gchar *latin1, gsize len)
|
||||
{
|
||||
GString *s = g_string_new(NULL);
|
||||
while (len--) {
|
||||
guchar c = *latin1++;
|
||||
if (c < 0x80) {
|
||||
g_string_append_c(s, c);
|
||||
} else {
|
||||
g_string_append_c(s, 0xC0 | (c >> 6));
|
||||
g_string_append_c(s, 0x80 | (c & 0x3F));
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// --- Globals -----------------------------------------------------------------
|
||||
|
||||
struct event {
|
||||
@@ -90,86 +104,143 @@ struct {
|
||||
} g;
|
||||
|
||||
struct {
|
||||
Display *dpy; // X display handle
|
||||
xcb_connection_t *X; // X display handle
|
||||
xcb_screen_t *screen; // X screen information
|
||||
GThread *thread; // Worker thread
|
||||
|
||||
Atom net_active_window; // _NET_ACTIVE_WINDOW
|
||||
Atom net_wm_name; // _NET_WM_NAME
|
||||
xcb_atom_t atom_net_active_window; // _NET_ACTIVE_WINDOW
|
||||
xcb_atom_t atom_net_wm_name; // _NET_WM_NAME
|
||||
xcb_atom_t atom_utf8_string; // UTF8_STRING
|
||||
xcb_atom_t atom_compound_text; // COMPOUND_TEXT
|
||||
|
||||
// Window title tracking
|
||||
|
||||
gchar *current_title; // Current window title or NULL
|
||||
Window current_window; // Current window
|
||||
xcb_window_t current_window; // Current window
|
||||
gboolean current_idle; // Current idle status
|
||||
|
||||
// XSync activity tracking
|
||||
|
||||
int xsync_base_event_code; // XSync base event code
|
||||
XSyncCounter idle_counter; // XSync IDLETIME counter
|
||||
XSyncValue idle_timeout; // Idle timeout
|
||||
const xcb_query_extension_reply_t *sync; // Sync extension
|
||||
xcb_sync_counter_t idle_counter; // Sync IDLETIME counter
|
||||
xcb_sync_int64_t idle_timeout; // Idle timeout
|
||||
|
||||
XSyncAlarm idle_alarm_inactive; // User is inactive
|
||||
XSyncAlarm idle_alarm_active; // User is active
|
||||
xcb_sync_alarm_t idle_alarm_inactive; // User is inactive
|
||||
xcb_sync_alarm_t idle_alarm_active; // User is active
|
||||
} gen;
|
||||
|
||||
// --- X helpers ---------------------------------------------------------------
|
||||
// --- XCB helpers -------------------------------------------------------------
|
||||
|
||||
static XSyncCounter
|
||||
get_counter(const char *name)
|
||||
static xcb_atom_t
|
||||
intern_atom(const char *atom)
|
||||
{
|
||||
int n;
|
||||
XSyncSystemCounter *counters = XSyncListSystemCounters(gen.dpy, &n);
|
||||
XSyncCounter counter = None;
|
||||
while (n--) {
|
||||
if (!strcmp(counters[n].name, name))
|
||||
counter = counters[n].counter;
|
||||
}
|
||||
XSyncFreeSystemCounterList(counters);
|
||||
return counter;
|
||||
}
|
||||
|
||||
static char *
|
||||
x_text_property_to_utf8(XTextProperty *prop)
|
||||
{
|
||||
Atom utf8_string = XInternAtom(gen.dpy, "UTF8_STRING", true);
|
||||
if (prop->encoding == utf8_string)
|
||||
return g_strdup((char *) prop->value);
|
||||
|
||||
int n = 0;
|
||||
char **list = NULL;
|
||||
if (XmbTextPropertyToTextList(gen.dpy, prop, &list, &n) >= Success
|
||||
&& n > 0 && *list) {
|
||||
char *result = g_locale_to_utf8(*list, -1, NULL, NULL, NULL);
|
||||
XFreeStringList(list);
|
||||
return result;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *
|
||||
x_text_property(Window window, Atom atom)
|
||||
{
|
||||
XTextProperty name;
|
||||
XGetTextProperty(gen.dpy, window, &name, atom);
|
||||
if (!name.value)
|
||||
return NULL;
|
||||
|
||||
char *result = x_text_property_to_utf8(&name);
|
||||
XFree(name.value);
|
||||
xcb_intern_atom_reply_t *iar = xcb_intern_atom_reply(gen.X,
|
||||
xcb_intern_atom(gen.X, false, strlen(atom), atom), NULL);
|
||||
xcb_atom_t result = iar ? iar->atom : XCB_NONE;
|
||||
free(iar);
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- X error handling --------------------------------------------------------
|
||||
|
||||
static XErrorHandler g_default_x_error_handler;
|
||||
|
||||
static int
|
||||
on_x_error(Display *dpy, XErrorEvent *ee)
|
||||
static xcb_sync_counter_t
|
||||
find_counter(xcb_sync_list_system_counters_reply_t *slsr, const char *name)
|
||||
{
|
||||
// This just is going to happen since those windows aren't ours
|
||||
if (ee->error_code == BadWindow)
|
||||
return 0;
|
||||
return g_default_x_error_handler(dpy, ee);
|
||||
// FIXME: https://gitlab.freedesktop.org/xorg/lib/libxcb/-/issues/36
|
||||
const size_t xcb_sync_systemcounter_t_len = 14;
|
||||
|
||||
xcb_sync_systemcounter_iterator_t slsi =
|
||||
xcb_sync_list_system_counters_counters_iterator(slsr);
|
||||
while (slsi.rem--) {
|
||||
xcb_sync_systemcounter_t *counter = slsi.data;
|
||||
char *counter_name = (char *) counter + xcb_sync_systemcounter_t_len;
|
||||
if (!strncmp(counter_name, name, counter->name_len) &&
|
||||
!name[counter->name_len])
|
||||
return counter->counter;
|
||||
|
||||
slsi.data = (void *) counter +
|
||||
((xcb_sync_systemcounter_t_len + counter->name_len + 3) & ~3);
|
||||
}
|
||||
return XCB_NONE;
|
||||
}
|
||||
|
||||
static xcb_sync_counter_t
|
||||
get_counter(const char *name)
|
||||
{
|
||||
xcb_sync_list_system_counters_reply_t *slsr =
|
||||
xcb_sync_list_system_counters_reply(gen.X,
|
||||
xcb_sync_list_system_counters(gen.X), NULL);
|
||||
|
||||
xcb_sync_counter_t counter = XCB_NONE;
|
||||
if (slsr) {
|
||||
counter = find_counter(slsr, name);
|
||||
free(slsr);
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
// --- X helpers ---------------------------------------------------------------
|
||||
|
||||
static GString *
|
||||
x_text_property_to_utf8(GString *value, xcb_atom_t encoding)
|
||||
{
|
||||
if (encoding == gen.atom_utf8_string)
|
||||
return value;
|
||||
|
||||
if (encoding == XCB_ATOM_STRING) {
|
||||
// Could use g_convert() but this will certainly never fail
|
||||
GString *utf8 = latin1_to_utf8(value->str, value->len);
|
||||
g_string_free(value, true);
|
||||
return utf8;
|
||||
}
|
||||
|
||||
// COMPOUND_TEXT doesn't deserve support for multiple NUL-separated items
|
||||
int *ucs4 = NULL;
|
||||
if (encoding == gen.atom_compound_text &&
|
||||
(ucs4 = compound_text_to_ucs4((char *) value->str, value->len))) {
|
||||
g_string_free(value, true);
|
||||
glong len = 0;
|
||||
gchar *utf8 = g_ucs4_to_utf8((gunichar *) ucs4, -1, NULL, &len, NULL);
|
||||
free(ucs4);
|
||||
|
||||
// malloc failure or, rather theoretically, an out of range codepoint
|
||||
if (utf8) {
|
||||
value = g_string_new_len(utf8, len);
|
||||
free(utf8);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
g_string_free(value, true);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static GString *
|
||||
x_text_property(xcb_window_t window, xcb_atom_t property)
|
||||
{
|
||||
GString *buffer = g_string_new(NULL);
|
||||
xcb_atom_t type = XCB_NONE;
|
||||
uint32_t offset = 0;
|
||||
|
||||
xcb_get_property_reply_t *gpr = NULL;
|
||||
while ((gpr = xcb_get_property_reply(gen.X,
|
||||
xcb_get_property(gen.X, false /* delete */, window,
|
||||
property, XCB_GET_PROPERTY_TYPE_ANY, offset, 0x8000), NULL))) {
|
||||
if (gpr->format != 8 || (type && gpr->type != type)) {
|
||||
free(gpr);
|
||||
break;
|
||||
}
|
||||
|
||||
int len = xcb_get_property_value_length(gpr);
|
||||
g_string_append_len(buffer, xcb_get_property_value(gpr), len);
|
||||
offset += len >> 2;
|
||||
type = gpr->type;
|
||||
|
||||
bool last = !gpr->bytes_after;
|
||||
free(gpr);
|
||||
if (last)
|
||||
return x_text_property_to_utf8(buffer, type);
|
||||
}
|
||||
g_string_free(buffer, true);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// --- Generator ---------------------------------------------------------------
|
||||
@@ -184,13 +255,13 @@ push_event(void) {
|
||||
}
|
||||
|
||||
static char *
|
||||
x_window_title(Window window)
|
||||
x_window_title(xcb_window_t window)
|
||||
{
|
||||
char *title;
|
||||
if (!(title = x_text_property(window, gen.net_wm_name))
|
||||
&& !(title = x_text_property(window, XA_WM_NAME)))
|
||||
title = g_strdup("broken");
|
||||
return title;
|
||||
GString *title;
|
||||
if (!(title = x_text_property(window, gen.atom_net_wm_name))
|
||||
&& !(title = x_text_property(window, XCB_ATOM_WM_NAME)))
|
||||
return g_strdup("broken");
|
||||
return g_string_free(title, false);
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -206,27 +277,38 @@ update_window_title(char *new_title)
|
||||
static void
|
||||
update_current_window(void)
|
||||
{
|
||||
Window root = DefaultRootWindow(gen.dpy);
|
||||
|
||||
Atom dummy_type; int dummy_format; unsigned long nitems, dummy_bytes;
|
||||
unsigned char *p = NULL;
|
||||
if (XGetWindowProperty(gen.dpy, root, gen.net_active_window,
|
||||
0L, 1L, false, XA_WINDOW, &dummy_type, &dummy_format,
|
||||
&nitems, &dummy_bytes, &p) != Success)
|
||||
xcb_get_property_reply_t *gpr = xcb_get_property_reply(gen.X,
|
||||
xcb_get_property(gen.X, false /* delete */, gen.screen->root,
|
||||
gen.atom_net_active_window, XCB_ATOM_WINDOW, 0, 1), NULL);
|
||||
if (!gpr)
|
||||
return;
|
||||
|
||||
// TODO: also consider watching XCB_ATOM_WM_CLASS
|
||||
// - the first one is an "Instance name", the second one a "Class name"
|
||||
// - don't know which one to pick, though probably the second one,
|
||||
// since the instance name is Navigator/Mail for Firefox/Thunderbird
|
||||
// - they are separated by a NUL character
|
||||
// - it's not worth to watch for changes of this property
|
||||
|
||||
char *new_title = NULL;
|
||||
if (nitems) {
|
||||
Window active_window = *(Window *) p;
|
||||
XFree(p);
|
||||
if (xcb_get_property_value_length(gpr)) {
|
||||
xcb_window_t active_window =
|
||||
*(xcb_window_t *) xcb_get_property_value(gpr);
|
||||
|
||||
const uint32_t disable[] = { 0 };
|
||||
if (gen.current_window != active_window && gen.current_window)
|
||||
XSelectInput(gen.dpy, gen.current_window, 0);
|
||||
(void) xcb_change_window_attributes(gen.X,
|
||||
gen.current_window, XCB_CW_EVENT_MASK, disable);
|
||||
|
||||
const uint32_t enable[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
|
||||
(void) xcb_change_window_attributes(gen.X,
|
||||
active_window, XCB_CW_EVENT_MASK, enable);
|
||||
|
||||
XSelectInput(gen.dpy, active_window, PropertyChangeMask);
|
||||
new_title = x_window_title(active_window);
|
||||
gen.current_window = active_window;
|
||||
}
|
||||
|
||||
free(gpr);
|
||||
if (update_window_title(new_title)) {
|
||||
printf("Window changed: %s\n",
|
||||
gen.current_title ? gen.current_title : "(none)");
|
||||
@@ -235,19 +317,13 @@ update_current_window(void)
|
||||
}
|
||||
|
||||
static void
|
||||
on_x_property_notify(XPropertyEvent *ev)
|
||||
on_x_property_notify(const xcb_property_notify_event_t *ev)
|
||||
{
|
||||
// TODO: also consider watch WM_CLASS
|
||||
// - the first one is an "Instance name", the second one a "Class name"
|
||||
// - don't know which one to pick, though probably the second one,
|
||||
// since the instance name is Navigator/Mail for Firefox/Thunderbird
|
||||
// - perhaps only when the active window has changed, so u_c_window()
|
||||
|
||||
// This is from the EWMH specification, set by the window manager
|
||||
if (ev->atom == gen.net_active_window) {
|
||||
if (ev->atom == gen.atom_net_active_window) {
|
||||
update_current_window();
|
||||
} else if (ev->window == gen.current_window &&
|
||||
ev->atom == gen.net_wm_name) {
|
||||
ev->atom == gen.atom_net_wm_name) {
|
||||
if (update_window_title(x_window_title(ev->window))) {
|
||||
printf("Title changed: %s\n", gen.current_title);
|
||||
push_event();
|
||||
@@ -256,67 +332,83 @@ on_x_property_notify(XPropertyEvent *ev)
|
||||
}
|
||||
|
||||
static void
|
||||
set_idle_alarm(XSyncAlarm *alarm, XSyncTestType test, XSyncValue value)
|
||||
set_idle_alarm(xcb_sync_alarm_t *alarm, xcb_sync_testtype_t test,
|
||||
xcb_sync_int64_t value)
|
||||
{
|
||||
XSyncAlarmAttributes attr;
|
||||
attr.trigger.counter = gen.idle_counter;
|
||||
attr.trigger.test_type = test;
|
||||
attr.trigger.wait_value = value;
|
||||
XSyncIntToValue(&attr.delta, 0);
|
||||
// TODO: consider xcb_sync_{change,create}_alarm_aux()
|
||||
uint32_t values[] = {
|
||||
gen.idle_counter,
|
||||
value.hi, value.lo,
|
||||
test,
|
||||
0, 0,
|
||||
};
|
||||
|
||||
long flags = XSyncCACounter | XSyncCATestType | XSyncCAValue | XSyncCADelta;
|
||||
if (*alarm)
|
||||
XSyncChangeAlarm(gen.dpy, *alarm, flags, &attr);
|
||||
else
|
||||
*alarm = XSyncCreateAlarm(gen.dpy, flags, &attr);
|
||||
xcb_sync_ca_t flags = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE |
|
||||
XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_DELTA;
|
||||
if (*alarm) {
|
||||
xcb_sync_change_alarm(gen.X, *alarm, flags, values);
|
||||
} else {
|
||||
*alarm = xcb_generate_id(gen.X);
|
||||
xcb_sync_create_alarm(gen.X, *alarm, flags, values);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_x_alarm_notify(XSyncAlarmNotifyEvent *ev)
|
||||
on_x_alarm_notify(const xcb_sync_alarm_notify_event_t *ev)
|
||||
{
|
||||
if (ev->alarm == gen.idle_alarm_inactive) {
|
||||
printf("User is inactive\n");
|
||||
gen.current_idle = true;
|
||||
push_event();
|
||||
|
||||
XSyncValue one, minus_one;
|
||||
XSyncIntToValue(&one, 1);
|
||||
|
||||
Bool overflow;
|
||||
XSyncValueSubtract(&minus_one, ev->counter_value, one, &overflow);
|
||||
xcb_sync_int64_t minus_one = ev->counter_value;
|
||||
if (!~(--minus_one.lo))
|
||||
minus_one.hi--;
|
||||
|
||||
// Set an alarm for IDLETIME <= current_idletime - 1
|
||||
set_idle_alarm(&gen.idle_alarm_active,
|
||||
XSyncNegativeComparison, minus_one);
|
||||
XCB_SYNC_TESTTYPE_NEGATIVE_COMPARISON, minus_one);
|
||||
} else if (ev->alarm == gen.idle_alarm_active) {
|
||||
printf("User is active\n");
|
||||
gen.current_idle = false;
|
||||
push_event();
|
||||
|
||||
set_idle_alarm(&gen.idle_alarm_inactive,
|
||||
XSyncPositiveComparison, gen.idle_timeout);
|
||||
XCB_SYNC_TESTTYPE_POSITIVE_COMPARISON, gen.idle_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
process_x11_event(xcb_generic_event_t *ev)
|
||||
{
|
||||
int event_code = ev->response_type & 0x7f;
|
||||
if (!event_code) {
|
||||
xcb_generic_error_t *err = (xcb_generic_error_t *) ev;
|
||||
// TODO: report the error
|
||||
} else if (event_code == XCB_PROPERTY_NOTIFY) {
|
||||
on_x_property_notify((const xcb_property_notify_event_t *) ev);
|
||||
} else if (event_code == gen.sync->first_event + XCB_SYNC_ALARM_NOTIFY) {
|
||||
on_x_alarm_notify((const xcb_sync_alarm_notify_event_t *) ev);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_x_ready(G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
XEvent ev;
|
||||
while (XPending(gen.dpy)) {
|
||||
if (XNextEvent(gen.dpy, &ev))
|
||||
exit_fatal("XNextEvent returned non-zero");
|
||||
else if (ev.type == PropertyNotify)
|
||||
on_x_property_notify(&ev.xproperty);
|
||||
else if (ev.type == gen.xsync_base_event_code + XSyncAlarmNotify)
|
||||
on_x_alarm_notify((XSyncAlarmNotifyEvent *) &ev);
|
||||
xcb_generic_event_t *event;
|
||||
while ((event = xcb_poll_for_event(gen.X))) {
|
||||
process_x11_event(event);
|
||||
free(event);
|
||||
}
|
||||
return G_SOURCE_CONTINUE;
|
||||
(void) xcb_flush(gen.X);
|
||||
// TODO: some form of error handling, this just silently stops working
|
||||
return xcb_connection_has_error(gen.X) == 0;
|
||||
}
|
||||
|
||||
static void
|
||||
generator_thread(void)
|
||||
{
|
||||
GIOChannel *channel = g_io_channel_unix_new(ConnectionNumber(gen.dpy));
|
||||
GIOChannel *channel = g_io_channel_unix_new(xcb_get_file_descriptor(gen.X));
|
||||
GSource *watch = g_io_create_watch(channel, G_IO_IN);
|
||||
g_source_set_callback(watch, on_x_ready, NULL, NULL);
|
||||
|
||||
@@ -329,34 +421,50 @@ generator_thread(void)
|
||||
static void
|
||||
generator_init(void)
|
||||
{
|
||||
if (!XSupportsLocale())
|
||||
exit_fatal("locale not supported by Xlib");
|
||||
int which_screen = -1, xcb_error;
|
||||
gen.X = xcb_connect(NULL, &which_screen);
|
||||
if ((xcb_error = xcb_connection_has_error(gen.X)))
|
||||
exit_fatal("cannot open display (code %d)", xcb_error);
|
||||
|
||||
XInitThreads();
|
||||
if (!(gen.dpy = XOpenDisplay(NULL)))
|
||||
exit_fatal("cannot open display");
|
||||
|
||||
gen.net_active_window = XInternAtom(gen.dpy, "_NET_ACTIVE_WINDOW", true);
|
||||
gen.net_wm_name = XInternAtom(gen.dpy, "_NET_WM_NAME", true);
|
||||
if (!(gen.atom_net_active_window = intern_atom("_NET_ACTIVE_WINDOW")) ||
|
||||
!(gen.atom_net_wm_name = intern_atom("_NET_WM_NAME")) ||
|
||||
!(gen.atom_utf8_string = intern_atom("UTF8_STRING")) ||
|
||||
!(gen.atom_compound_text = intern_atom("COMPOUND_TEXT")))
|
||||
exit_fatal("unable to resolve atoms");
|
||||
|
||||
// TODO: it is possible to employ a fallback mechanism via XScreenSaver
|
||||
// by polling the XScreenSaverInfo::idle field, see
|
||||
// https://www.x.org/releases/X11R7.5/doc/man/man3/Xss.3.html
|
||||
|
||||
int dummy;
|
||||
if (!XSyncQueryExtension(gen.dpy, &gen.xsync_base_event_code, &dummy)
|
||||
|| !XSyncInitialize(gen.dpy, &dummy, &dummy))
|
||||
exit_fatal("cannot initialize XSync");
|
||||
gen.sync = xcb_get_extension_data(gen.X, &xcb_sync_id);
|
||||
if (!gen.sync->present)
|
||||
exit_fatal("missing Sync extension");
|
||||
|
||||
xcb_generic_error_t *err = NULL;
|
||||
xcb_sync_initialize_cookie_t sic = xcb_sync_initialize(gen.X,
|
||||
XCB_SYNC_MAJOR_VERSION, XCB_SYNC_MINOR_VERSION);
|
||||
xcb_sync_initialize_reply_t *sir =
|
||||
xcb_sync_initialize_reply(gen.X, sic, &err);
|
||||
if (!sir)
|
||||
exit_fatal("failed to initialise Sync extension");
|
||||
free(sir);
|
||||
|
||||
// The idle counter is not guaranteed to exist, only SERVERTIME is
|
||||
if (!(gen.idle_counter = get_counter("IDLETIME")))
|
||||
exit_fatal("idle counter is missing");
|
||||
|
||||
Window root = DefaultRootWindow(gen.dpy);
|
||||
XSelectInput(gen.dpy, root, PropertyChangeMask);
|
||||
XSync(gen.dpy, False);
|
||||
// TODO: what is the interaction with GTK+ here?
|
||||
g_default_x_error_handler = XSetErrorHandler(on_x_error);
|
||||
const xcb_setup_t *setup = xcb_get_setup(gen.X);
|
||||
xcb_screen_iterator_t setup_iter = xcb_setup_roots_iterator(setup);
|
||||
while (which_screen--)
|
||||
xcb_screen_next(&setup_iter);
|
||||
|
||||
gen.screen = setup_iter.data;
|
||||
xcb_window_t root = gen.screen->root;
|
||||
|
||||
const uint32_t values[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
|
||||
(void) xcb_change_window_attributes(gen.X, root, XCB_CW_EVENT_MASK, values);
|
||||
(void) xcb_flush(gen.X);
|
||||
// TODO: how are XCB errors handled? What if the last xcb_flush() fails?
|
||||
|
||||
GKeyFile *kf = g_key_file_new();
|
||||
gchar *subpath = g_build_filename(PROJECT_NAME, PROJECT_NAME ".conf", NULL);
|
||||
@@ -374,10 +482,13 @@ generator_init(void)
|
||||
g_free(subpath);
|
||||
g_key_file_free(kf);
|
||||
|
||||
XSyncIntToValue(&gen.idle_timeout, timeout * 1000);
|
||||
gint64 timeout_ms = timeout * 1000;
|
||||
gen.idle_timeout.hi = timeout_ms >> 32;
|
||||
gen.idle_timeout.lo = timeout_ms;
|
||||
|
||||
update_current_window();
|
||||
set_idle_alarm(&gen.idle_alarm_inactive,
|
||||
XSyncPositiveComparison, gen.idle_timeout);
|
||||
XCB_SYNC_TESTTYPE_POSITIVE_COMPARISON, gen.idle_timeout);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -392,7 +503,7 @@ generator_cleanup(void)
|
||||
{
|
||||
g_thread_join(gen.thread);
|
||||
free(gen.current_title);
|
||||
XCloseDisplay(gen.dpy);
|
||||
xcb_disconnect(gen.X);
|
||||
}
|
||||
|
||||
// --- Main --------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user