Watch changes of WM_CLASS
There may be some interesting information in there. Sometimes it may be hard to identify applications by their title.
This commit is contained in:
parent
86b0579cb7
commit
3482ee66a3
61
wdmtg.c
61
wdmtg.c
|
@ -86,6 +86,7 @@ latin1_to_utf8(const gchar *latin1, gsize len)
|
||||||
struct event {
|
struct event {
|
||||||
gint64 timestamp; // When the event happened
|
gint64 timestamp; // When the event happened
|
||||||
gchar *title; // Current title at the time
|
gchar *title; // Current title at the time
|
||||||
|
gchar *class; // Current class at the time
|
||||||
gboolean idle; // Whether the user is idle
|
gboolean idle; // Whether the user is idle
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -93,6 +94,7 @@ static void
|
||||||
event_free(struct event *self)
|
event_free(struct event *self)
|
||||||
{
|
{
|
||||||
g_free(self->title);
|
g_free(self->title);
|
||||||
|
g_free(self->class);
|
||||||
g_slice_free(struct event, self);
|
g_slice_free(struct event, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,6 +119,7 @@ struct {
|
||||||
// Window title tracking
|
// Window title tracking
|
||||||
|
|
||||||
gchar *current_title; // Current window title or NULL
|
gchar *current_title; // Current window title or NULL
|
||||||
|
gchar *current_class; // Current window class or NULL
|
||||||
xcb_window_t current_window; // Current window
|
xcb_window_t current_window; // Current window
|
||||||
gboolean current_idle; // Current idle status
|
gboolean current_idle; // Current idle status
|
||||||
|
|
||||||
|
@ -286,6 +289,7 @@ push_event(void) {
|
||||||
struct event *event = g_slice_new0(struct event);
|
struct event *event = g_slice_new0(struct event);
|
||||||
event->timestamp = g_get_real_time();
|
event->timestamp = g_get_real_time();
|
||||||
event->title = g_strdup(gen.current_title);
|
event->title = g_strdup(gen.current_title);
|
||||||
|
event->class = g_strdup(gen.current_class);
|
||||||
event->idle = gen.current_idle;
|
event->idle = gen.current_idle;
|
||||||
g_async_queue_push(g.queue, event);
|
g_async_queue_push(g.queue, event);
|
||||||
|
|
||||||
|
@ -313,6 +317,33 @@ update_window_title(char *new_title)
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
x_window_class(xcb_window_t window)
|
||||||
|
{
|
||||||
|
GString *title;
|
||||||
|
if (!(title = x_text_property(window, XCB_ATOM_WM_CLASS)))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
// First is an "instance name", followed by a NUL and a "class name".
|
||||||
|
// Strongly prefer Firefox/Thunderbird over Navigator/Mail.
|
||||||
|
size_t skip = strlen(title->str);
|
||||||
|
if (++skip >= title->len)
|
||||||
|
return g_string_free(title, true);
|
||||||
|
|
||||||
|
g_string_erase(title, 0, skip);
|
||||||
|
return g_string_free(title, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
update_window_class(char *new_class)
|
||||||
|
{
|
||||||
|
bool changed = !gen.current_class != !new_class
|
||||||
|
|| (new_class && strcmp(gen.current_class, new_class));
|
||||||
|
free(gen.current_class);
|
||||||
|
gen.current_class = new_class;
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
update_current_window(void)
|
update_current_window(void)
|
||||||
{
|
{
|
||||||
|
@ -322,14 +353,8 @@ update_current_window(void)
|
||||||
if (!gpr)
|
if (!gpr)
|
||||||
return;
|
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;
|
char *new_title = NULL;
|
||||||
|
char *new_class = NULL;
|
||||||
if (xcb_get_property_value_length(gpr)) {
|
if (xcb_get_property_value_length(gpr)) {
|
||||||
xcb_window_t active_window =
|
xcb_window_t active_window =
|
||||||
*(xcb_window_t *) xcb_get_property_value(gpr);
|
*(xcb_window_t *) xcb_get_property_value(gpr);
|
||||||
|
@ -344,11 +369,14 @@ update_current_window(void)
|
||||||
active_window, XCB_CW_EVENT_MASK, enable);
|
active_window, XCB_CW_EVENT_MASK, enable);
|
||||||
|
|
||||||
new_title = x_window_title(active_window);
|
new_title = x_window_title(active_window);
|
||||||
|
new_class = x_window_class(active_window);
|
||||||
gen.current_window = active_window;
|
gen.current_window = active_window;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(gpr);
|
free(gpr);
|
||||||
if (update_window_title(new_title))
|
|
||||||
|
// We need to absorb both pointers, so we need a bitwise OR
|
||||||
|
if (update_window_title(new_title) |
|
||||||
|
update_window_class(new_class))
|
||||||
push_event();
|
push_event();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -555,14 +583,18 @@ on_queue_incoming(G_GNUC_UNUSED gpointer user_data)
|
||||||
// in a transaction at once (the amount of dequeues here)
|
// in a transaction at once (the amount of dequeues here)
|
||||||
struct event *event = NULL;
|
struct event *event = NULL;
|
||||||
while ((event = g_async_queue_try_pop(g.queue))) {
|
while ((event = g_async_queue_try_pop(g.queue))) {
|
||||||
printf("Event: ts: %ld, title: %s, idle: %d\n",
|
printf("Event: ts: %ld, title: %s, class: %s, idle: %d\n",
|
||||||
event->timestamp, event->title ?: "(none)", event->idle);
|
event->timestamp, event->title ?: "(none)",
|
||||||
|
event->class ?: "(none)", event->idle);
|
||||||
|
|
||||||
if ((rc = sqlite3_bind_int64(g.add_event, 1, event->timestamp)) ||
|
if ((rc = sqlite3_bind_int64(g.add_event, 1, event->timestamp)) ||
|
||||||
// FIXME: it will fail on NULL titles
|
// FIXME: it will fail on NULL titles
|
||||||
(rc = sqlite3_bind_text(g.add_event, 2, event->title, -1,
|
(rc = sqlite3_bind_text(g.add_event, 2, event->title, -1,
|
||||||
SQLITE_STATIC)) ||
|
SQLITE_STATIC)) ||
|
||||||
(rc = sqlite3_bind_int(g.add_event, 3, event->idle)))
|
// FIXME: it will fail on NULL classes
|
||||||
|
(rc = sqlite3_bind_text(g.add_event, 3, event->class, -1,
|
||||||
|
SQLITE_STATIC)) ||
|
||||||
|
(rc = sqlite3_bind_int(g.add_event, 4, event->idle)))
|
||||||
g_printerr("DB bind error: %s\n", sqlite3_errmsg(g.db));
|
g_printerr("DB bind error: %s\n", sqlite3_errmsg(g.db));
|
||||||
|
|
||||||
if (((rc = sqlite3_step(g.add_event)) && rc != SQLITE_DONE) ||
|
if (((rc = sqlite3_step(g.add_event)) && rc != SQLITE_DONE) ||
|
||||||
|
@ -612,6 +644,7 @@ database_init(const gchar *db_path)
|
||||||
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||||
"timestamp INTEGER, "
|
"timestamp INTEGER, "
|
||||||
"title TEXT, "
|
"title TEXT, "
|
||||||
|
"class TEXT, "
|
||||||
"idle BOOLEAN)", NULL, NULL, &errmsg)))
|
"idle BOOLEAN)", NULL, NULL, &errmsg)))
|
||||||
exit_fatal("%s: %s", db_path, errmsg);
|
exit_fatal("%s: %s", db_path, errmsg);
|
||||||
if ((rc = sqlite3_exec(db, "PRAGMA user_version = 1", NULL, NULL,
|
if ((rc = sqlite3_exec(db, "PRAGMA user_version = 1", NULL, NULL,
|
||||||
|
@ -624,8 +657,8 @@ database_init(const gchar *db_path)
|
||||||
exit_fatal("%s: %s", db_path, errmsg);
|
exit_fatal("%s: %s", db_path, errmsg);
|
||||||
|
|
||||||
if ((rc = sqlite3_prepare_v2(db,
|
if ((rc = sqlite3_prepare_v2(db,
|
||||||
"INSERT INTO events (timestamp, title, idle) VALUES (?, ?, ?)", -1,
|
"INSERT INTO events (timestamp, title, class, idle) "
|
||||||
&g.add_event, NULL)))
|
"VALUES (?, ?, ?, ?)", -1, &g.add_event, NULL)))
|
||||||
exit_fatal("%s: %s", db_path, sqlite3_errmsg(db));
|
exit_fatal("%s: %s", db_path, sqlite3_errmsg(db));
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue