main wf/panko / src / panko.c
  1#include <stdio.h>
  2#include <stdlib.h>
  3#include <unistd.h>
  4#include <string.h>
  5#include <inttypes.h>
  6#include <stdbool.h>
  7#include <getopt.h>
  8#include <signal.h>
  9#include <errno.h>
 10#include <fcntl.h>
 11#include <poll.h>
 12#include <time.h>
 13#include <sys/wait.h>
 14
 15#include <wayland-client.h>
 16#include <wld/wld.h>
 17#include <wld/wayland.h>
 18
 19#include <scfg.h>
 20
 21#include "xdg-shell-protocol.h"
 22#include "wlr-layer-shell-unstable-v1.h"
 23
 24#define MAXMOD 6
 25
 26#define LOG(...) fprintf(stderr, "panko: " __VA_ARGS__)
 27#define ERRNUM(elem) \
 28	LOG("at line %d: wrong number of values for option `%s`\n", elem.lineno, elem.name); return false
 29#define ERRINC(elem) \
 30	LOG("at line %d: incorrect value for option `%s`\n", elem.lineno, elem.name); return false
 31
 32enum side { SIDE_LEFT, SIDE_MIDDLE, SIDE_RIGHT };
 33struct module {
 34	char *name, *command;
 35	uint32_t interval;
 36	int32_t margins[4], padding[4];
 37	uint32_t width, height;
 38	struct {
 39		uint32_t foreground, background;
 40		char *font;
 41		int border_width;
 42		uint32_t border_color;
 43	} style;
 44
 45	pid_t pid;
 46	int fd;
 47	char *buf;
 48	size_t buflen;
 49	uint64_t next;
 50	struct wld_font *font;
 51};
 52
 53struct config {
 54	uint32_t width, height;
 55	enum zwlr_layer_surface_v1_anchor anchor;
 56	int32_t margins[4];
 57	bool exclusive;
 58	struct {
 59		uint32_t foreground, background;
 60		char *font;
 61	} style;
 62};
 63
 64struct panel {
 65	struct module **parsed, *modules[3][MAXMOD];
 66	size_t plen;
 67};
 68
 69struct app {
 70	struct wl_display *dpy;
 71	struct wl_registry *reg;
 72	struct wl_compositor *comp;
 73	struct zwlr_layer_shell_v1 *layer_shell;
 74
 75	/* panel resources */
 76	struct wl_surface *surface;
 77	struct zwlr_layer_surface_v1 *layer_surface;
 78
 79	/* drawing resources */
 80	struct wld_context *wld_ctx;
 81	struct wld_renderer *wld_ren;
 82	struct wld_font_context *wld_fctx;
 83	struct wld_font *wld_font;
 84	struct wld_surface *wld_surface;
 85
 86	struct config config;
 87	struct panel panel;
 88};
 89
 90static volatile sig_atomic_t running = 1;
 91static char config_path[512];
 92static bool have_config = true;
 93
 94static void
 95handle_global(void *data, struct wl_registry *reg, uint32_t name,
 96	const char *iface, uint32_t ver) {
 97
 98	struct app *app = data;
 99
100	if (strcmp(iface, "wl_compositor") == 0) {
101		app->comp =
102			wl_registry_bind(reg, name, &wl_compositor_interface, ver < 4 ? ver : 4);
103		return;
104	}
105
106	if (strcmp(iface, "zwlr_layer_shell_v1") == 0) {
107		app->layer_shell =
108			wl_registry_bind(reg, name, &zwlr_layer_shell_v1_interface, 5);
109		return;
110	}
111}
112
113static void
114handle_global_remove(void *data, struct wl_registry *reg, uint32_t name) {
115	/* no-op */
116}
117
118static const struct wl_registry_listener reg_listener = {
119	.global = handle_global,
120	.global_remove = handle_global_remove
121};
122
123static void
124handle_panel_configure(void *data, struct zwlr_layer_surface_v1 *layer_surface,
125	uint32_t serial, uint32_t width, uint32_t height) {
126
127	/* ignored */
128}
129
130static void
131handle_panel_close(void *data, struct zwlr_layer_surface_v1 *layer_surface) {
132	zwlr_layer_surface_v1_destroy(layer_surface);
133}
134
135static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
136	.configure = handle_panel_configure,
137	.closed = handle_panel_close
138};
139
140static uint64_t
141now_ms(void) {
142	struct timespec ts;
143	clock_gettime(CLOCK_MONOTONIC, &ts);
144	return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
145}
146
147static void
148module_add(struct app *app, struct module *m, enum side side) {
149	for (size_t i = 0; i < MAXMOD; i++) {
150		if (app->panel.modules[side][i] == NULL) {
151			app->panel.modules[side][i] = m;
152			return;
153		}
154	}
155	LOG("too many modules on the %s side\n",
156		(side == SIDE_LEFT) ? "left" : (side == SIDE_MIDDLE) ? "middle" : "right");
157}
158
159static bool
160module_run(struct module *m) {
161	if (!m->command)
162		return false;
163
164	int fds[2];
165	if (pipe(fds) < 0)
166		return false;
167
168	pid_t pid = fork();
169	if (pid < 0) {
170		close(fds[0]);
171		close(fds[1]);
172		return false;
173	}
174
175	if (pid == 0) {
176		dup2(fds[1], STDOUT_FILENO);
177		close(fds[0]);
178		close(fds[1]);
179		execl("/bin/sh", "sh", "-c", m->command, NULL);
180		_exit(127);
181	}
182
183	close(fds[1]);
184	m->pid = pid;
185	m->fd = fds[0];
186
187	int f = fcntl(m->fd, F_GETFL, 0);
188	if (f < 0 || fcntl(m->fd, F_SETFL, f | O_NONBLOCK) < 0) {
189		LOG("while running module `%s`: couldn't fcntl: %s\n", m->name, strerror(errno));
190		close(m->fd);
191		m->fd = -1;
192		kill(m->pid, SIGTERM);
193		m->pid = 0;
194		return false;
195	}
196
197	m->next = m->interval ? now_ms() + m->interval * 1000 : UINT64_MAX;
198	return true;
199}
200
201static void
202module_out(struct module *m) {
203	if (m->fd < 0)
204		return;
205	char buf[1024];
206	size_t total = 0;
207	ssize_t r = 0;
208	char *out = NULL;
209
210	while ((r = read(m->fd, buf, sizeof(buf))) > 0) {
211		char *n = realloc(out, total + r + 1);
212		if (!n) {
213			free(out);
214			out = NULL;
215			break;
216		}
217		out = n;
218		memcpy(out + total, buf, r);
219		total += r;
220		out[total] = '\0';
221	}
222
223	if (r <= 0) {
224		if (errno == EAGAIN || errno == EWOULDBLOCK)
225			; /* non-problem */
226		else
227			LOG("while running module `%s`: couldn't read: %s\n", m->name, strerror(errno));
228		int ret;
229		waitpid(m->pid, &ret, WNOHANG);
230		close(m->fd);
231		m->pid = 0;
232		m->fd = -1;
233		m->next = m->interval ? now_ms() + m->interval * 1000 : UINT64_MAX;
234	}
235
236	if (out) {
237		while (total > 0 && (out[total-1] == '\n' || out[total-1] == '\r'))
238			out[--total] = '\0';
239		free(m->buf);
240		m->buf = out;
241		m->buflen = total;
242	} else {
243		free(m->buf);
244		m->buf = strdup("");
245		m->buflen = 0;
246	}
247}
248
249static bool
250parse_config(const char *path, struct app *app) {
251	struct scfg_block block;
252
253	int ret = scfg_load_file(&block, path);
254	if (ret < 0) {
255		LOG("couldn't load config file: %s\n", strerror(-ret));
256		return false;
257	}
258
259	for (size_t i = 0; i < block.directives_len; i++) {
260		struct scfg_directive d = block.directives[i];
261
262		if (strcmp(d.name, "width") == 0 || strcmp(d.name, "height") == 0) {
263			if (d.params_len != 1) {
264				ERRNUM(d);
265			}
266			uint32_t *k = d.name[0] == 'w' ? &app->config.width : &app->config.height;
267			*k = strtoul(d.params[0], NULL, 0);
268		}
269
270		else if (strcmp(d.name, "anchor") == 0) {
271			if (d.params_len > 4 || d.params_len <= 0) {
272				ERRNUM(d);
273			}
274			for (size_t j = 0; j < d.params_len; j++) {
275				if (strcmp(d.params[j], "top") == 0)
276					app->config.anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
277				else if (strcmp(d.params[j], "right") == 0)
278					app->config.anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
279				else if (strcmp(d.params[j], "bottom") == 0)
280					app->config.anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
281				else if (strcmp(d.params[j], "left") == 0)
282					app->config.anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
283				else {
284					ERRINC(d);
285				}
286			}
287		}
288
289		else if (strcmp(d.name, "margins") == 0) {
290			if (d.params_len != 4) {
291				ERRNUM(d);
292			}
293			for (int j = 0; j < 4; j++)
294				app->config.margins[j] = strtol(d.params[j], NULL, 0);
295		}
296
297		else if (strcmp(d.name, "exclusive") == 0) {
298			if (d.params_len != 1) {
299				ERRNUM(d);
300			}
301			if (strcmp(d.params[0], "true") == 0)
302				app->config.exclusive = true;
303			else if (strcmp(d.params[0], "false") == 0)
304				app->config.exclusive = false;
305			else {
306				ERRINC(d);
307			}
308		}
309
310		else if (strcmp(d.name, "style") == 0) {
311			struct scfg_block sub = d.children;
312			for (size_t j = 0; j < sub.directives_len; j++) {
313				struct scfg_directive s = sub.directives[j];
314
315				if (strcmp(s.name, "foreground") == 0) {
316					if (s.params_len != 1) {
317						ERRNUM(s);
318					}
319					wld_lookup_named_color(s.params[0], &app->config.style.foreground);
320				}
321
322				else if (strcmp(s.name, "background") == 0) {
323					if (s.params_len != 1) {
324						ERRNUM(s);
325					}
326					wld_lookup_named_color(s.params[0], &app->config.style.background);
327				}
328
329				else if (strcmp(s.name, "font") == 0) {
330					if (s.params_len != 1) {
331						ERRNUM(s);
332					}
333					app->config.style.font = strdup(s.params[0]);
334				}
335			}
336		}
337
338		else if (strcmp(d.name, "module") == 0) {
339			if (d.params_len != 1) {
340				ERRNUM(d);
341			}
342			struct module m = {0};
343			memset(&m, 0, sizeof(m));
344			m.name = strdup(d.params[0]);
345
346			m.style.foreground = 0xDDDDDD;
347			m.style.background = 0x121212;
348			m.style.font = strdup(app->config.style.font);
349			m.style.border_width = 2;
350			m.style.border_color = 0x242424;
351
352			struct scfg_block sub = d.children;
353			for (size_t j = 0; j < sub.directives_len; j++) {
354				struct scfg_directive s = sub.directives[j];
355
356				if (strcmp(s.name, "command") == 0) {
357					if (s.params_len != 1) {
358						ERRNUM(s);
359					}
360					m.command = strdup(s.params[0]);
361				}
362
363				else if (strcmp(s.name, "interval") == 0) {
364					if (s.params_len != 1) {
365						ERRNUM(s);
366					}
367					m.interval = strtoul(s.params[0], NULL, 0);
368				}
369
370				else if (strcmp(s.name, "margins") == 0 || strcmp(s.name, "padding") == 0) {
371					if (s.params_len != 4) {
372						ERRNUM(s);
373					}
374					int32_t *k = s.name[0] == 'm' ? m.margins : m.padding;
375					for (int y = 0; y < 4; y++)
376						k[y] = strtol(s.params[y], NULL, 0);
377				}
378
379				else if (strcmp(s.name, "width") == 0 || strcmp(s.name, "height") == 0) {
380					if (s.params_len != 1) {
381						ERRNUM(s);
382					}
383					uint32_t *k = s.name[0] == 'w' ? &m.width : &m.height;
384					*k = strtoul(s.params[0], NULL, 0);
385				}
386
387				else if (strcmp(s.name, "style") == 0) {
388					struct scfg_block ssub = s.children;
389					for (size_t n = 0; n < ssub.directives_len; n++) {
390						struct scfg_directive t = ssub.directives[n];
391
392						if (strcmp(t.name, "background") == 0) {
393							if (t.params_len != 1) {
394								ERRNUM(t);
395							}
396							wld_lookup_named_color(t.params[0], &m.style.background);
397						}
398
399						else if (strcmp(t.name, "foreground") == 0) {
400							if (t.params_len != 1) {
401								ERRNUM(t);
402							}
403							wld_lookup_named_color(t.params[0], &m.style.foreground);
404						}
405
406						else if (strcmp(t.name, "font") == 0) {
407							if (t.params_len != 1) {
408								ERRNUM(t);
409							}
410							m.style.font = strdup(t.params[0]);
411						}
412
413						else if (strcmp(t.name, "border-width") == 0) {
414							if (t.params_len != 1) {
415								ERRNUM(t);
416							}
417							m.style.border_width = atoi(t.params[0]);
418						}
419
420						else if (strcmp(t.name, "border-color") == 0) {
421							if (t.params_len != 1) {
422								ERRNUM(t);
423							}
424							wld_lookup_named_color(t.params[0], &m.style.border_color);
425						}
426					}
427				}
428			}
429
430			struct module *new = calloc(1, sizeof(*new));
431			if (!new) {
432				LOG("couldn't allocate\n");
433				if (m.name) free(m.name);
434				if (m.command) free(m.command);
435				if (m.style.font) free(m.style.font);
436				scfg_block_finish(&block);
437				return false;
438			}
439			*new = m;
440
441			app->panel.parsed = realloc(app->panel.parsed, (app->panel.plen + 1) * sizeof(*app->panel.parsed));
442			if (!app->panel.parsed) {
443				LOG("couldn't allocate\n");
444				free(new);
445				if (m.name) free(m.name);
446				if (m.command) free(m.command);
447				if (m.style.font) free(m.style.font);
448				scfg_block_finish(&block);
449				return false;
450			}
451			app->panel.parsed[app->panel.plen++] = new;
452		}
453
454		else if (strcmp(d.name, "modules-left") == 0 || strcmp(d.name, "modules-middle") == 0 || strcmp(d.name, "modules-right") == 0) {
455			if (d.params_len < 0) {
456				ERRNUM(d);
457			}
458			enum side side = (d.name[8] == 'l') ? SIDE_LEFT : (d.name[8] == 'm') ? SIDE_MIDDLE : SIDE_RIGHT;
459			for (size_t p = 0; p < d.params_len; p++) {
460				const char *name = d.params[p];
461				if (name[0] == '\0')
462					continue;
463
464				struct module *m = NULL;
465				for (size_t i = 0; i < app->panel.plen; i++)
466					if (strcmp(app->panel.parsed[i]->name, name) == 0) {
467						m = app->panel.parsed[i];
468						break;
469					}
470
471				if (m) module_add(app, m, side);
472				else
473					LOG("module `%s` referenced but not defined\n", name);
474			}
475		}
476	}
477
478	scfg_block_finish(&block);
479	return true;
480}
481
482static void
483sig_handler(int s) {
484	running = 0;
485}
486
487static bool
488setup(struct app *app) {
489	memset(app, 0, sizeof(*app));
490
491	struct sigaction sa = {0};
492	sa.sa_handler = sig_handler;
493	sigemptyset(&sa.sa_mask);
494	sa.sa_flags = 0;
495	sigaction(SIGINT, &sa, NULL);
496	sigaction(SIGTERM, &sa, NULL);
497
498	struct sigaction sa_chld = {0};
499	sa_chld.sa_handler = SIG_IGN;
500	sa_chld.sa_flags = SA_NOCLDWAIT;
501	sigaction(SIGCHLD, &sa_chld, NULL);
502
503	app->dpy = wl_display_connect(NULL);
504	if (!app->dpy) {
505		LOG("couldn't connect to Wayland\n");
506		return false;
507	}
508
509	app->reg = wl_display_get_registry(app->dpy);
510	if (!app->reg) {
511		LOG("couldn't get registry\n");
512		return false;
513	}
514
515	wl_registry_add_listener(app->reg, &reg_listener, app);
516	wl_display_roundtrip(app->dpy);
517
518	if (!app->comp || !app->layer_shell) {
519		LOG("required globals wl_compositor or layer_shell not available\n");
520		return false;
521	}
522
523	app->surface = wl_compositor_create_surface(app->comp);
524	if (!app->surface) {
525		LOG("couldn't create surface\n");
526		return false;
527	}
528	app->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
529		app->layer_shell,
530		app->surface,
531		NULL,
532		ZWLR_LAYER_SHELL_V1_LAYER_TOP,
533		"panko"
534	);
535
536	if (!app->layer_surface) {
537		LOG("couldn't get layer surface\n");
538		return false;
539	}
540
541	zwlr_layer_surface_v1_add_listener(
542		app->layer_surface,
543		&layer_surface_listener,
544		NULL
545	);
546
547	app->config.style.foreground = 0xDDDDDD;
548	app->config.style.background = 0x080808;
549	app->config.style.font = strdup("monospace:size=12");
550
551	if (config_path[0] == '\0') {
552		char *xdg_home = getenv("XDG_CONFIG_HOME");
553		if (xdg_home != NULL) {
554			snprintf(config_path, sizeof(config_path), "%s/panko.scfg", xdg_home);
555		}
556		else {
557			char *home = getenv("HOME");
558			if (home == NULL) {
559				LOG("couldn't find config path\n");
560				return false;
561			}
562			snprintf(config_path, sizeof(config_path), "%s/.config/panko/panko.scfg", home);
563		}
564	}
565
566	if (!parse_config(config_path, app))
567		return false;
568
569	zwlr_layer_surface_v1_set_size(
570		app->layer_surface,
571		app->config.width, app->config.height
572	);
573	zwlr_layer_surface_v1_set_anchor(
574		app->layer_surface,
575		app->config.anchor
576	);
577	zwlr_layer_surface_v1_set_exclusive_zone(
578		app->layer_surface,
579		app->config.exclusive ? app->config.height : 0
580	);
581	zwlr_layer_surface_v1_set_margin(
582		app->layer_surface,
583		app->config.margins[0],
584		app->config.margins[1],
585		app->config.margins[2],
586		app->config.margins[3]
587	);
588	wl_surface_commit(app->surface);
589	wl_display_roundtrip(app->dpy);
590
591	app->wld_ctx = wld_wayland_create_context(app->dpy, WLD_ANY);
592	if (!app->wld_ctx) {
593		LOG("couldn't create WLD context\n");
594		return false;
595	}
596
597	app->wld_ren = wld_create_renderer(app->wld_ctx);
598	if (!app->wld_ren) {
599		LOG("couldn't create WLD renderer\n");
600		return false;
601	}
602
603	app->wld_fctx = wld_font_create_context();
604	if (!app->wld_fctx) {
605		LOG("couldn't create WLD font context\n");
606		return false;
607	}
608
609	app->wld_font = wld_font_open_name(app->wld_fctx, app->config.style.font);
610	if (!app->wld_font) {
611		LOG("couldn't open font `%s`\n", app->config.style.font);
612		return false;
613	}
614
615	app->wld_surface =
616		wld_wayland_create_surface(app->wld_ctx, app->config.width,
617		app->config.height, WLD_FORMAT_XRGB8888, 0, app->surface);
618	if (!app->wld_surface) {
619		LOG("couldn't create WLD surface\n");
620		return false;
621	}
622
623	for (size_t i = 0; i < app->panel.plen; i++) {
624		struct module *m = app->panel.parsed[i];
625		m->fd = -1;
626		m->pid = 0;
627		m->buf = strdup("");
628		m->buflen = 0;
629		m->next = 0;
630
631		if (!module_run(m))
632			LOG("failed to start module `%s`\n", m->name);
633	}
634
635	for (int s = 0; s < 3; s++) {
636		for (size_t i = 0; i < MAXMOD; i++) {
637			struct module *m = app->panel.modules[s][i];
638			if (!m || !m->style.font || m->font)
639				continue;
640
641			m->font = wld_font_open_name(app->wld_fctx, m->style.font);
642			if (!m->font) {
643				LOG("couldn't open font `%s`\n", app->config.style.font);
644				return false;
645			}
646		}
647	}
648
649	return true;
650}
651
652static void
653module_draw(struct app *app, struct module *m, struct wld_font *f, uint32_t x) {
654	struct wld_extents ext;
655	wld_font_text_extents(f, m->buf, &ext);
656
657	uint32_t tw = (uint32_t)ext.advance;
658	uint32_t th = f->height;
659
660	uint32_t mw = m->width ? m->width : tw + m->padding[1] + m->padding[3];
661	uint32_t mh = m->height ? m->height : th + m->padding[0] + m->padding[2];
662	int32_t mx = x + m->margins[3];
663	int32_t my = app->config.height / 2 + m->margins[0] - (int32_t)mh / 2;
664
665	int32_t cw = mw - m->padding[1] - m->padding[3];
666	int32_t ch = mh - m->padding[0] - m->padding[2];
667	int32_t cx = mx + m->padding[3];
668	int32_t cy = my + m->padding[0];
669
670	uint32_t tx = cx + (cw - tw) / 2;
671	int32_t ty = cy + ch / 2 + th / 2 - f->descent;
672
673	wld_fill_rectangle(app->wld_ren, m->style.border_color,
674		mx - m->style.border_width, my - m->style.border_width,
675		mw + m->style.border_width * 2, mh + m->style.border_width * 2);
676	wld_fill_rectangle(app->wld_ren, m->style.background,
677		mx, my, mw, mh);
678	wld_draw_text(app->wld_ren, f, m->style.foreground,
679		tx, ty, m->buf, m->buflen, NULL);
680}
681
682static void
683draw(struct app *app) {
684	wld_set_target_surface(app->wld_ren, app->wld_surface);
685	wld_fill_rectangle(app->wld_ren, app->config.style.background,
686		0, 0, app->config.width, app->config.height);
687
688	/* left */
689	{
690		int32_t x = 0;
691
692		for (size_t i = 0; i < MAXMOD; i++) {
693			struct module *m = app->panel.modules[SIDE_LEFT][i];
694			if (!m || m->buflen == 0)
695				continue;
696
697			struct wld_font *font = m->font ? m->font : app->wld_font;
698			module_draw(app, m, font, x);
699
700			struct wld_extents ext;
701			wld_font_text_extents(font, m->buf, &ext);
702			uint32_t w = m->width ? m->width : ext.advance + m->padding[1] + m->padding[3];
703
704			x += w;
705		}
706	}
707
708	/* middle
709	 * I would love to have a better way to do this, but I don't know how */
710	{
711		uint32_t tw = 0;
712
713		for (size_t i = 0; i < MAXMOD; i++) {
714			struct module *m = app->panel.modules[SIDE_MIDDLE][i];
715			if (!m || m->buflen == 0)
716				continue;
717
718			struct wld_font *font = m->font ? m->font : app->wld_font;
719
720			struct wld_extents ext;
721			wld_font_text_extents(font, m->buf, &ext);
722			uint32_t w = m->width ? m->width : ext.advance + m->padding[1] + m->padding[3];
723
724			tw += w;
725		}
726
727		int32_t x = app->config.width / 2 - tw / 2;
728
729		for (size_t i = 0; i < MAXMOD; i++) {
730			struct module *m = app->panel.modules[SIDE_MIDDLE][i];
731			if (!m || m->buflen == 0)
732				continue;
733
734			struct wld_font *font = m->font ? m->font : app->wld_font;
735			module_draw(app, m, font, x);
736
737			struct wld_extents ext;
738			wld_font_text_extents(font, m->buf, &ext);
739			uint32_t w = m->width ? m->width : ext.advance + m->padding[1] + m->padding[3];
740
741			x += w;
742		}
743	}
744
745	/* right */
746	{
747		int32_t x = app->config.width;
748
749		for (size_t i = MAXMOD; i > 0; --i) {
750			struct module *m = app->panel.modules[SIDE_RIGHT][i - 1];
751			if (!m || m->buflen == 0)
752				continue;
753
754			struct wld_font *font = m->font ? m->font : app->wld_font;
755
756			struct wld_extents ext;
757			wld_font_text_extents(font, m->buf, &ext);
758			uint32_t w = m->width ? m->width : ext.advance + m->padding[1] + m->padding[3];
759
760			x -= w + m->margins[1];
761			module_draw(app, m, font, x);
762		}
763	}
764
765	wl_surface_damage(app->surface, 0, 0, app->config.width, app->config.height);
766	wld_flush(app->wld_ren);
767	wld_swap(app->wld_surface);
768	wl_display_roundtrip(app->dpy);
769}
770
771static void
772run(struct app *app) {
773	while (running) {
774		uint64_t now = now_ms();
775		int timeout = 50, count = 0;
776
777		for (size_t i = 0; i < app->panel.plen; i++) {
778			struct module *m = app->panel.parsed[i];
779			if (m->next != UINT64_MAX) {
780				int64_t t = (int64_t)(m->next - now);
781				if (timeout < 0 || t < timeout)
782					timeout = (int)t;
783			}
784			if (m->fd >= 0)
785				count++;
786		}
787
788		int fds = 1 + count;
789		struct pollfd *pfds = calloc(fds, sizeof(*pfds));
790		if (!pfds)
791			return;
792
793		int n = 0;
794		pfds[n].fd = wl_display_get_fd(app->dpy);
795		pfds[n].events = POLLIN;
796		n++;
797
798		for (size_t i = 0; i < app->panel.plen; i++) {
799			struct module *m = app->panel.parsed[i];
800			if (m->fd >= 0) {
801				pfds[n].fd = m->fd;
802				pfds[n].events = POLLIN;
803				n++;
804			}
805		}
806
807		wl_display_dispatch_pending(app->dpy);
808		if (wl_display_flush(app->dpy) < 0 && errno != EAGAIN) {
809			free(pfds);
810			break;
811		}
812
813		int ret = poll(pfds, n, timeout);
814		now = now_ms();
815
816		if (ret < 0) {
817			if (errno == EINTR) {
818				free(pfds);
819				continue;
820			}
821			free(pfds);
822			break;
823		}
824
825		if (ret == 0) {
826			for (size_t i = 0; i < app->panel.plen; i++) {
827				struct module *m = app->panel.parsed[i];
828				if (m->fd < 0 && m->pid == 0 && m->interval && now >= m->next)
829					module_run(m);
830			}
831			free(pfds);
832			continue;
833		}
834
835		if (pfds[0].revents & POLLIN)
836			wl_display_dispatch(app->dpy);
837
838		bool redraw = false;
839		n = 1;
840		for (int s = 0; s < 3; s++) {
841			for (size_t i = 0; i < MAXMOD; i++) {
842				struct module *m = app->panel.modules[s][i];
843				if (!m)
844					continue;
845
846				if (m->fd >= 0) {
847					if (pfds[n].revents & (POLLIN | POLLHUP | POLLRDHUP)) {
848						module_out(m);
849						redraw = true;
850					}
851					n++;
852				}
853
854				if (m->fd < 0 && m->pid == 0 && m->interval && now >= m->next)
855					module_run(m);
856			}
857		}
858
859		if (redraw)
860			draw(app);
861
862		free(pfds);
863	}
864}
865
866static void
867cleanup(struct app *app) {
868	if (app->config.style.font)
869		free(app->config.style.font);
870	if (app->panel.parsed) {
871		for (size_t i = 0; i < app->panel.plen; i++) {
872			struct module *m = app->panel.parsed[i];
873			if (!m) continue;
874			if (m->pid > 0)
875				kill(m->pid, SIGTERM);
876			if (m->fd >= 0)
877				close(m->fd);
878			if (m->buf)
879				free(m->buf);
880			if (m->name)
881				free(m->name);
882			if (m->command)
883				free(m->command);
884			if (m->font)
885				wld_font_close(m->font);
886			if (m->style.font)
887				free(m->style.font);
888			free(m);
889		}
890		free(app->panel.parsed);
891		app->panel.parsed = NULL;
892	}
893	memset(app->panel.modules, 0, sizeof(app->panel.modules));
894
895	if (app->wld_surface)
896		wld_destroy_surface(app->wld_surface);
897	if (app->wld_font)
898		wld_font_close(app->wld_font);
899	if (app->wld_fctx)
900		wld_font_destroy_context(app->wld_fctx);
901	if (app->wld_ren)
902		wld_destroy_renderer(app->wld_ren);
903	if (app->wld_ctx)
904		wld_destroy_context(app->wld_ctx);
905	if (app->layer_surface)
906		zwlr_layer_surface_v1_destroy(app->layer_surface);
907	if (app->surface)
908		wl_surface_destroy(app->surface);
909	if (app->layer_shell)
910		zwlr_layer_shell_v1_destroy(app->layer_shell);
911	if (app->comp)
912		wl_compositor_destroy(app->comp);
913	if (app->reg)
914		wl_registry_destroy(app->reg);
915	if (app->dpy)
916		wl_display_disconnect(app->dpy);
917
918	memset(app, 0, sizeof(*app));
919}
920
921int
922main(int argc, char **argv) {
923	struct app app = {0};
924	config_path[0] = '\0';
925
926	int opt;
927	while ((opt = getopt(argc, argv, ":hvc:")) != -1) {
928		switch (opt) {
929			case 'h':
930				fprintf(stderr, "Usage: %s [-hv] [-c path]\n\n"
931						"Options:\n"
932						"  -h\t\tPrint this help message\n"
933						"  -v\t\tPrint the current version\n"
934						"  -c path\tLoad the config from this path (default ~/.config/panko/panko.scfg)\n",
935						argv[0]
936				);
937				return 1;
938				break;
939			case 'v':
940				fprintf(stderr, "panko v" VERSION "\n");
941				return 0;
942				break;
943			case 'c':
944				snprintf(config_path, sizeof(config_path), "%s", optarg);
945				have_config = true;
946				break;
947		}
948	}
949
950	if (!setup(&app))
951		goto error;
952
953	run(&app);
954
955	cleanup(&app);
956	return 0;
957
958error:
959	cleanup(&app);
960	return 1;
961}