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}