ohmu
·
2026-07-25
1/* mwtitle - titlebar daemon */
2// vibecoded, for educational purposes only
3
4#include <xcb/xcb.h>
5#include <stdlib.h>
6#include <stdio.h>
7#include <string.h>
8#include <err.h>
9#include <signal.h>
10#include <unistd.h>
11
12static xcb_connection_t *conn;
13static xcb_screen_t *scr;
14static int running = 1;
15
16typedef struct {
17 xcb_window_t win;
18 xcb_window_t title;
19 int height;
20} Client;
21
22static Client *clients = NULL;
23static int client_count = 0;
24
25static void
26cleanup(void)
27{
28 if (conn)
29 xcb_disconnect(conn);
30 free(clients);
31}
32
33static void
34signal_handler(int sig)
35{
36 (void)sig;
37 running = 0;
38}
39
40static void
41init_xcb(void)
42{
43 conn = xcb_connect(NULL, NULL);
44 if (xcb_connection_has_error(conn))
45 errx(1, "cannot connect to X server");
46}
47
48static void
49get_screen(void)
50{
51 const xcb_setup_t *setup = xcb_get_setup(conn);
52 xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
53 scr = iter.data;
54}
55
56static int
57find_client(xcb_window_t win)
58{
59 for (int i = 0; i < client_count; i++) {
60 if (clients[i].win == win)
61 return i;
62 }
63 return -1;
64}
65
66static int
67is_titlebar(xcb_window_t win)
68{
69 for (int i = 0; i < client_count; i++) {
70 if (clients[i].title == win)
71 return 1;
72 }
73 return 0;
74}
75
76static void
77remove_client(int idx)
78{
79 if (idx < 0 || idx >= client_count)
80 return;
81
82 if (clients[idx].title)
83 xcb_destroy_window(conn, clients[idx].title);
84 xcb_flush(conn);
85
86 if (idx < client_count - 1) {
87 memmove(&clients[idx], &clients[idx + 1],
88 (client_count - idx - 1) * sizeof(Client));
89 }
90 client_count--;
91 clients = realloc(clients, client_count * sizeof(Client));
92}
93
94static int
95window_is_visible(xcb_window_t win)
96{
97 xcb_get_window_attributes_reply_t *attrs;
98 attrs = xcb_get_window_attributes_reply(conn,
99 xcb_get_window_attributes(conn, win), NULL);
100 if (!attrs)
101 return 0;
102
103 int visible = (attrs->map_state == XCB_MAP_STATE_VIEWABLE &&
104 !attrs->override_redirect);
105 free(attrs);
106 return visible;
107}
108
109static void
110add_titlebar(xcb_window_t win, int height)
111{
112 xcb_get_geometry_reply_t *geom;
113 xcb_window_t title_win;
114 uint32_t values[8];
115 uint32_t mask;
116
117 if (is_titlebar(win))
118 return;
119
120 if (find_client(win) >= 0)
121 return;
122
123 if (!window_is_visible(win) || win == scr->root)
124 return;
125
126 geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
127 if (!geom)
128 return;
129
130 if (geom->width < 10 || geom->height < 10) {
131 free(geom);
132 return;
133 }
134
135 xcb_translate_coordinates_reply_t *trans;
136 trans = xcb_translate_coordinates_reply(conn,
137 xcb_translate_coordinates(conn, win, scr->root, 0, 0), NULL);
138
139 int win_x = geom->x;
140 int win_y = geom->y;
141 if (trans) {
142 win_x = trans->dst_x;
143 win_y = trans->dst_y;
144 free(trans);
145 }
146
147 title_win = xcb_generate_id(conn);
148
149 mask = XCB_CW_BACK_PIXEL |
150 XCB_CW_BORDER_PIXEL |
151 XCB_CW_OVERRIDE_REDIRECT |
152 XCB_CW_EVENT_MASK;
153
154 values[0] = 0x000000;
155 values[1] = 0x000000;
156 values[2] = 1;
157 values[3] = XCB_EVENT_MASK_EXPOSURE;
158
159 xcb_create_window(conn, XCB_COPY_FROM_PARENT, title_win, scr->root,
160 win_x, win_y - height,
161 geom->width, height,
162 0,
163 XCB_WINDOW_CLASS_INPUT_OUTPUT,
164 scr->root_visual,
165 mask, values);
166
167 clients = realloc(clients, (client_count + 1) * sizeof(Client));
168 clients[client_count].win = win;
169 clients[client_count].title = title_win;
170 clients[client_count].height = height;
171 client_count++;
172
173 xcb_map_window(conn, title_win);
174
175 uint32_t stack_values[] = { XCB_STACK_MODE_ABOVE };
176 xcb_configure_window(conn, title_win,
177 XCB_CONFIG_WINDOW_STACK_MODE, stack_values);
178
179 xcb_flush(conn);
180
181 free(geom);
182}
183
184static void
185update_titlebar(xcb_window_t win)
186{
187 int idx = find_client(win);
188 if (idx < 0)
189 return;
190
191 xcb_get_geometry_reply_t *geom;
192 uint32_t values[4];
193
194 geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
195 if (!geom)
196 return;
197
198 xcb_translate_coordinates_reply_t *trans;
199 trans = xcb_translate_coordinates_reply(conn,
200 xcb_translate_coordinates(conn, win, scr->root, 0, 0), NULL);
201
202 int win_x = geom->x;
203 int win_y = geom->y;
204 if (trans) {
205 win_x = trans->dst_x;
206 win_y = trans->dst_y;
207 free(trans);
208 }
209
210 values[0] = win_x;
211 values[1] = win_y - clients[idx].height;
212 values[2] = geom->width;
213 values[3] = clients[idx].height;
214
215 xcb_configure_window(conn, clients[idx].title,
216 XCB_CONFIG_WINDOW_X |
217 XCB_CONFIG_WINDOW_Y |
218 XCB_CONFIG_WINDOW_WIDTH |
219 XCB_CONFIG_WINDOW_HEIGHT,
220 values);
221
222 uint32_t stack_values[] = { XCB_STACK_MODE_ABOVE };
223 xcb_configure_window(conn, clients[idx].title,
224 XCB_CONFIG_WINDOW_STACK_MODE, stack_values);
225
226 xcb_flush(conn);
227 free(geom);
228}
229
230static void
231scan_existing_windows(int height)
232{
233 xcb_query_tree_reply_t *tree;
234 xcb_window_t *children;
235
236 tree = xcb_query_tree_reply(conn, xcb_query_tree(conn, scr->root), NULL);
237 if (!tree)
238 return;
239
240 children = xcb_query_tree_children(tree);
241 int num_children = xcb_query_tree_children_length(tree);
242
243 for (int i = 0; i < num_children; i++) {
244 xcb_window_t win = children[i];
245 add_titlebar(win, height);
246 }
247
248 free(tree);
249}
250
251int
252main(int argc, char **argv)
253{
254 xcb_generic_event_t *ev;
255 int height = 30;
256
257 init_xcb();
258 get_screen();
259 atexit(cleanup);
260
261 signal(SIGINT, signal_handler);
262 signal(SIGTERM, signal_handler);
263
264 uint32_t values[] = {
265 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
266 XCB_EVENT_MASK_PROPERTY_CHANGE
267 };
268 xcb_change_window_attributes(conn, scr->root, XCB_CW_EVENT_MASK, values);
269 xcb_flush(conn);
270
271 scan_existing_windows(height);
272
273 printf("mwtitle started. Height: %dpx\n", height);
274
275 while (running && (ev = xcb_wait_for_event(conn))) {
276 switch (ev->response_type & ~0x80) {
277 case XCB_CONFIGURE_NOTIFY: {
278 xcb_configure_notify_event_t *cn = (xcb_configure_notify_event_t *)ev;
279 if (cn->window != scr->root && !is_titlebar(cn->window)) {
280 update_titlebar(cn->window);
281 }
282 break;
283 }
284 case XCB_DESTROY_NOTIFY: {
285 xcb_destroy_notify_event_t *d = (xcb_destroy_notify_event_t *)ev;
286 if (d->window != scr->root) {
287 int idx = find_client(d->window);
288 if (idx >= 0) {
289 remove_client(idx);
290 }
291 }
292 break;
293 }
294 case XCB_UNMAP_NOTIFY: {
295 xcb_unmap_notify_event_t *u = (xcb_unmap_notify_event_t *)ev;
296 if (u->window != scr->root && !is_titlebar(u->window)) {
297 int idx = find_client(u->window);
298 if (idx >= 0) {
299 xcb_unmap_window(conn, clients[idx].title);
300 xcb_flush(conn);
301 }
302 }
303 break;
304 }
305 case XCB_MAP_NOTIFY: {
306 xcb_map_notify_event_t *m = (xcb_map_notify_event_t *)ev;
307 if (m->window != scr->root && !is_titlebar(m->window)) {
308 int idx = find_client(m->window);
309 if (idx >= 0) {
310 xcb_map_window(conn, clients[idx].title);
311 update_titlebar(m->window);
312 xcb_flush(conn);
313 } else {
314 add_titlebar(m->window, height);
315 }
316 }
317 break;
318 }
319 }
320 free(ev);
321 }
322
323 return 0;
324}