commit bb4b24a
wf
·
2026-03-25 17:36:00 +0000 UTC
parent bb4b24a
initial
+2,
-0
1@@ -0,0 +1,2 @@
2+howl
3+howlc
A
Makefile
+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
A
client.c
+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+}
A
window.c
+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+}