master ohmu/wmutils / src / opt / mwtitle.c
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}