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 { | ||||
| 	GAsyncQueue *queue;                 // Async queue of `struct event`
 | ||||
| 	sqlite3 *db;                        // Event database
 | ||||
| } g; | ||||
| 
 | ||||
| struct { | ||||
| 	Display *dpy;                       // X display handle
 | ||||
| 	GThread *thread;                    // Worker thread
 | ||||
| 
 | ||||
| 	Atom net_active_window;             // _NET_ACTIVE_WINDOW
 | ||||
| 	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); | ||||
| } | ||||
| 
 | ||||
| // --- 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 * | ||||
| x_window_title(Window window) | ||||
| @ -192,15 +203,6 @@ update_window_title(char *new_title) | ||||
| 	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 | ||||
| update_current_window(void) | ||||
| { | ||||
| @ -235,6 +237,12 @@ update_current_window(void) | ||||
| static void | ||||
| 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
 | ||||
| 	if (ev->atom == gen.net_active_window) { | ||||
| 		update_current_window(); | ||||
| @ -306,7 +314,7 @@ on_x_ready(G_GNUC_UNUSED gpointer user_data) | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| generate_events(void) | ||||
| generator_thread(void) | ||||
| { | ||||
| 	GIOChannel *channel = g_io_channel_unix_new(ConnectionNumber(gen.dpy)); | ||||
| 	GSource *watch = g_io_create_watch(channel, G_IO_IN); | ||||
| @ -318,31 +326,9 @@ generate_events(void) | ||||
| 	g_main_loop_run(loop); | ||||
| } | ||||
| 
 | ||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||||
| 
 | ||||
| int | ||||
| main(int argc, char *argv[]) | ||||
| static void | ||||
| generator_init(void) | ||||
| { | ||||
| 	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()) | ||||
| 		exit_fatal("locale not supported by Xlib"); | ||||
| 
 | ||||
| @ -392,56 +378,29 @@ main(int argc, char *argv[]) | ||||
| 	update_current_window(); | ||||
| 	set_idle_alarm(&gen.idle_alarm_inactive, | ||||
| 		XSyncPositiveComparison, gen.idle_timeout); | ||||
| } | ||||
| 
 | ||||
| 	gchar *data_path = | ||||
| 		g_build_filename(g_get_user_data_dir(), PROJECT_NAME, NULL); | ||||
| 	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 = | ||||
| static void | ||||
| generator_launch(void) | ||||
| { | ||||
| 		.l_type = F_WRLCK, | ||||
| 		.l_start = 0, | ||||
| 		.l_whence = SEEK_SET, | ||||
| 		.l_len = 0, | ||||
| 	}; | ||||
| 	gen.thread = | ||||
| 		g_thread_new("generator", (GThreadFunc) generator_thread, NULL); | ||||
| } | ||||
| 
 | ||||
| 	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); | ||||
| static void | ||||
| generator_cleanup(void) | ||||
| { | ||||
| 	g_thread_join(gen.thread); | ||||
| 	free(gen.current_title); | ||||
| 	XCloseDisplay(gen.dpy); | ||||
| } | ||||
| 
 | ||||
| 	int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); | ||||
| 	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)); | ||||
| // --- Main --------------------------------------------------------------------
 | ||||
| 
 | ||||
| static sqlite3 * | ||||
| database_init(const gchar *db_path) | ||||
| { | ||||
| 	sqlite3 *db = NULL; | ||||
| 	gchar *db_path = g_build_filename(data_path, "db.sqlite", NULL); | ||||
| 	int rc = sqlite3_open(db_path, &db); | ||||
| 	if (rc != SQLITE_OK) | ||||
| 		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"); | ||||
| 
 | ||||
| 	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 ((rc = sqlite3_exec(db, "CREATE TABLE events (" | ||||
| 			"id INTEGER PRIMARY KEY AUTOINCREMENT, " | ||||
| @ -479,14 +441,95 @@ main(int argc, char *argv[]) | ||||
| 	} | ||||
| 	if ((rc = sqlite3_exec(db, "COMMIT", NULL, NULL, &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(socket_path); | ||||
| 	g_free(data_path); | ||||
| 
 | ||||
| 	GThread *generator = | ||||
| 		g_thread_new("generator", (GThreadFunc) generate_events, NULL); | ||||
| 	int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); | ||||
| 	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: 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
 | ||||
| 
 | ||||
| 	WdmtgWindow *window = wdmtg_window_new_with_db(db); | ||||
| 	WdmtgWindow *window = wdmtg_window_new_with_db(g.db); | ||||
| 
 | ||||
| 	generator_launch(); | ||||
| 	gtk_main(); | ||||
| 	g_thread_join(generator); | ||||
| 	generator_cleanup(); | ||||
| 
 | ||||
| 	free(gen.current_title); | ||||
| 	XCloseDisplay(gen.dpy); | ||||
| 	sqlite3_close(g.db); | ||||
| 	close(socket_fd); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user