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 {
|
||||
gint64 timestamp; // When the event happened
|
||||
gchar *title; // Current title at the time
|
||||
gchar *class; // Current class at the time
|
||||
gboolean idle; // Whether the user is idle
|
||||
};
|
||||
|
||||
|
@ -93,6 +94,7 @@ static void
|
|||
event_free(struct event *self)
|
||||
{
|
||||
g_free(self->title);
|
||||
g_free(self->class);
|
||||
g_slice_free(struct event, self);
|
||||
}
|
||||
|
||||
|
@ -117,6 +119,7 @@ struct {
|
|||
// Window title tracking
|
||||
|
||||
gchar *current_title; // Current window title or NULL
|
||||
gchar *current_class; // Current window class or NULL
|
||||
xcb_window_t current_window; // Current window
|
||||
gboolean current_idle; // Current idle status
|
||||
|
||||
|
@ -286,6 +289,7 @@ push_event(void) {
|
|||
struct event *event = g_slice_new0(struct event);
|
||||
event->timestamp = g_get_real_time();
|
||||
event->title = g_strdup(gen.current_title);
|
||||
event->class = g_strdup(gen.current_class);
|
||||
event->idle = gen.current_idle;
|
||||
g_async_queue_push(g.queue, event);
|
||||
|
||||
|
@ -313,6 +317,33 @@ update_window_title(char *new_title)
|
|||
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
|
||||
update_current_window(void)
|
||||
{
|
||||
|
@ -322,14 +353,8 @@ update_current_window(void)
|
|||
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;
|
||||
char *new_class = NULL;
|
||||
if (xcb_get_property_value_length(gpr)) {
|
||||
xcb_window_t active_window =
|
||||
*(xcb_window_t *) xcb_get_property_value(gpr);
|
||||
|
@ -344,11 +369,14 @@ update_current_window(void)
|
|||
active_window, XCB_CW_EVENT_MASK, enable);
|
||||
|
||||
new_title = x_window_title(active_window);
|
||||
new_class = x_window_class(active_window);
|
||||
gen.current_window = active_window;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -555,14 +583,18 @@ on_queue_incoming(G_GNUC_UNUSED gpointer user_data)
|
|||
// in a transaction at once (the amount of dequeues here)
|
||||
struct event *event = NULL;
|
||||
while ((event = g_async_queue_try_pop(g.queue))) {
|
||||
printf("Event: ts: %ld, title: %s, idle: %d\n",
|
||||
event->timestamp, event->title ?: "(none)", event->idle);
|
||||
printf("Event: ts: %ld, title: %s, class: %s, idle: %d\n",
|
||||
event->timestamp, event->title ?: "(none)",
|
||||
event->class ?: "(none)", event->idle);
|
||||
|
||||
if ((rc = sqlite3_bind_int64(g.add_event, 1, event->timestamp)) ||
|
||||
// FIXME: it will fail on NULL titles
|
||||
(rc = sqlite3_bind_text(g.add_event, 2, event->title, -1,
|
||||
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));
|
||||
|
||||
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, "
|
||||
"timestamp INTEGER, "
|
||||
"title TEXT, "
|
||||
"class TEXT, "
|
||||
"idle BOOLEAN)", NULL, NULL, &errmsg)))
|
||||
exit_fatal("%s: %s", db_path, errmsg);
|
||||
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);
|
||||
|
||||
if ((rc = sqlite3_prepare_v2(db,
|
||||
"INSERT INTO events (timestamp, title, idle) VALUES (?, ?, ?)", -1,
|
||||
&g.add_event, NULL)))
|
||||
"INSERT INTO events (timestamp, title, class, idle) "
|
||||
"VALUES (?, ?, ?, ?)", -1, &g.add_event, NULL)))
|
||||
exit_fatal("%s: %s", db_path, sqlite3_errmsg(db));
|
||||
return db;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue