main tohu / source / tohu.c
  1#include <signal.h>
  2#include <stdio.h>
  3#include <stdlib.h>
  4#include <string.h>
  5#include <unistd.h>
  6
  7#include <swc.h>
  8#include <wayland-server.h>
  9#include <wayland-util.h>
 10#include <xkbcommon/xkbcommon-keysyms.h>
 11
 12#include "config.h"
 13#include "types.h"
 14#include "util.h"
 15#include "tohu.h"
 16
 17static void focus(struct client* c);
 18static void apply_decor(struct client* c, bool active);
 19static const char* decorstring(struct client* c, char* buf, size_t size);
 20static struct client* first_client(struct screen* s);
 21static bool is_ws_client(const struct client* c, const struct screen* s);
 22static void on_screen_destroy(void* data);
 23static void on_win_destroy(void* data);
 24static void on_win_entered(void* data);
 25static void setup(void);
 26static void setup_binds(void);
 27static void sync_window_visibility(void);
 28
 29struct wm wm;
 30const struct swc_manager manager = {
 31	.new_screen = new_screen, .new_window = new_window, .new_device = new_device,
 32};
 33struct swc_window_handler window_handler = {
 34	.destroy = on_win_destroy, .entered = on_win_entered,
 35};
 36struct swc_screen_handler screen_handler = {
 37	.destroy = on_screen_destroy,
 38};
 39
 40static void focus(struct client* c)
 41{
 42	if (wm.sel_client) {
 43		swc_window_set_border(
 44			wm.sel_client->win,
 45			cfg.border_col_normal, cfg.border_width,
 46			0, 0
 47		);
 48		apply_decor(wm.sel_client, false);
 49	}
 50
 51	if (c) {
 52		swc_window_set_border(
 53			c->win,
 54			cfg.border_col_active, cfg.border_width,
 55			0, 0
 56		);
 57		apply_decor(c, true);
 58	}
 59
 60	swc_window_focus(c ? c->win : NULL);
 61	wm.sel_client = c;
 62}
 63
 64static void apply_decor(struct client* c, bool active)
 65{
 66	char process_name[256];
 67	const char* title;
 68	struct swc_decor decor;
 69
 70	if (!c)
 71		return;
 72
 73	if (c->fullscreen) {
 74		swc_window_set_decor(c->win, NULL);
 75		return;
 76	}
 77
 78	decor = cfg.decor;
 79	decor.parts = active ? &afterstep_parts : &afterstep_inactive_parts;
 80	decor.title.color = active ? 0xffffffff : 0xffc0c0c0;
 81	title = decorstring(c, process_name, sizeof(process_name));
 82	if (title)
 83		decor.title.string = title;
 84	swc_window_set_decor(c->win, &decor);
 85}
 86
 87static const char* decorstring(struct client* c, char* buf, size_t size)
 88{
 89	char path[64];
 90	FILE* fp;
 91	pid_t pid;
 92	size_t len;
 93
 94	if (!c || !buf || size == 0)
 95		return NULL;
 96
 97	pid = swc_window_get_pid(c->win);
 98	if (pid <= 0)
 99		return NULL;
100
101	snprintf(path, sizeof(path), "/proc/%lld/comm", (long long)pid);
102	fp = fopen(path, "r");
103	if (!fp)
104		return NULL;
105
106	if (!fgets(buf, (int)size, fp)) {
107		fclose(fp);
108		return NULL;
109	}
110
111	fclose(fp);
112	len = strcspn(buf, "\n");
113	buf[len] = '\0';
114	return buf[0] ? buf : NULL;
115}
116
117static struct client* first_client(struct screen* s)
118{
119	struct client* c;
120
121	wl_list_for_each(c, &wm.clients, link) {
122		if (is_ws_client(c, s))
123			return c;
124	}
125
126	return NULL;
127}
128
129static bool is_ws_client(const struct client* c, const struct screen* s)
130{
131	return c && c->ws == wm.ws && (!s || c->scr == s);
132}
133
134static void on_screen_destroy(void* data)
135{
136	struct screen* s = data;
137
138	if (!s)
139		return;
140
141	wl_list_remove(&s->link);
142
143	if (wm.sel_screen == s) {
144		if (wl_list_empty(&wm.screens))
145			wm.sel_screen = NULL;
146		else
147			wm.sel_screen = wl_container_of(wm.screens.next, wm.sel_screen, link);
148	}
149
150	free(s);
151}
152
153static void on_win_destroy(void* data)
154{
155	struct client* c = data;
156	struct client* next;
157
158	if (!c)
159		return;
160
161	if (wm.grab.active && wm.grab.c == c) {
162		wm.grab.active = false;
163		wm.grab.c = NULL;
164	}
165
166	if (wm.sel_client == c) {
167		wm.sel_client = NULL;
168	}
169
170	wl_list_remove(&c->link);
171	free(c);
172
173	next = first_client(wm.sel_screen);
174	if (!next)
175		next = first_client(NULL);
176	focus(next);
177}
178
179static void on_win_entered(void* data)
180{
181	if (wm.grab.active)
182		return;
183
184	struct client* c = data;
185	if (!is_ws_client(c, NULL))
186		return;
187
188	focus(c);
189}
190
191static void setup(void)
192{
193	/* display */
194	wm.dpy = wl_display_create();
195	if (!wm.dpy)
196		die(EXIT_FAILURE, "wl_display_create failed");
197
198	/* variables */
199	wl_list_init(&wm.screens);
200	wl_list_init(&wm.clients);
201	wm.sel_client = NULL;
202	wm.sel_screen = NULL;
203	wm.grab.active = false;
204	wm.grab.resize = false;
205	wm.grab.c = NULL;
206	wm.ws = 1;
207
208	/* event loop */
209	wm.ev_loop = wl_display_get_event_loop(wm.dpy);
210	if (!swc_initialize(wm.dpy, wm.ev_loop, &manager))
211		die(EXIT_FAILURE, "swc_initialize failed\n");
212
213	setup_binds();
214
215	/* display socket */
216	const char* sock;
217	sock = wl_display_add_socket_auto(wm.dpy);
218	if (!sock)
219		die(EXIT_FAILURE, "wl_display_add_socket_auto failed\n");
220	setenv("WAYLAND_DISPLAY", sock, 1);
221	_log(stderr, "WAYLAND_DISPLAY=%s\n", sock);
222
223	/* signals */
224	signal(SIGINT,  sig_handler);
225	signal(SIGTERM, sig_handler);
226	signal(SIGQUIT, sig_handler);
227}
228
229static void setup_binds(void)
230{
231	for (size_t i = 0; i < LENGTH(binds); i++) {
232		const struct bind* b = &binds[i];
233		swc_add_binding(b->type, b->mods, b->ksym, b->fn, (void*)&b->arg);
234	}
235}
236
237static void sync_window_visibility(void)
238{
239	struct client* c;
240
241	wl_list_for_each(c, &wm.clients, link) {
242		if (c->ws == wm.ws)
243			swc_window_show(c->win);
244		else
245			swc_window_hide(c->win);
246	}
247}
248
249void focus_next(void* data, uint32_t time, uint32_t value, uint32_t state)
250{
251	(void)data;
252	(void)time;
253	(void)value;
254
255	struct client* c = NULL;
256
257	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
258		return;
259
260	if (wl_list_empty(&wm.clients))
261		return;
262
263	if (!wm.sel_client || !is_ws_client(wm.sel_client, wm.sel_screen)) {
264		c = first_client(wm.sel_screen);
265		if (!c)
266			c = first_client(NULL);
267		focus(c);
268		return;
269	}
270
271	struct wl_list* start = wm.sel_client->link.next;
272	struct wl_list* it = start;
273
274	do {
275		if (it == &wm.clients)
276			it = wm.clients.next;
277		if (it == &wm.clients)
278			break;
279
280		c = wl_container_of(it, c, link);
281		if (is_ws_client(c, wm.sel_screen)) {
282			focus(c);
283			return;
284		}
285		it = it->next;
286	} while (it != start);
287
288	c = first_client(wm.sel_screen);
289	if (!c)
290		c = first_client(NULL);
291	focus(c);
292}
293
294void focus_prev(void* data, uint32_t time, uint32_t value, uint32_t state)
295{
296	struct client* c = NULL;
297
298	(void)data;
299	(void)time;
300	(void)value;
301
302	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
303		return;
304
305	if (wl_list_empty(&wm.clients))
306		return;
307
308	if (!wm.sel_client || !is_ws_client(wm.sel_client, wm.sel_screen)) {
309		c = first_client(wm.sel_screen);
310		if (!c)
311			c = first_client(NULL);
312		focus(c);
313		return;
314	}
315
316	struct wl_list* start = wm.sel_client->link.prev;
317	struct wl_list* it = start;
318
319	do {
320		if (it == &wm.clients)
321			it = wm.clients.prev;
322		if (it == &wm.clients)
323			break;
324
325		c = wl_container_of(it, c, link);
326		if (is_ws_client(c, wm.sel_screen)) {
327			focus(c);
328			return;
329		}
330		it = it->prev;
331	} while (it != start);
332
333	c = first_client(wm.sel_screen);
334	if (!c)
335		c = first_client(NULL);
336	focus(c);
337}
338
339void kill_sel(void* data, uint32_t time, uint32_t value, uint32_t state)
340{
341	(void)data;
342	(void)time;
343	(void)value;
344
345	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
346		return;
347
348	if (!wm.sel_client)
349		return;
350
351	swc_window_close(wm.sel_client->win);
352}
353
354void fullscreen(void* data, uint32_t time, uint32_t value, uint32_t state)
355{
356	struct swc_rectangle geom;
357
358	(void)data;
359	(void)time;
360	(void)value;
361
362	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
363		return;
364
365	if (!wm.sel_client)
366		return;
367
368	if (wm.sel_client->fullscreen) {
369		wm.sel_client->fullscreen = false;
370		swc_window_set_stacked(wm.sel_client->win);
371		apply_decor(wm.sel_client, true);
372
373		if (wm.sel_client->w > 0 && wm.sel_client->h > 0) {
374			geom.x = wm.sel_client->x;
375			geom.y = wm.sel_client->y;
376			geom.width = wm.sel_client->w;
377			geom.height = wm.sel_client->h;
378			swc_window_set_geometry(wm.sel_client->win, &geom);
379		}
380		return;
381	}
382
383	if (!wm.sel_client->scr || !wm.sel_client->scr->scr)
384		return;
385
386	if (swc_window_get_geometry(wm.sel_client->win, &geom)) {
387		wm.sel_client->x = geom.x;
388		wm.sel_client->y = geom.y;
389		wm.sel_client->w = geom.width;
390		wm.sel_client->h = geom.height;
391	}
392
393	wm.sel_client->fullscreen = true;
394	apply_decor(wm.sel_client, true);
395	swc_window_set_fullscreen(wm.sel_client->win, wm.sel_client->scr->scr);
396}
397
398void mouse_move(void* data, uint32_t time, uint32_t value, uint32_t state)
399{
400	(void)data;
401	(void)time;
402	(void)value;
403
404	if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
405		if (!wm.sel_client)
406			return;
407
408		if (!wm.sel_client->floating) {
409			wm.sel_client->floating = true;
410			swc_window_set_stacked(wm.sel_client->win);
411		}
412
413		wm.grab.active = true;
414		wm.grab.resize = false;
415		wm.grab.c = wm.sel_client;
416
417		swc_window_begin_move(wm.grab.c->win);
418	}
419	else {
420		if (!wm.grab.active || wm.grab.resize || !wm.grab.c)
421			return;
422
423		swc_window_end_move(wm.grab.c->win);
424
425		wm.grab.active = false;
426		wm.grab.c = NULL;
427	}
428}
429
430void mouse_resize(void* data, uint32_t time, uint32_t value, uint32_t state)
431{
432	(void)data;
433	(void)time;
434	(void)value;
435
436	if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
437		if (!wm.sel_client)
438			return;
439
440		if (!wm.sel_client->floating) {
441			wm.sel_client->floating = true;
442			swc_window_set_stacked(wm.sel_client->win);
443		}
444
445		wm.grab.active = true;
446		wm.grab.resize = true;
447		wm.grab.c = wm.sel_client;
448
449		swc_window_begin_resize(
450			wm.grab.c->win,
451			SWC_WINDOW_EDGE_RIGHT | SWC_WINDOW_EDGE_BOTTOM
452		);
453	}
454	else {
455		if (!wm.grab.active || !wm.grab.resize || !wm.grab.c)
456			return;
457
458		swc_window_end_resize(wm.grab.c->win);
459
460		wm.grab.active = false;
461		wm.grab.c = NULL;
462	}
463}
464
465void new_screen(struct swc_screen* scr)
466{
467	struct screen* s;
468
469	s = malloc(sizeof(*s));
470	if (!s)
471		die(EXIT_FAILURE, "new screen calloc failed");
472
473	s->scr = scr;
474
475	s->x = 0;
476	s->y = 0;
477	s->w = 0;
478	s->h = 0;
479
480	wl_list_insert(&wm.screens, &s->link);
481
482	if (!wm.sel_screen)
483		wm.sel_screen = s;
484
485	swc_screen_set_handler(scr, &screen_handler, s);
486
487	_log(stderr, "new_screen=%p\n", (void*)scr);
488}
489
490void new_window(struct swc_window* win)
491{
492	struct client* c;
493
494	c = malloc(sizeof(*c));
495	if (!c)
496		die(EXIT_FAILURE, "malloc client failed");
497
498	win->motion_throttle_ms = 1000 / cfg.motion_throttle_hz;
499	win->min_width = 1;
500	win->min_height = 1;
501	win->max_width = 0;
502	win->max_height = 0;
503
504	c->win = win;
505	c->scr = wm.sel_screen;
506	c->mapped = 0;
507	c->floating = true;
508	c->fullscreen = 0;
509	c->ws = wm.ws;
510	c->x = 0;
511	c->y = 0;
512	c->w = 0;
513	c->h = 0;
514
515	wl_list_insert(&wm.clients, &c->link);
516	swc_window_set_handler(win, &window_handler, c);
517	swc_window_set_stacked(win);
518	apply_decor(c, false);
519	{
520		/* swc reports cursor coordinates in wl_fixed_t (24.8) units */
521		int32_t cx_fixed = 0;
522		int32_t cy_fixed = 0;
523
524		if (swc_cursor_position(&cx_fixed, &cy_fixed))
525			swc_window_set_position(win, cx_fixed / 256, cy_fixed / 256);
526	}
527	swc_window_show(win);
528	focus(c);
529
530	_log(stderr, "new_window=%p\n", (void*)win);
531}
532
533void new_device(struct libinput_device* dev)
534{
535	(void)dev;
536}
537
538void quit(void* data, uint32_t time, uint32_t value, uint32_t state)
539{
540	(void)data;
541	(void)time;
542	(void)value;
543	(void)state;
544
545	wl_display_terminate(wm.dpy);
546}
547
548void spawn(void* data, uint32_t time, uint32_t value, uint32_t state)
549{
550	union arg* a = data;
551	char* const* cmd = (char* const*)a->v;
552
553	(void)time;
554	(void)value;
555
556	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
557		return;
558
559	if (fork() == 0) {
560		execvp(cmd[0], cmd);
561		_exit(127);
562	}
563}
564
565void workspace_goto(void* data, uint32_t time, uint32_t value, uint32_t state)
566{
567	union arg* a = data;
568	struct client* c;
569
570	(void)time;
571	(void)value;
572
573	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
574		return;
575
576	if (a->ui < 1 || a->ui > 9 || a->ui == wm.ws)
577		return;
578
579	wm.ws = a->ui;
580	sync_window_visibility();
581
582	c = first_client(wm.sel_screen);
583	if (!c)
584		c = first_client(NULL);
585	focus(c);
586}
587
588void workspace_moveto(void* data, uint32_t time, uint32_t value, uint32_t state)
589{
590	union arg* a = data;
591	struct client* c;
592	struct client* next;
593
594	(void)time;
595	(void)value;
596
597	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
598		return;
599
600	if (!wm.sel_client)
601		return;
602
603	if (a->ui < 1 || a->ui > 9)
604		return;
605
606	c = wm.sel_client;
607	if (c->ws == a->ui)
608		return;
609
610	c->ws = a->ui;
611	if (c->ws == wm.ws)
612		swc_window_show(c->win);
613	else
614		swc_window_hide(c->win);
615
616	next = first_client(wm.sel_screen);
617	if (!next)
618		next = first_client(NULL);
619	focus(next);
620}
621
622int main(void)
623{
624	setup();
625	wl_display_run(wm.dpy);
626	swc_finalize();
627	wl_display_destroy(wm.dpy);
628	return EXIT_SUCCESS;
629}