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