Cleanup
This commit is contained in:
parent
60bfaa1a97
commit
93c61425b3
220
wdmtg.c
220
wdmtg.c
|
@ -86,10 +86,12 @@ event_free(struct event *self)
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
GAsyncQueue *queue; // Async queue of `struct event`
|
GAsyncQueue *queue; // Async queue of `struct event`
|
||||||
|
sqlite3 *db; // Event database
|
||||||
} g;
|
} g;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
Display *dpy; // X display handle
|
Display *dpy; // X display handle
|
||||||
|
GThread *thread; // Worker thread
|
||||||
|
|
||||||
Atom net_active_window; // _NET_ACTIVE_WINDOW
|
Atom net_active_window; // _NET_ACTIVE_WINDOW
|
||||||
Atom net_wm_name; // _NET_WM_NAME
|
Atom net_wm_name; // _NET_WM_NAME
|
||||||
|
@ -170,7 +172,16 @@ on_x_error(Display *dpy, XErrorEvent *ee)
|
||||||
return g_default_x_error_handler(dpy, ee);
|
return g_default_x_error_handler(dpy, ee);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Application -------------------------------------------------------------
|
// --- Generator ---------------------------------------------------------------
|
||||||
|
|
||||||
|
static void
|
||||||
|
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->idle = gen.current_idle;
|
||||||
|
g_async_queue_push(g.queue, event);
|
||||||
|
}
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
x_window_title(Window window)
|
x_window_title(Window window)
|
||||||
|
@ -192,15 +203,6 @@ update_window_title(char *new_title)
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
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->idle = gen.current_idle;
|
|
||||||
g_async_queue_push(g.queue, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
update_current_window(void)
|
update_current_window(void)
|
||||||
{
|
{
|
||||||
|
@ -235,6 +237,12 @@ update_current_window(void)
|
||||||
static void
|
static void
|
||||||
on_x_property_notify(XPropertyEvent *ev)
|
on_x_property_notify(XPropertyEvent *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
|
// This is from the EWMH specification, set by the window manager
|
||||||
if (ev->atom == gen.net_active_window) {
|
if (ev->atom == gen.net_active_window) {
|
||||||
update_current_window();
|
update_current_window();
|
||||||
|
@ -306,7 +314,7 @@ on_x_ready(G_GNUC_UNUSED gpointer user_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
generate_events(void)
|
generator_thread(void)
|
||||||
{
|
{
|
||||||
GIOChannel *channel = g_io_channel_unix_new(ConnectionNumber(gen.dpy));
|
GIOChannel *channel = g_io_channel_unix_new(ConnectionNumber(gen.dpy));
|
||||||
GSource *watch = g_io_create_watch(channel, G_IO_IN);
|
GSource *watch = g_io_create_watch(channel, G_IO_IN);
|
||||||
|
@ -318,31 +326,9 @@ generate_events(void)
|
||||||
g_main_loop_run(loop);
|
g_main_loop_run(loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
static void
|
||||||
|
generator_init(void)
|
||||||
int
|
|
||||||
main(int argc, char *argv[])
|
|
||||||
{
|
{
|
||||||
gboolean show_version = false;
|
|
||||||
const GOptionEntry options[] = {
|
|
||||||
{"version", 'V', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE,
|
|
||||||
&show_version, "output version information and exit", NULL},
|
|
||||||
{},
|
|
||||||
};
|
|
||||||
|
|
||||||
GError *error = NULL;
|
|
||||||
if (!gtk_init_with_args(&argc, &argv, " - activity tracker",
|
|
||||||
options, NULL, &error))
|
|
||||||
exit_fatal("%s", error->message);
|
|
||||||
if (show_version) {
|
|
||||||
printf(PROJECT_NAME " " PROJECT_VERSION "\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
g.queue = g_async_queue_new_full((GDestroyNotify) event_free);
|
|
||||||
|
|
||||||
if (!setlocale(LC_CTYPE, ""))
|
|
||||||
exit_fatal("cannot set locale");
|
|
||||||
if (!XSupportsLocale())
|
if (!XSupportsLocale())
|
||||||
exit_fatal("locale not supported by Xlib");
|
exit_fatal("locale not supported by Xlib");
|
||||||
|
|
||||||
|
@ -392,56 +378,29 @@ main(int argc, char *argv[])
|
||||||
update_current_window();
|
update_current_window();
|
||||||
set_idle_alarm(&gen.idle_alarm_inactive,
|
set_idle_alarm(&gen.idle_alarm_inactive,
|
||||||
XSyncPositiveComparison, gen.idle_timeout);
|
XSyncPositiveComparison, gen.idle_timeout);
|
||||||
|
}
|
||||||
|
|
||||||
gchar *data_path =
|
static void
|
||||||
g_build_filename(g_get_user_data_dir(), PROJECT_NAME, NULL);
|
generator_launch(void)
|
||||||
g_mkdir_with_parents(data_path, 0755);
|
|
||||||
|
|
||||||
// TODO: try exclusivity/invocation either via DBus directly,
|
|
||||||
// or via GApplication or GtkApplication:
|
|
||||||
// - GtkApplication calls Gtk.init automatically during "startup" signal,
|
|
||||||
// Gtk.init doesn't get command line args
|
|
||||||
// - "inhibiting" makes no sense, it can't be used for mere delays
|
|
||||||
// - actually, the "query-end" signal
|
|
||||||
// - should check whether it tries to exit cleanly
|
|
||||||
// - what is the session manager, do I have it?
|
|
||||||
// - "register-session" looks useful
|
|
||||||
// - GTK+ keeps the application running as long as it has windows,
|
|
||||||
// though I want to keep it running forever
|
|
||||||
// - g_application_hold(), perhaps
|
|
||||||
// - so maybe just use GApplication, that will provide more control
|
|
||||||
|
|
||||||
// Bind to a control socket, also ensuring only one instance is running
|
|
||||||
gchar *socket_path = g_build_filename(data_path, "socket", NULL);
|
|
||||||
|
|
||||||
struct flock lock =
|
|
||||||
{
|
{
|
||||||
.l_type = F_WRLCK,
|
gen.thread =
|
||||||
.l_start = 0,
|
g_thread_new("generator", (GThreadFunc) generator_thread, NULL);
|
||||||
.l_whence = SEEK_SET,
|
}
|
||||||
.l_len = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
gchar *lock_path = g_strdup_printf("%s.lock", socket_path);
|
static void
|
||||||
int lock_fd = open(lock_path, O_RDWR | O_CREAT, 0644);
|
generator_cleanup(void)
|
||||||
if (fcntl(lock_fd, F_SETLK, &lock))
|
{
|
||||||
exit_fatal("failed to acquire lock: %s", strerror(errno));
|
g_thread_join(gen.thread);
|
||||||
unlink(socket_path);
|
free(gen.current_title);
|
||||||
|
XCloseDisplay(gen.dpy);
|
||||||
|
}
|
||||||
|
|
||||||
int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
// --- Main --------------------------------------------------------------------
|
||||||
if (socket_fd < 0)
|
|
||||||
exit_fatal("%s: %s", socket_path, strerror(errno));
|
|
||||||
|
|
||||||
struct sockaddr_un sun;
|
|
||||||
sun.sun_family = AF_UNIX;
|
|
||||||
strncpy(sun.sun_path, socket_path, sizeof sun.sun_path);
|
|
||||||
if (bind(socket_fd, (struct sockaddr *) &sun, sizeof sun))
|
|
||||||
exit_fatal("%s: %s", socket_path, strerror(errno));
|
|
||||||
if (listen(socket_fd, 10))
|
|
||||||
exit_fatal("%s: %s", socket_path, strerror(errno));
|
|
||||||
|
|
||||||
|
static sqlite3 *
|
||||||
|
database_init(const gchar *db_path)
|
||||||
|
{
|
||||||
sqlite3 *db = NULL;
|
sqlite3 *db = NULL;
|
||||||
gchar *db_path = g_build_filename(data_path, "db.sqlite", NULL);
|
|
||||||
int rc = sqlite3_open(db_path, &db);
|
int rc = sqlite3_open(db_path, &db);
|
||||||
if (rc != SQLITE_OK)
|
if (rc != SQLITE_OK)
|
||||||
exit_fatal("%s: %s", db_path, sqlite3_errmsg(db));
|
exit_fatal("%s: %s", db_path, sqlite3_errmsg(db));
|
||||||
|
@ -464,6 +423,9 @@ main(int argc, char *argv[])
|
||||||
exit_fatal("%s: %s", db_path, "cannot retrieve user version");
|
exit_fatal("%s: %s", db_path, "cannot retrieve user version");
|
||||||
|
|
||||||
int user_version = sqlite3_column_int(stmt, 0);
|
int user_version = sqlite3_column_int(stmt, 0);
|
||||||
|
if ((rc = sqlite3_finalize(stmt)))
|
||||||
|
exit_fatal("%s: %s", db_path, "cannot retrieve user version");
|
||||||
|
|
||||||
if (user_version == 0) {
|
if (user_version == 0) {
|
||||||
if ((rc = sqlite3_exec(db, "CREATE TABLE events ("
|
if ((rc = sqlite3_exec(db, "CREATE TABLE events ("
|
||||||
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||||
|
@ -479,14 +441,95 @@ main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
if ((rc = sqlite3_exec(db, "COMMIT", NULL, NULL, &errmsg)))
|
if ((rc = sqlite3_exec(db, "COMMIT", NULL, NULL, &errmsg)))
|
||||||
exit_fatal("%s: %s", db_path, errmsg);
|
exit_fatal("%s: %s", db_path, errmsg);
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
g_free(db_path);
|
static int
|
||||||
|
socket_init(const gchar *socket_path)
|
||||||
|
{
|
||||||
|
// TODO: try exclusivity/invocation either via DBus directly,
|
||||||
|
// or via GApplication or GtkApplication:
|
||||||
|
// - GtkApplication calls Gtk.init automatically during "startup" signal,
|
||||||
|
// Gtk.init doesn't get command line args
|
||||||
|
// - "inhibiting" makes no sense, it can't be used for mere delays
|
||||||
|
// - actually, the "query-end" signal
|
||||||
|
// - should check whether it tries to exit cleanly
|
||||||
|
// - what is the session manager, do I have it?
|
||||||
|
// - "register-session" looks useful
|
||||||
|
// - GTK+ keeps the application running as long as it has windows,
|
||||||
|
// though I want to keep it running forever
|
||||||
|
// - g_application_hold(), perhaps
|
||||||
|
// - so maybe just use GApplication, that will provide more control
|
||||||
|
|
||||||
|
struct flock lock =
|
||||||
|
{
|
||||||
|
.l_type = F_WRLCK,
|
||||||
|
.l_start = 0,
|
||||||
|
.l_whence = SEEK_SET,
|
||||||
|
.l_len = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
gchar *lock_path = g_strdup_printf("%s.lock", socket_path);
|
||||||
|
int lock_fd = open(lock_path, O_RDWR | O_CREAT, 0644);
|
||||||
|
if (fcntl(lock_fd, F_SETLK, &lock))
|
||||||
|
exit_fatal("failed to acquire lock: %s", strerror(errno));
|
||||||
|
unlink(socket_path);
|
||||||
g_free(lock_path);
|
g_free(lock_path);
|
||||||
g_free(socket_path);
|
|
||||||
g_free(data_path);
|
|
||||||
|
|
||||||
GThread *generator =
|
int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
g_thread_new("generator", (GThreadFunc) generate_events, NULL);
|
if (socket_fd < 0)
|
||||||
|
exit_fatal("%s: %s", socket_path, strerror(errno));
|
||||||
|
|
||||||
|
struct sockaddr_un sun;
|
||||||
|
sun.sun_family = AF_UNIX;
|
||||||
|
strncpy(sun.sun_path, socket_path, sizeof sun.sun_path);
|
||||||
|
if (bind(socket_fd, (struct sockaddr *) &sun, sizeof sun))
|
||||||
|
exit_fatal("%s: %s", socket_path, strerror(errno));
|
||||||
|
if (listen(socket_fd, 10))
|
||||||
|
exit_fatal("%s: %s", socket_path, strerror(errno));
|
||||||
|
|
||||||
|
// lock_fd stays open as long as the application is running--leak
|
||||||
|
return socket_fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
if (!setlocale(LC_CTYPE, ""))
|
||||||
|
exit_fatal("cannot set locale");
|
||||||
|
|
||||||
|
gboolean show_version = false;
|
||||||
|
const GOptionEntry options[] = {
|
||||||
|
{"version", 'V', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE,
|
||||||
|
&show_version, "output version information and exit", NULL},
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
|
||||||
|
GError *error = NULL;
|
||||||
|
if (!gtk_init_with_args(&argc, &argv, " - activity tracker",
|
||||||
|
options, NULL, &error))
|
||||||
|
exit_fatal("%s", error->message);
|
||||||
|
if (show_version) {
|
||||||
|
printf(PROJECT_NAME " " PROJECT_VERSION "\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.queue = g_async_queue_new_full((GDestroyNotify) event_free);
|
||||||
|
generator_init();
|
||||||
|
|
||||||
|
gchar *data_path =
|
||||||
|
g_build_filename(g_get_user_data_dir(), PROJECT_NAME, NULL);
|
||||||
|
g_mkdir_with_parents(data_path, 0755);
|
||||||
|
|
||||||
|
// Bind to a control socket, also ensuring only one instance is running
|
||||||
|
gchar *socket_path = g_build_filename(data_path, "socket", NULL);
|
||||||
|
int socket_fd = socket_init(socket_path);
|
||||||
|
g_free(socket_path);
|
||||||
|
|
||||||
|
gchar *db_path = g_build_filename(data_path, "db.sqlite", NULL);
|
||||||
|
g_free(data_path);
|
||||||
|
g.db = database_init(db_path);
|
||||||
|
g_free(db_path);
|
||||||
|
|
||||||
// TODO: somehow read events from the async queue
|
// TODO: somehow read events from the async queue
|
||||||
// TODO: how in the name of fuck would our custom source wake up a sleeping
|
// TODO: how in the name of fuck would our custom source wake up a sleeping
|
||||||
|
@ -495,12 +538,13 @@ main(int argc, char *argv[])
|
||||||
|
|
||||||
// TODO: listen for connections on the control socket
|
// TODO: listen for connections on the control socket
|
||||||
|
|
||||||
WdmtgWindow *window = wdmtg_window_new_with_db(db);
|
WdmtgWindow *window = wdmtg_window_new_with_db(g.db);
|
||||||
|
|
||||||
|
generator_launch();
|
||||||
gtk_main();
|
gtk_main();
|
||||||
g_thread_join(generator);
|
generator_cleanup();
|
||||||
|
|
||||||
free(gen.current_title);
|
sqlite3_close(g.db);
|
||||||
XCloseDisplay(gen.dpy);
|
close(socket_fd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue