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}