commit bb4b24a

wf  ·  2026-03-25 17:36:00 +0000 UTC
parent bb4b24a
initial
10 files changed,  +662, -0
A README
A howl.c
A log.c
+2, -0
1@@ -0,0 +1,2 @@
2+howl
3+howlc
+34, -0
 1@@ -0,0 +1,34 @@
 2+CC        ?= cc
 3+CFLAGS    := -std=c99 -Wall -O3 -pedantic
 4+LDFLAGS   := `pkg-config --cflags --libs swc` -Iinclude/
 5+LDLIBS    := `pkg-config --libs swc`
 6+PREFIX    ?= /usr/local
 7+
 8+howl      := howl
 9+howl_src  := window.c log.c howl.c
10+
11+howlc     := howlc
12+howlc_src := window.c client.c
13+
14+.PHONY: all install uninstall clean
15+
16+all: $(howl) $(howlc)
17+
18+$(howl): $(howl_src)
19+	@echo "Building howl..."
20+	$(CC) $(CFLAGS) $(LDFLAGS) -o $(howl) $(howl_src) $(LDLIBS)
21+
22+$(howlc): $(howlc_src)
23+	@echo "Building howlc..."
24+	$(CC) $(CFLAGS) $(LDFLAGS) -o $(howlc) $(howlc_src) $(LDLIBS)
25+
26+install: $(howl) $(howlc)
27+	install -Dm755 $(howl)  $(DESTDIR)$(PREFIX)/bin/$(howl)
28+	install -Dm755 $(howlc) $(DESTDIR)$(PREFIX)/bin/$(howlc)
29+
30+uninstall:
31+	rm -f $(DESTDIR)$(PREFIX)/bin/$(howl)
32+	rm -f $(DESTDIR)$(PREFIX)/bin/$(howlc)
33+
34+clean:
35+	rm -f $(howl) $(howlc)
A README
+21, -0
 1@@ -0,0 +1,21 @@
 2+howl
 3+====
 4+
 5+howl is a floating wayland compositor written in neuswc[1] that implements an IPC interface, allowing for scriptability and advanced usage. howl's code is largely based on that of tohu[2]'s.
 6+
 7+dependencies
 8+------------
 9+
10+ - neuswc[1]
11+ - wayland headers
12+
13+todo
14+----
15+
16+ - parse IPC commands
17+ - configuration
18+ - IPC keybinding command
19+ - document IPC interface
20+
21+[1]: https://git.sr.ht/~shrub900/neuswc
22+[2]: https://git.sr.ht/~shrub900/tohu
+68, -0
 1@@ -0,0 +1,68 @@
 2+#include <stdio.h>
 3+#include <string.h>
 4+#include <unistd.h>
 5+#include <sys/socket.h>
 6+#include <sys/un.h>
 7+
 8+#include <swc.h>
 9+
10+#include "howl.h"
11+#include "types.h"
12+#include "window.h"
13+
14+static void
15+ipc_msg(char **cmd) {
16+	struct sockaddr_un addr;
17+	char msg[256];
18+
19+	if (sock == -1) {
20+		perror("couldn't connect to IPC socket");
21+		return;
22+	}
23+
24+	memset(&addr, 0, sizeof(struct sockaddr_un));
25+	addr.sun_family = AF_LOCAL;
26+	strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);
27+
28+	if (connect(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
29+		perror("couldn't connect to IPC socket");
30+		close(sock);
31+		return;
32+	}
33+
34+	snprintf(msg, sizeof(msg), "%s", cmd[0]);
35+	for (int i = 0; cmd[i] != NULL; i++) {
36+		strcat(msg, " ");
37+		strcat(msg, cmd[i]);
38+	}
39+
40+	if (send(sock, msg, strlen(msg), 0) == -1) {
41+		perror("couldn't send IPC message");
42+	}
43+
44+	close(sock);
45+}
46+
47+int
48+main(int argc, char **argv) {
49+	if (argc < 2) {
50+		fprintf(stderr, "usage: %s cmd [args]\n", argv[0]);
51+		return 1;
52+	}
53+	argc--;
54+	argv++;
55+
56+	for (int i = 0; i < (int)(sizeof commands / sizeof commands[0]); i++) {
57+		if (strcmp(commands[i].name, argv[0]) == 0) {
58+			if (commands[i].argc != argc) {
59+				fprintf(stderr, "not enough arguments for %s (need %d))\n", commands[i].name, commands[i].argc);
60+				return 1;
61+			}
62+			ipc_msg(argv);
63+			return 0;
64+		}
65+	}
66+	
67+	fprintf(stderr, "no such command %s", argv[0]);
68+	return 1;
69+}
A howl.c
+262, -0
  1@@ -0,0 +1,262 @@
  2+#include <stdio.h>
  3+#include <stdlib.h>
  4+#include <string.h>
  5+#include <errno.h>
  6+#include <signal.h>
  7+#include <unistd.h>
  8+#include <sys/socket.h>
  9+#include <sys/un.h>
 10+
 11+#include <wayland-server.h>
 12+#include <swc.h>
 13+
 14+#include "howl.h"
 15+#include "window.h"
 16+
 17+static void new_screen(struct swc_screen *);
 18+static void new_window(struct swc_window *);
 19+static void on_win_destroy(void *);
 20+static void on_win_entered(void *);
 21+static void on_scr_destroy(void *);
 22+static bool is_ws_client(const struct client *, const struct screen *);
 23+
 24+struct wm wm;
 25+struct swc_manager mgr = {
 26+	.new_screen = &new_screen,
 27+	.new_window = &new_window
 28+};
 29+
 30+struct swc_window_handler win_handler = {
 31+	.destroy = on_win_destroy,
 32+	.entered = on_win_entered
 33+};
 34+
 35+struct swc_screen_handler scr_handler = {
 36+	.destroy = on_scr_destroy
 37+};
 38+
 39+static int
 40+handler(int fd, uint32_t mask, void *data) {
 41+	UNUSED(mask);
 42+	UNUSED(data);
 43+
 44+	char buf[256];
 45+	ssize_t res = read(fd, buf, sizeof(buf) - 1);
 46+
 47+	if (res < 0) {
 48+		_wrn("couldn't read from IPC socket: %s", strerror(errno));
 49+		return 1;
 50+	}
 51+	buf[res] = '\0';
 52+
 53+	/* TODO: Parse response. */
 54+
 55+	return 0;
 56+}
 57+
 58+static void
 59+setup_ipc(void) {
 60+	struct sockaddr_un addr;
 61+
 62+	sock = socket(AF_LOCAL, SOCK_STREAM, 0);
 63+	if (sock == -1)
 64+		_err(1, "couldn't create IPC socket: %s", strerror(errno));
 65+
 66+	memset(&addr, 0, sizeof(addr));
 67+	addr.sun_family = AF_LOCAL;
 68+	strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);
 69+
 70+	if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1)
 71+		_err(1, "couldn't bind IPC socket: %s", strerror(errno));
 72+
 73+	if (listen(sock, 5) == -1)
 74+		_err(1, "couldn't listen on IPC socket: %s", strerror(errno));
 75+
 76+	wl_event_loop_add_fd(wm.loop, sock, WL_EVENT_READABLE, handler, NULL);
 77+}
 78+
 79+static void
 80+sig_handler(int s) {
 81+	_wrn("caught deadly signal, exiting!");
 82+	if (wm.dpy) {		
 83+		swc_finalize();
 84+		wl_display_terminate(wm.dpy);
 85+		close(sock);
 86+	}
 87+}
 88+
 89+static void
 90+setup(void) {
 91+	wm.dpy = wl_display_create();
 92+	if (!wm.dpy) _err(1, "couldn't create Wayland display");
 93+
 94+	wl_list_init(&wm.screens);
 95+	wl_list_init(&wm.clients);
 96+	wm.cur         = NULL;
 97+	wm.scr         = NULL;
 98+	wm.grab.active = false;
 99+	wm.grab.resize = false;
100+	wm.grab.c      = NULL;
101+	wm.ws          = 1;
102+
103+	wm.loop = wl_display_get_event_loop(wm.dpy);
104+	if (!swc_initialize(wm.dpy, wm.loop, &mgr))
105+		_err(1, "couldn't initialize swc");
106+
107+	/* TODO: Parse config. */
108+
109+	const char *sock = wl_display_add_socket_auto(wm.dpy);
110+	if (!sock) _err(1, "couldn't add Wayland display socket");
111+
112+	setenv("WAYLAND_DISPLAY", sock, 1);
113+	_inf("set WAYLAND_DISPLAY=%s", sock);
114+
115+	setup_ipc();
116+
117+	signal(SIGINT,  sig_handler);
118+	signal(SIGTERM, sig_handler);
119+	signal(SIGQUIT, sig_handler);
120+}
121+
122+static void
123+cleanup(void) {
124+	swc_finalize();
125+	wl_display_destroy(wm.dpy);
126+	close(sock);
127+}
128+
129+static void
130+focus(struct client *c) {
131+	if (wm.cur)
132+		swc_window_set_border(
133+			wm.cur->win,
134+			0xFF333333, 2,
135+			0, 0
136+		);
137+
138+	if (c)
139+		swc_window_set_border(
140+			wm.cur->win,
141+			0xFFFF0000, 2,
142+			0, 0
143+		);
144+
145+	swc_window_focus(c ? c->win : NULL);
146+	wm.cur = c;
147+}
148+
149+static struct client *
150+first_client(struct screen *s) {
151+	struct client *c;
152+
153+	wl_list_for_each(c, &wm.clients, link) {
154+		if (is_ws_client(c, s)) return c;
155+	}
156+
157+	return NULL;
158+}
159+
160+static bool
161+is_ws_client(const struct client *c, const struct screen *s) {
162+	return c && c->ws == wm.ws && (!s || c->scr == s);
163+}
164+
165+static void
166+new_screen(struct swc_screen *scr) {
167+	struct screen *s;
168+	s = malloc(sizeof(*s));
169+	if (!s) _err(1, "couldn't allocate screen");
170+
171+	s->scr    = scr;
172+	s->x      = 0;
173+	s->y      = 0;
174+	s->width  = scr->usable_geometry.width;
175+	s->height = scr->usable_geometry.height;
176+
177+	wl_list_insert(&wm.screens, &s->link);
178+	if (!wm.scr) wm.scr = s;
179+	swc_screen_set_handler(scr, &scr_handler, s);
180+	_inf("new_screen=%p", (void *)scr);
181+}
182+
183+static void
184+new_window(struct swc_window *win) {
185+	struct client *c;
186+	c = malloc(sizeof(*c));
187+	if (!c) _err(1, "couldn't allocate client");
188+
189+	win->motion_throttle_ms = 1000 / 85;
190+	win->min_width          = 1;
191+	win->min_height         = 1;
192+	win->max_width          = 0;
193+	win->max_height         = 0;
194+
195+	c->win        = win;
196+	c->scr        = wm.scr;
197+	c->visible    = 0;
198+	c->fullscreen = 0;
199+	c->ws         = wm.ws;
200+	c->x          = 0;
201+	c->y          = 0;
202+	c->width      = 0;
203+	c->height     = 0;
204+
205+	wl_list_insert(&wm.clients, &c->link);
206+	swc_window_set_handler(win, &win_handler, c);
207+	swc_window_set_stacked(win);
208+	do_center((char **){0});
209+	swc_window_show(win);
210+	focus(c);
211+	_inf("new_window=%p", (void *)win);
212+}
213+
214+static void
215+on_win_entered(void *data) {
216+	if (wm.grab.active) return;
217+
218+	struct client *c = data;
219+	if (!is_ws_client(c, NULL)) return;
220+
221+	focus(c);
222+}
223+
224+static void
225+on_win_destroy(void *data) {
226+	struct client *c = data, *next;
227+
228+	if (!c) return;
229+	if (wm.grab.active && wm.grab.c == c) {
230+		wm.grab.active = false;
231+		wm.grab.c = NULL;
232+	}
233+
234+	wl_list_remove(&c->link);
235+	free(c);
236+
237+	next = first_client(wm.scr);
238+	if (!next) next = first_client(NULL);
239+	focus(next);
240+}
241+
242+static void
243+on_scr_destroy(void *data) {
244+	struct screen *s = data;
245+	if (!s) return;
246+
247+	wl_list_remove(&s->link);
248+	if (wm.scr == s) {
249+		if (wl_list_empty(&wm.screens))
250+			wm.scr = NULL;
251+		else
252+			wm.scr = wl_container_of(wm.screens.next, wm.scr, link);
253+	}
254+	free(s);
255+}
256+
257+int
258+main(void) {
259+	setup();
260+	wl_display_run(wm.dpy);
261+	cleanup();
262+	return 1; /* NOTREACHED */
263+}
+14, -0
 1@@ -0,0 +1,14 @@
 2+#ifndef HOWL_H
 3+#define HOWL_H
 4+
 5+#define UNUSED(x) (void)x
 6+#define SOCK_PATH "/tmp/.howl.sock"
 7+
 8+/* IPC socket */
 9+static int sock;
10+
11+extern void _inf(const char *, ...);
12+extern void _wrn(const char *, ...);
13+extern void _err(int, const char *, ...);
14+
15+#endif /* HOWL_H */
+54, -0
 1@@ -0,0 +1,54 @@
 2+#ifndef TYPES_H
 3+#define TYPES_H
 4+
 5+#include <stdbool.h>
 6+#include <stdint.h>
 7+
 8+#include <wayland-server.h>
 9+#include <swc.h>
10+
11+struct command {
12+	const char  *name;
13+	int         argc;
14+	int         (*fn)(char **);
15+};
16+
17+struct client {
18+	struct wl_list    link;
19+	struct swc_window *win;
20+	struct screen     *scr;
21+
22+	bool              visible;
23+	bool              fullscreen;
24+
25+	int32_t           x, y;
26+	uint32_t          width, height;
27+	uint8_t           ws;
28+};
29+
30+struct screen {
31+	struct wl_list     link;
32+	struct swc_screen  *scr;
33+	int32_t            x, y;
34+	uint32_t           width, height;
35+};
36+
37+struct grab {
38+	bool           active, resize;
39+	struct client  *c;
40+};
41+
42+struct wm {
43+	struct wl_display     *dpy;
44+	struct wl_event_loop  *loop;
45+
46+	struct wl_list        clients;
47+	struct wl_list        screens;
48+
49+	struct screen         *scr;
50+	struct client         *cur;
51+	struct grab           grab;
52+	uint8_t               ws;
53+};
54+
55+#endif /* TYPES_H */
+35, -0
 1@@ -0,0 +1,35 @@
 2+#ifndef WINDOW_H
 3+#define WINDOW_H
 4+
 5+#include "types.h"
 6+
 7+extern int do_move(char **);
 8+extern int do_resize(char **);
 9+extern int do_teleport(char **);
10+extern int do_center(char **);
11+extern int do_hide(char **);
12+extern int do_show(char **);
13+extern int do_close(char **);
14+extern int do_title(char **);
15+
16+extern int get_geometry(char **);
17+extern int get_pid(char **);
18+extern int get_title(char **);
19+extern int get_app_id(char **);
20+
21+static const struct command commands[] = {
22+	{ "move",      2,  do_move      },
23+	{ "resize",    2,  do_resize    },
24+	{ "teleport",  4,  do_teleport  },
25+	{ "center",    0,  do_center    },
26+	{ "hide",      0,  do_hide      },
27+	{ "show",      0,  do_show      },
28+	{ "close",     0,  do_close     },
29+	{ "title",     1,  do_title     },
30+	{ "geometry",  0,  get_geometry },
31+	{ "pid",       0,  get_pid      },
32+	{ "title",     0,  get_title    },
33+	{ "app_id",    0,  get_app_id   }
34+};
35+
36+#endif /* WINDOW_H */
A log.c
+46, -0
 1@@ -0,0 +1,46 @@
 2+#include <stdio.h>
 3+#include <stdarg.h>
 4+
 5+#include "howl.h"
 6+
 7+void
 8+_inf(const char *msg, ...) {
 9+	va_list list;
10+
11+	fprintf(stdout, "INFO:   ");
12+
13+	va_start(list, msg);
14+	vfprintf(stdout, msg, list);
15+	va_end(list);
16+
17+	fputc('\n', stdout);
18+	fflush(stdout);
19+}
20+
21+void
22+_wrn(const char *msg, ...) {
23+	va_list list;
24+
25+	fprintf(stderr, "WARN:   ");
26+
27+	va_start(list, msg);
28+	vfprintf(stderr, msg, list);
29+	va_end(list);
30+
31+	fputc('\n', stderr);
32+	fflush(stderr);
33+}
34+
35+void
36+_err(int ret, const char *msg, ...) {
37+	va_list list;
38+
39+	fprintf(stderr, "FATAL:  ");
40+
41+	va_start(list, msg);
42+	vfprintf(stderr, msg, list);
43+	va_end(list);
44+
45+	fputc('\n', stderr);
46+	fflush(stderr);
47+}
+126, -0
  1@@ -0,0 +1,126 @@
  2+#include <stdio.h>
  3+#include <stdlib.h>
  4+
  5+#include <swc.h>
  6+
  7+#include "howl.h"
  8+#include "types.h"
  9+#include "window.h"
 10+
 11+struct wm wm;
 12+
 13+int
 14+do_move(char **arg) {
 15+	if (arg[0] == NULL || arg[1] == NULL) return 1;
 16+
 17+	swc_window_set_position(wm.cur->win, atoi(arg[0]), atoi(arg[1]));
 18+	return 0;
 19+}
 20+
 21+int
 22+do_resize(char **arg) {
 23+	if (arg[0] == NULL || arg[1] == NULL) return 1;
 24+
 25+	swc_window_set_size(wm.cur->win, strtoul(arg[0], NULL, 0), strtoul(arg[1], NULL, 0));
 26+	return 0;
 27+}
 28+
 29+int
 30+do_teleport(char **arg) {
 31+	if (arg[0] == NULL || arg[1] == NULL ||
 32+	    arg[2] == NULL || arg[3] == NULL) return 1;
 33+
 34+	struct swc_rectangle rect = {
 35+		.x      = atoi(arg[0]),
 36+		.y      = atoi(arg[1]),
 37+		.width  = strtoul(arg[2], NULL, 0),
 38+		.height = strtoul(arg[3], NULL, 0)
 39+	};
 40+	swc_window_set_geometry(wm.cur->win, &rect);
 41+	return 0;
 42+}
 43+
 44+int
 45+do_center(char **arg) {
 46+	UNUSED(arg);
 47+
 48+	swc_window_set_position(
 49+		wm.cur->win,
 50+		(wm.scr->width - wm.cur->width)  / 2,
 51+		(wm.scr->height - wm.cur->height) / 2
 52+	);
 53+	return 0;
 54+}
 55+
 56+int
 57+do_hide(char **arg) {
 58+	UNUSED(arg);
 59+
 60+	swc_window_hide(wm.cur->win);
 61+	return 0;
 62+}
 63+
 64+int
 65+do_show(char **arg) {
 66+	UNUSED(arg);
 67+
 68+	swc_window_show(wm.cur->win);
 69+	return 0;
 70+}
 71+
 72+int
 73+do_close(char **arg) {
 74+	UNUSED(arg);
 75+
 76+	swc_window_close(wm.cur->win);
 77+	return 0;
 78+}
 79+
 80+int
 81+do_title(char **arg) {
 82+	if (arg[0] == NULL) return 1;
 83+
 84+	/* TODO */
 85+	return 0;
 86+}
 87+
 88+/*
 89+ * window info
 90+ */
 91+
 92+int
 93+get_geometry(char **arg) {
 94+	UNUSED(arg);
 95+
 96+	struct swc_rectangle geom;
 97+	if (swc_window_get_geometry(wm.cur->win, &geom)) {
 98+		printf("%d %d %u %u\n", geom.x, geom.y, geom.width, geom.height);  
 99+		return 0;
100+	}
101+	return 1;
102+}
103+
104+int
105+get_pid(char **arg) {
106+	UNUSED(arg);
107+
108+	pid_t pid = swc_window_get_pid(wm.cur->win);
109+	printf("%d\n", pid);
110+	return 0;
111+}
112+
113+int
114+get_title(char **arg) {
115+	UNUSED(arg);
116+
117+	printf("%s\n", wm.cur->win->title);
118+	return 0;
119+}
120+
121+int
122+get_app_id(char **arg) {
123+	UNUSED(arg);
124+
125+	printf("%s\n", wm.cur->win->app_id);
126+	return 0;
127+}