commit 9236a46

shrub  ·  2026-02-20 10:19:30 +0000 UTC
parent 9236a46
init
10 files changed,  +842, -0
+7, -0
1@@ -0,0 +1,7 @@
2+*.swp
3+*.o
4+wsxwm
5+compile_flags.txt
6+*.sh
7+*.log
8+
+7, -0
1@@ -0,0 +1,7 @@
2+ISC License
3+
4+Copyright 2026 uint
5+
6+Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
7+
8+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+23, -0
 1@@ -0,0 +1,23 @@
 2+CC      ?= cc
 3+# CFLAGS   = -std=c99 -Wall -Wextra -O0 -g -fcolor-diagnostics # debug
 4+CFLAGS   = -std=c99 -Wall -Wextra -O2 -fcolor-diagnostics
 5+CPPFLAGS = -D_POSIX_C_SOURCE=200809L -Isource/include
 6+
 7+OUT = tohu
 8+SRC = source/tohu.c source/util.c
 9+
10+PKGS = swc wayland-server xkbcommon libinput pixman-1 libdrm wld libudev xcb xcb-composite xcb-ewmh xcb-icccm
11+CFLAGS += $(shell pkg-config --cflags $(PKGS))
12+LDLIBS += $(shell pkg-config --libs   $(PKGS)) -lm
13+
14+all: ${OUT}
15+
16+${OUT}: ${SRC}
17+	${CC} ${CFLAGS} ${CPPFLAGS} -o ${OUT} ${SRC} ${LDLIBS}
18+
19+clean:
20+	rm -f ${OUT}
21+
22+compile_flags:
23+	rm -f compile_flags.txt
24+	for f in ${CFLAGS} ${CPPFLAGS}; do echo $$f >> compile_flags.txt; done
+17, -0
 1@@ -0,0 +1,17 @@
 2+tohu
 3+
 4+floating window manager for swc
 5+
 6+this is wsxwm with tiling removed, all the code is borrowed basically, i will add more things soon: check out https://git.sr.ht/~uint/wsxwm
 7+
 8+mod+enter to create terminal
 9+
10+mod+q to close window
11+
12+mod+m1 to move
13+
14+mod+m3 to resize
15+
16+mod+f to fullscreen
17+
18+mod+number to workspace
+47, -0
 1@@ -0,0 +1,47 @@
 2+#ifndef CONFIG_H
 3+#define CONFIG_H
 4+
 5+#include <xkbcommon/xkbcommon-keysyms.h>
 6+
 7+
 8+#include "types.h"
 9+#include "tohu.h"
10+
11+static const struct config cfg = {
12+	.motion_throttle_hz = 85,
13+	.border_col_active = 0xffed953e,
14+	.border_col_normal = 0xff444444,
15+	.border_width = 1,
16+	.gaps = 0,
17+};
18+
19+static const char* termcmd[] = { "st-wl", NULL };
20+static struct bind binds[] = {
21+	{ SWC_BINDING_KEY,    MOD4,        XKB_KEY_Return, { .v = termcmd }, spawn },
22+	{ SWC_BINDING_KEY,    MOD4|SHFT,   XKB_KEY_q,      { .v = NULL },    quit },
23+	{ SWC_BINDING_KEY,    MOD4,        XKB_KEY_Tab,    { .v = NULL },    focus_next },
24+	{ SWC_BINDING_KEY,    MOD4,        XKB_KEY_f,      { .v = NULL },    fullscreen },
25+	{ SWC_BINDING_KEY,    MOD4,        XKB_KEY_q,      { .v = NULL },    kill_sel },
26+	{ SWC_BINDING_KEY,    MOD4,        XKB_KEY_1,      { .ui = 1 },      workspace_goto },
27+	{ SWC_BINDING_KEY,    MOD4,        XKB_KEY_2,      { .ui = 2 },      workspace_goto },
28+	{ SWC_BINDING_KEY,    MOD4,        XKB_KEY_3,      { .ui = 3 },      workspace_goto },
29+	{ SWC_BINDING_KEY,    MOD4,        XKB_KEY_4,      { .ui = 4 },      workspace_goto },
30+	{ SWC_BINDING_KEY,    MOD4,        XKB_KEY_5,      { .ui = 5 },      workspace_goto },
31+	{ SWC_BINDING_KEY,    MOD4,        XKB_KEY_6,      { .ui = 6 },      workspace_goto },
32+	{ SWC_BINDING_KEY,    MOD4,        XKB_KEY_7,      { .ui = 7 },      workspace_goto },
33+	{ SWC_BINDING_KEY,    MOD4,        XKB_KEY_8,      { .ui = 8 },      workspace_goto },
34+	{ SWC_BINDING_KEY,    MOD4,        XKB_KEY_9,      { .ui = 9 },      workspace_goto },
35+	{ SWC_BINDING_KEY,    MOD4|SHFT,   XKB_KEY_1,      { .ui = 1 },      workspace_moveto },
36+	{ SWC_BINDING_KEY,    MOD4|SHFT,   XKB_KEY_2,      { .ui = 2 },      workspace_moveto },
37+	{ SWC_BINDING_KEY,    MOD4|SHFT,   XKB_KEY_3,      { .ui = 3 },      workspace_moveto },
38+	{ SWC_BINDING_KEY,    MOD4|SHFT,   XKB_KEY_4,      { .ui = 4 },      workspace_moveto },
39+	{ SWC_BINDING_KEY,    MOD4|SHFT,   XKB_KEY_5,      { .ui = 5 },      workspace_moveto },
40+	{ SWC_BINDING_KEY,    MOD4|SHFT,   XKB_KEY_6,      { .ui = 6 },      workspace_moveto },
41+	{ SWC_BINDING_KEY,    MOD4|SHFT,   XKB_KEY_7,      { .ui = 7 },      workspace_moveto },
42+	{ SWC_BINDING_KEY,    MOD4|SHFT,   XKB_KEY_8,      { .ui = 8 },      workspace_moveto },
43+	{ SWC_BINDING_KEY,    MOD4|SHFT,   XKB_KEY_9,      { .ui = 9 },      workspace_moveto },
44+	{ SWC_BINDING_BUTTON, MOD4,        BTN_LEFT,       { .v = NULL },    mouse_move },
45+	{ SWC_BINDING_BUTTON, MOD4,        BTN_RIGHT,      { .v = NULL },    mouse_resize },
46+};
47+
48+#endif /* CONFIG_H */
+23, -0
 1@@ -0,0 +1,23 @@
 2+#ifndef TOHU_H
 3+#define TOHU_H
 4+
 5+#include <stdint.h>
 6+
 7+#include "types.h"
 8+
 9+extern void focus_next(void* data, uint32_t time, uint32_t value, uint32_t state);
10+extern void focus_prev(void* data, uint32_t time, uint32_t value, uint32_t state);
11+extern void kill_sel(void* data, uint32_t time, uint32_t value, uint32_t state);
12+extern void fullscreen(void* data, uint32_t time, uint32_t value, uint32_t state);
13+extern void mouse_move(void* data, uint32_t time, uint32_t value, uint32_t state);
14+extern void mouse_resize(void* data, uint32_t time, uint32_t value, uint32_t state);
15+extern void new_screen(struct swc_screen* scr);
16+extern void new_window(struct swc_window* win);
17+extern void new_device(struct libinput_device* dev);
18+extern void quit(void* data, uint32_t time, uint32_t value, uint32_t state);
19+extern void spawn(void* data, uint32_t time, uint32_t value, uint32_t state);
20+extern void workspace_goto(void* data, uint32_t time, uint32_t value, uint32_t state);
21+extern void workspace_moveto(void* data, uint32_t time, uint32_t value, uint32_t state);
22+extern struct wm wm;
23+
24+#endif /* TOHU_H */
+88, -0
 1@@ -0,0 +1,88 @@
 2+#ifndef TYPES_H
 3+#define TYPES_H
 4+
 5+#include <stdbool.h>
 6+#include <stdint.h>
 7+
 8+#include <swc.h>
 9+#include <wayland-server.h>
10+
11+enum {
12+	BTN_LEFT   = 0x110,
13+	BTN_RIGHT  = 0x111,
14+	BTN_MIDDLE = 0x112,
15+};
16+
17+enum {
18+	MOD1 = SWC_MOD_ALT,
19+	MOD4 = SWC_MOD_LOGO,
20+	SHFT = SWC_MOD_SHIFT,
21+	CTRL = SWC_MOD_CTRL,
22+};
23+
24+union arg {
25+	int            i;
26+	uint32_t       ui;
27+	float          f;
28+	const void*    v;
29+};
30+
31+struct bind {
32+	uint32_t       type;
33+	uint32_t       mods;
34+	uint32_t       ksym;
35+	union arg      arg;
36+	void           (*fn)(void* data, uint32_t time, uint32_t value, uint32_t state);
37+};
38+
39+struct client {
40+	struct wl_list link;
41+	struct swc_window* win;
42+	struct screen* scr;
43+	bool           mapped;
44+	bool           floating;
45+	bool           fullscreen;
46+	int32_t        x;
47+	int32_t        y;
48+	uint32_t       w;
49+	uint32_t       h;
50+	uint32_t       ws;
51+};
52+
53+struct config {
54+	uint32_t       motion_throttle_hz;
55+	uint32_t       border_col_active;
56+	uint32_t       border_col_normal;
57+	uint32_t       border_width;
58+	uint32_t       gaps;
59+};
60+
61+struct grab {
62+	bool           active;
63+	bool           resize;
64+	struct client* c;
65+};
66+
67+struct screen {
68+	struct wl_list link;
69+	struct swc_screen* scr;
70+	int32_t        x;
71+	int32_t        y;
72+	uint32_t       w;
73+	uint32_t       h;
74+};
75+
76+struct wm {
77+	struct wl_display* dpy;
78+	struct wl_event_loop* ev_loop;
79+
80+	struct wl_list screens;
81+	struct wl_list clients;
82+
83+	struct screen* sel_screen;
84+	struct client* sel_client;
85+	struct grab    grab;
86+	uint8_t        ws;
87+};
88+
89+#endif /* TYPES_H */
+18, -0
 1@@ -0,0 +1,18 @@
 2+#ifndef UTIL_H
 3+#define UTIL_H
 4+
 5+#include <stdarg.h>
 6+#include <stdint.h>
 7+#include <stdio.h>
 8+
 9+#include <swc.h>
10+#include <wayland-server.h>
11+
12+void die(int ret, const char* fmt, ...);
13+void _log(FILE* fd, const char* fmt, ...);
14+void sig_handler(int s);
15+
16+#define LENGTH(x) (sizeof(x) / sizeof((x)[0]))
17+
18+#endif /* UTIL_H */
19+
+568, -0
  1@@ -0,0 +1,568 @@
  2+#include <signal.h>
  3+#include <stdio.h>
  4+#include <stdlib.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+
 17+static void focus(struct client* c);
 18+static struct client* first_client(struct screen* s);
 19+static bool is_ws_client(const struct client* c, const struct screen* s);
 20+static void on_screen_destroy(void* data);
 21+static void on_win_destroy(void* data);
 22+static void on_win_entered(void* data);
 23+static void setup(void);
 24+static void setup_binds(void);
 25+static void sync_window_visibility(void);
 26+
 27+struct wm wm;
 28+const struct swc_manager manager = {
 29+	.new_screen = new_screen, .new_window = new_window, .new_device = new_device,
 30+};
 31+struct swc_window_handler window_handler = {
 32+	.destroy = on_win_destroy, .entered = on_win_entered,
 33+};
 34+struct swc_screen_handler screen_handler = {
 35+	.destroy = on_screen_destroy,
 36+};
 37+
 38+static void focus(struct client* c)
 39+{
 40+	if (wm.sel_client)
 41+		swc_window_set_border(
 42+			wm.sel_client->win,
 43+			cfg.border_col_normal, cfg.border_width,
 44+			0, 0
 45+		);
 46+
 47+	if (c)
 48+		swc_window_set_border(
 49+			c->win,
 50+			cfg.border_col_active, cfg.border_width,
 51+			0, 0
 52+		);
 53+
 54+	swc_window_focus(c ? c->win : NULL);
 55+	wm.sel_client = c;
 56+}
 57+
 58+static struct client* first_client(struct screen* s)
 59+{
 60+	struct client* c;
 61+
 62+	wl_list_for_each(c, &wm.clients, link) {
 63+		if (is_ws_client(c, s))
 64+			return c;
 65+	}
 66+
 67+	return NULL;
 68+}
 69+
 70+static bool is_ws_client(const struct client* c, const struct screen* s)
 71+{
 72+	return c && c->ws == wm.ws && (!s || c->scr == s);
 73+}
 74+
 75+static void on_screen_destroy(void* data)
 76+{
 77+	struct screen* s = data;
 78+
 79+	if (!s)
 80+		return;
 81+
 82+	wl_list_remove(&s->link);
 83+
 84+	if (wm.sel_screen == s) {
 85+		if (wl_list_empty(&wm.screens))
 86+			wm.sel_screen = NULL;
 87+		else
 88+			wm.sel_screen = wl_container_of(wm.screens.next, wm.sel_screen, link);
 89+	}
 90+
 91+	free(s);
 92+}
 93+
 94+static void on_win_destroy(void* data)
 95+{
 96+	struct client* c = data;
 97+	struct client* next;
 98+
 99+	if (!c)
100+		return;
101+
102+	if (wm.grab.active && wm.grab.c == c) {
103+		wm.grab.active = false;
104+		wm.grab.c = NULL;
105+	}
106+
107+	if (wm.sel_client == c) {
108+		wm.sel_client = NULL;
109+	}
110+
111+	wl_list_remove(&c->link);
112+	free(c);
113+
114+	next = first_client(wm.sel_screen);
115+	if (!next)
116+		next = first_client(NULL);
117+	focus(next);
118+}
119+
120+static void on_win_entered(void* data)
121+{
122+	if (wm.grab.active)
123+		return;
124+
125+	struct client* c = data;
126+	if (!is_ws_client(c, NULL))
127+		return;
128+
129+	focus(c);
130+}
131+
132+static void setup(void)
133+{
134+	/* display */
135+	wm.dpy = wl_display_create();
136+	if (!wm.dpy)
137+		die(EXIT_FAILURE, "wl_display_create failed");
138+
139+	/* variables */
140+	wl_list_init(&wm.screens);
141+	wl_list_init(&wm.clients);
142+	wm.sel_client = NULL;
143+	wm.sel_screen = NULL;
144+	wm.grab.active = false;
145+	wm.grab.resize = false;
146+	wm.grab.c = NULL;
147+	wm.ws = 1;
148+
149+	/* event loop */
150+	wm.ev_loop = wl_display_get_event_loop(wm.dpy);
151+	if (!swc_initialize(wm.dpy, wm.ev_loop, &manager))
152+		die(EXIT_FAILURE, "swc_initialize failed\n");
153+
154+	/* TODO: temp wallpaper and quit binding */
155+	swc_wallpaper_color_set(0xff1e1f21);
156+	setup_binds();
157+
158+	/* display socket */
159+	const char* sock;
160+	sock = wl_display_add_socket_auto(wm.dpy);
161+	if (!sock)
162+		die(EXIT_FAILURE, "wl_display_add_socket_auto failed\n");
163+	setenv("WAYLAND_DISPLAY", sock, 1);
164+	_log(stderr, "WAYLAND_DISPLAY=%s\n", sock);
165+
166+	/* signals */
167+	signal(SIGINT,  sig_handler);
168+	signal(SIGTERM, sig_handler);
169+	signal(SIGQUIT, sig_handler);
170+}
171+
172+static void setup_binds(void)
173+{
174+	for (size_t i = 0; i < LENGTH(binds); i++) {
175+		const struct bind* b = &binds[i];
176+		swc_add_binding(b->type, b->mods, b->ksym, b->fn, (void*)&b->arg);
177+	}
178+}
179+
180+static void sync_window_visibility(void)
181+{
182+	struct client* c;
183+
184+	wl_list_for_each(c, &wm.clients, link) {
185+		if (c->ws == wm.ws)
186+			swc_window_show(c->win);
187+		else
188+			swc_window_hide(c->win);
189+	}
190+}
191+
192+void focus_next(void* data, uint32_t time, uint32_t value, uint32_t state)
193+{
194+	(void)data;
195+	(void)time;
196+	(void)value;
197+
198+	struct client* c = NULL;
199+
200+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
201+		return;
202+
203+	if (wl_list_empty(&wm.clients))
204+		return;
205+
206+	if (!wm.sel_client || !is_ws_client(wm.sel_client, wm.sel_screen)) {
207+		c = first_client(wm.sel_screen);
208+		if (!c)
209+			c = first_client(NULL);
210+		focus(c);
211+		return;
212+	}
213+
214+	struct wl_list* start = wm.sel_client->link.next;
215+	struct wl_list* it = start;
216+
217+	do {
218+		if (it == &wm.clients)
219+			it = wm.clients.next;
220+		if (it == &wm.clients)
221+			break;
222+
223+		c = wl_container_of(it, c, link);
224+		if (is_ws_client(c, wm.sel_screen)) {
225+			focus(c);
226+			return;
227+		}
228+		it = it->next;
229+	} while (it != start);
230+
231+	c = first_client(wm.sel_screen);
232+	if (!c)
233+		c = first_client(NULL);
234+	focus(c);
235+}
236+
237+void focus_prev(void* data, uint32_t time, uint32_t value, uint32_t state)
238+{
239+	struct client* c = NULL;
240+
241+	(void)data;
242+	(void)time;
243+	(void)value;
244+
245+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
246+		return;
247+
248+	if (wl_list_empty(&wm.clients))
249+		return;
250+
251+	if (!wm.sel_client || !is_ws_client(wm.sel_client, wm.sel_screen)) {
252+		c = first_client(wm.sel_screen);
253+		if (!c)
254+			c = first_client(NULL);
255+		focus(c);
256+		return;
257+	}
258+
259+	struct wl_list* start = wm.sel_client->link.prev;
260+	struct wl_list* it = start;
261+
262+	do {
263+		if (it == &wm.clients)
264+			it = wm.clients.prev;
265+		if (it == &wm.clients)
266+			break;
267+
268+		c = wl_container_of(it, c, link);
269+		if (is_ws_client(c, wm.sel_screen)) {
270+			focus(c);
271+			return;
272+		}
273+		it = it->prev;
274+	} while (it != start);
275+
276+	c = first_client(wm.sel_screen);
277+	if (!c)
278+		c = first_client(NULL);
279+	focus(c);
280+}
281+
282+void kill_sel(void* data, uint32_t time, uint32_t value, uint32_t state)
283+{
284+	(void)data;
285+	(void)time;
286+	(void)value;
287+
288+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
289+		return;
290+
291+	if (!wm.sel_client)
292+		return;
293+
294+	swc_window_close(wm.sel_client->win);
295+}
296+
297+void fullscreen(void* data, uint32_t time, uint32_t value, uint32_t state)
298+{
299+	struct swc_rectangle geom;
300+
301+	(void)data;
302+	(void)time;
303+	(void)value;
304+
305+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
306+		return;
307+
308+	if (!wm.sel_client)
309+		return;
310+
311+	if (wm.sel_client->fullscreen) {
312+		wm.sel_client->fullscreen = false;
313+		swc_window_set_stacked(wm.sel_client->win);
314+
315+		if (wm.sel_client->w > 0 && wm.sel_client->h > 0) {
316+			geom.x = wm.sel_client->x;
317+			geom.y = wm.sel_client->y;
318+			geom.width = wm.sel_client->w;
319+			geom.height = wm.sel_client->h;
320+			swc_window_set_geometry(wm.sel_client->win, &geom);
321+		}
322+		return;
323+	}
324+
325+	if (!wm.sel_client->scr || !wm.sel_client->scr->scr)
326+		return;
327+
328+	if (swc_window_get_geometry(wm.sel_client->win, &geom)) {
329+		wm.sel_client->x = geom.x;
330+		wm.sel_client->y = geom.y;
331+		wm.sel_client->w = geom.width;
332+		wm.sel_client->h = geom.height;
333+	}
334+
335+	wm.sel_client->fullscreen = true;
336+	swc_window_set_fullscreen(wm.sel_client->win, wm.sel_client->scr->scr);
337+}
338+
339+void mouse_move(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_POINTER_BUTTON_STATE_PRESSED) {
346+		if (!wm.sel_client)
347+			return;
348+
349+		if (!wm.sel_client->floating) {
350+			wm.sel_client->floating = true;
351+			swc_window_set_stacked(wm.sel_client->win);
352+		}
353+
354+		wm.grab.active = true;
355+		wm.grab.resize = false;
356+		wm.grab.c = wm.sel_client;
357+
358+		swc_window_begin_move(wm.grab.c->win);
359+	}
360+	else {
361+		if (!wm.grab.active || wm.grab.resize || !wm.grab.c)
362+			return;
363+
364+		swc_window_end_move(wm.grab.c->win);
365+
366+		wm.grab.active = false;
367+		wm.grab.c = NULL;
368+	}
369+}
370+
371+void mouse_resize(void* data, uint32_t time, uint32_t value, uint32_t state)
372+{
373+	(void)data;
374+	(void)time;
375+	(void)value;
376+
377+	if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
378+		if (!wm.sel_client)
379+			return;
380+
381+		if (!wm.sel_client->floating) {
382+			wm.sel_client->floating = true;
383+			swc_window_set_stacked(wm.sel_client->win);
384+		}
385+
386+		wm.grab.active = true;
387+		wm.grab.resize = true;
388+		wm.grab.c = wm.sel_client;
389+
390+		swc_window_begin_resize(
391+			wm.grab.c->win,
392+			SWC_WINDOW_EDGE_RIGHT | SWC_WINDOW_EDGE_BOTTOM
393+		);
394+	}
395+	else {
396+		if (!wm.grab.active || !wm.grab.resize || !wm.grab.c)
397+			return;
398+
399+		swc_window_end_resize(wm.grab.c->win);
400+
401+		wm.grab.active = false;
402+		wm.grab.c = NULL;
403+	}
404+}
405+
406+void new_screen(struct swc_screen* scr)
407+{
408+	struct screen* s;
409+
410+	s = malloc(sizeof(*s));
411+	if (!s)
412+		die(EXIT_FAILURE, "new screen calloc failed");
413+
414+	s->scr = scr;
415+
416+	s->x = 0;
417+	s->y = 0;
418+	s->w = 0;
419+	s->h = 0;
420+
421+	wl_list_insert(&wm.screens, &s->link);
422+
423+	if (!wm.sel_screen)
424+		wm.sel_screen = s;
425+
426+	swc_screen_set_handler(scr, &screen_handler, s);
427+
428+	_log(stderr, "new_screen=%p\n", (void*)scr);
429+}
430+
431+void new_window(struct swc_window* win)
432+{
433+	struct client* c;
434+
435+	c = malloc(sizeof(*c));
436+	if (!c)
437+		die(EXIT_FAILURE, "malloc client failed");
438+
439+	win->motion_throttle_ms = 1000 / cfg.motion_throttle_hz;
440+	win->min_width = 1;
441+	win->min_height = 1;
442+	win->max_width = 0;
443+	win->max_height = 0;
444+
445+	c->win = win;
446+	c->scr = wm.sel_screen;
447+	c->mapped = 0;
448+	c->floating = true;
449+	c->fullscreen = 0;
450+	c->ws = wm.ws;
451+	c->x = 0;
452+	c->y = 0;
453+	c->w = 0;
454+	c->h = 0;
455+
456+	wl_list_insert(&wm.clients, &c->link);
457+	swc_window_set_handler(win, &window_handler, c);
458+	swc_window_set_stacked(win);
459+	{
460+		/* swc reports cursor coordinates in wl_fixed_t (24.8) units */
461+		int32_t cx_fixed = 0;
462+		int32_t cy_fixed = 0;
463+
464+		if (swc_cursor_position(&cx_fixed, &cy_fixed))
465+			swc_window_set_position(win, cx_fixed / 256, cy_fixed / 256);
466+	}
467+	swc_window_show(win);
468+	focus(c);
469+
470+	_log(stderr, "new_window=%p\n", (void*)win);
471+}
472+
473+void new_device(struct libinput_device* dev)
474+{
475+	(void)dev;
476+}
477+
478+void quit(void* data, uint32_t time, uint32_t value, uint32_t state)
479+{
480+	(void)data;
481+	(void)time;
482+	(void)value;
483+	(void)state;
484+
485+	wl_display_terminate(wm.dpy);
486+}
487+
488+void spawn(void* data, uint32_t time, uint32_t value, uint32_t state)
489+{
490+	union arg* a = data;
491+	char* const* cmd = (char* const*)a->v;
492+
493+	(void)time;
494+	(void)value;
495+
496+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
497+		return;
498+
499+	if (fork() == 0) {
500+		execvp(cmd[0], cmd);
501+		_exit(127);
502+	}
503+}
504+
505+void workspace_goto(void* data, uint32_t time, uint32_t value, uint32_t state)
506+{
507+	union arg* a = data;
508+	struct client* c;
509+
510+	(void)time;
511+	(void)value;
512+
513+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
514+		return;
515+
516+	if (a->ui < 1 || a->ui > 9 || a->ui == wm.ws)
517+		return;
518+
519+	wm.ws = a->ui;
520+	sync_window_visibility();
521+
522+	c = first_client(wm.sel_screen);
523+	if (!c)
524+		c = first_client(NULL);
525+	focus(c);
526+}
527+
528+void workspace_moveto(void* data, uint32_t time, uint32_t value, uint32_t state)
529+{
530+	union arg* a = data;
531+	struct client* c;
532+	struct client* next;
533+
534+	(void)time;
535+	(void)value;
536+
537+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
538+		return;
539+
540+	if (!wm.sel_client)
541+		return;
542+
543+	if (a->ui < 1 || a->ui > 9)
544+		return;
545+
546+	c = wm.sel_client;
547+	if (c->ws == a->ui)
548+		return;
549+
550+	c->ws = a->ui;
551+	if (c->ws == wm.ws)
552+		swc_window_show(c->win);
553+	else
554+		swc_window_hide(c->win);
555+
556+	next = first_client(wm.sel_screen);
557+	if (!next)
558+		next = first_client(NULL);
559+	focus(next);
560+}
561+
562+int main(void)
563+{
564+	setup();
565+	wl_display_run(wm.dpy);
566+	swc_finalize();
567+	wl_display_destroy(wm.dpy);
568+	return EXIT_SUCCESS;
569+}
+44, -0
 1@@ -0,0 +1,44 @@
 2+#include <stdlib.h>
 3+
 4+#include <swc.h>
 5+
 6+#include "util.h"
 7+#include "tohu.h"
 8+
 9+void die(int ret, const char* fmt, ...)
10+{
11+	va_list ap;
12+
13+	fprintf(stderr, "tohu: ");
14+
15+	va_start(ap, fmt);
16+	vfprintf(stderr, fmt, ap);
17+	va_end(ap);
18+
19+	fputc('\n', stderr);
20+	fflush(stderr);
21+
22+	exit(ret);
23+}
24+
25+void _log(FILE* fd, const char* fmt, ...)
26+{
27+	va_list ap;
28+
29+	fprintf(fd, "tohu: ");
30+
31+	va_start(ap, fmt);
32+	vfprintf(fd, fmt, ap);
33+	va_end(ap);
34+
35+	fputc('\n', fd);
36+	fflush(fd);
37+}
38+
39+void sig_handler(int s)
40+{
41+	(void)s;
42+
43+	if (wm.dpy)
44+		wl_display_terminate(wm.dpy);
45+}