1 /* Skippy - Seduces Kids Into Perversion
2  *
3  * Copyright (C) 2004 Hyriand <hyriand@thegraveyard.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19 
20 #include "skippy.h"
21 #include <errno.h>
22 #include <locale.h>
23 #include <getopt.h>
24 #include <strings.h>
25 #include <limits.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 
29 enum pipe_cmd_t {
30 	// Not ordered properly for backward compatibility
31 	PIPECMD_ACTIVATE_WINDOW_PICKER = 1,
32 	PIPECMD_EXIT_RUNNING_DAEMON,
33 	PIPECMD_DEACTIVATE_WINDOW_PICKER,
34 	PIPECMD_TOGGLE_WINDOW_PICKER,
35 };
36 
37 session_t *ps_g = NULL;
38 
39 /**
40  * @brief Parse a string representation of enum cliop.
41  */
42 static bool
parse_cliop(session_t * ps,const char * str,enum cliop * dest)43 parse_cliop(session_t *ps, const char *str, enum cliop *dest) {
44 	static const char * const STRS_CLIENTOP[] = {
45 		[	CLIENTOP_NO						] = "no",
46 		[	CLIENTOP_FOCUS				] = "focus",
47 		[	CLIENTOP_ICONIFY			] = "iconify",
48 		[	CLIENTOP_SHADE_EWMH		] = "shade-ewmh",
49 		[	CLIENTOP_CLOSE_ICCCM	] = "close-icccm",
50 		[	CLIENTOP_CLOSE_EWMH		] = "close-ewmh",
51 		[	CLIENTOP_DESTROY			] = "destroy",
52 	};
53 	for (int i = 0; i < sizeof(STRS_CLIENTOP) / sizeof(STRS_CLIENTOP[0]); ++i)
54 		if (!strcmp(STRS_CLIENTOP[i], str)) {
55 			*dest = i;
56 			return true;
57 		}
58 
59 	printfef("(\"%s\"): Unrecognized operation.", str);
60 	return false;
61 }
62 
63 /**
64  * @brief Parse a string representation of enum align.
65  */
66 static int
parse_align(session_t * ps,const char * str,enum align * dest)67 parse_align(session_t *ps, const char *str, enum align *dest) {
68 	static const char * const STRS_ALIGN[] = {
69 		[ ALIGN_LEFT  ] = "left",
70 		[ ALIGN_MID   ] = "mid",
71 		[ ALIGN_RIGHT ] = "right",
72 	};
73 	for (int i = 0; i < CARR_LEN(STRS_ALIGN); ++i)
74 		if (str_startswithword(str, STRS_ALIGN[i])) {
75 			*dest = i;
76 			return strlen(STRS_ALIGN[i]);
77 		}
78 
79 	printfef("(\"%s\"): Unrecognized operation.", str);
80 	return 0;
81 }
82 
83 static inline bool
parse_align_full(session_t * ps,const char * str,enum align * dest)84 parse_align_full(session_t *ps, const char *str, enum align *dest) {
85 	int r = parse_align(ps, str, dest);
86 	if (r && str[r]) r = 0;
87 	return r;
88 }
89 
90 /**
91  * @brief Parse a string representation of picture positioning mode.
92  */
93 static int
parse_pict_posp_mode(session_t * ps,const char * str,enum pict_posp_mode * dest)94 parse_pict_posp_mode(session_t *ps, const char *str, enum pict_posp_mode *dest) {
95 	static const char * const STRS_PICTPOSP[] = {
96 		[ PICTPOSP_ORIG			] = "orig",
97 		[ PICTPOSP_SCALE		] = "scale",
98 		[ PICTPOSP_SCALEK		] = "scalek",
99 		[ PICTPOSP_SCALEE		] = "scalee",
100 		[ PICTPOSP_SCALEEK	] = "scaleek",
101 		[ PICTPOSP_TILE	 		] = "tile",
102 	};
103 	for (int i = 0; i < CARR_LEN(STRS_PICTPOSP); ++i)
104 		if (str_startswithword(str, STRS_PICTPOSP[i])) {
105 			*dest = i;
106 			return strlen(STRS_PICTPOSP[i]);
107 		}
108 
109 	printfef("(\"%s\"): Unrecognized operation.", str);
110 	return 0;
111 }
112 static inline int
parse_color_sub(const char * s,unsigned short * dest)113 parse_color_sub(const char *s, unsigned short *dest) {
114 	static const int SEG = 2;
115 
116 	char *endptr = NULL;
117 	long v = 0L;
118 	char *s2 = mstrncpy(s, SEG);
119 	v = strtol(s2, &endptr, 16);
120 	int ret = 0;
121 	if (endptr && s2 + strlen(s2) == endptr)
122 		ret = endptr - s2;
123 	free(s2);
124 	if (!ret) return ret;
125 	*dest = (double) v / 0xff * 0xffff;
126 	return ret;
127 }
128 
129 /**
130  * @brief Parse an option string into XRenderColor.
131  */
132 static int
parse_color(session_t * ps,const char * s,XRenderColor * pc)133 parse_color(session_t *ps, const char *s, XRenderColor *pc) {
134 	const char * const sorig = s;
135 	static const struct {
136 		const char *name;
137 		XRenderColor c;
138 	} PREDEF_COLORS[] = {
139 		{ "black", { 0x0000, 0x0000, 0x0000, 0xFFFF } },
140 		{ "red", { 0xffff, 0x0000, 0x0000, 0xFFFF } },
141 	};
142 
143 	// Predefined color names
144 	for (int i = 0; i < CARR_LEN(PREDEF_COLORS); ++i)
145 		if (str_startswithwordi(s, PREDEF_COLORS[i].name)) {
146 			*pc = PREDEF_COLORS[i].c;
147 			return strlen(PREDEF_COLORS[i].name);
148 		}
149 
150 	// RRGGBBAA color
151 	if ('#' == s[0]) {
152 		++s;
153 		int next = 0;
154 		if (!((next = parse_color_sub(s, &pc->red))
155 					&& (next = parse_color_sub((s += next), &pc->green))
156 					&& (next = parse_color_sub((s += next), &pc->blue)))) {
157 			printfef("(\"%s\"): Failed to read color segment.", s);
158 			return 0;
159 		}
160 		if (!(next = parse_color_sub((s += next), &pc->alpha)))
161 			pc->alpha = 0xffff;
162 		s += next;
163 		return s - sorig;
164 	}
165 
166 	printfef("(\"%s\"): Unrecognized color format.", s);
167 	return 0;
168 }
169 
170 /**
171  * @brief Parse a size string.
172  */
173 static int
parse_size(const char * s,int * px,int * py)174 parse_size(const char *s, int *px, int *py) {
175 	const char * const sorig = s;
176 	long val = 0L;
177 	char *endptr = NULL;
178 	bool hasdata = false;
179 
180 #define T_NEXTFIELD() do { \
181 	hasdata = true; \
182 	if (isspace0(*s)) goto parse_size_end; \
183 } while(0)
184 
185 	// Parse width
186 	// Must be base 10, because "0x0..." may appear
187 	val = strtol(s, &endptr, 10);
188 	if (endptr && s != endptr) {
189 		*px = val;
190 		assert(*px >= 0);
191 		s = endptr;
192 		T_NEXTFIELD();
193 	}
194 
195 	// Parse height
196 	if ('x' == *s) {
197 		++s;
198 		val = strtol(s, &endptr, 10);
199 		if (endptr && s != endptr) {
200 			*py = val;
201 			if (*py < 0) {
202 				printfef("(\"%s\"): Invalid height.", s);
203 				return 0;
204 			}
205 			s = endptr;
206 		}
207 		T_NEXTFIELD();
208 	}
209 
210 #undef T_NEXTFIELD
211 
212 	if (!hasdata)
213 		return 0;
214 
215 	if (!isspace0(*s)) {
216 		printfef("(\"%s\"): Trailing characters.", s);
217 		return 0;
218 	}
219 
220 parse_size_end:
221 	return s - sorig;
222 }
223 
224 /**
225  * @brief Parse an image specification.
226  */
227 static bool
parse_pictspec(session_t * ps,const char * s,pictspec_t * dest)228 parse_pictspec(session_t *ps, const char *s, pictspec_t *dest) {
229 #define T_NEXTFIELD() do { \
230 	s += next; \
231 	while (isspace(*s)) ++s; \
232 	if (!*s) goto parse_pictspec_end; \
233 } while (0)
234 
235 	int next = 0;
236 	T_NEXTFIELD();
237 	if (!(next = parse_size(s, &dest->twidth, &dest->theight)))
238 		dest->twidth = dest->theight = 0;
239 	T_NEXTFIELD();
240 	if (!(next = parse_pict_posp_mode(ps, s, &dest->mode)))
241 		dest->mode = PICTPOSP_ORIG;
242 	T_NEXTFIELD();
243 	if (!(next = parse_align(ps, s, &dest->alg)))
244 		dest->alg = ALIGN_MID;
245 	T_NEXTFIELD();
246 	if (!(next && (next = parse_align(ps, s, &dest->valg))))
247 		dest->valg = ALIGN_MID;
248 	T_NEXTFIELD();
249 	next = parse_color(ps, s, &dest->c);
250 	T_NEXTFIELD();
251 	if (*s)
252 		dest->path = mstrdup(s);
253 #undef T_NEXTFIELD
254 
255 parse_pictspec_end:
256 	return true;
257 }
258 
259 static client_disp_mode_t *
parse_client_disp_mode(session_t * ps,const char * s)260 parse_client_disp_mode(session_t *ps, const char *s) {
261 	static const struct {
262 		client_disp_mode_t mode;
263 		const char *name;
264 	} ENTRIES[] = {
265 		{ CLIDISP_NONE, "none" },
266 		{ CLIDISP_FILLED, "filled" },
267 		{ CLIDISP_ICON, "icon" },
268 		{ CLIDISP_THUMBNAIL, "thumbnail" },
269 		{ CLIDISP_THUMBNAIL_ICON, "thumbnail-icon" },
270 	};
271 	static const int ALLOC_STEP = 3;
272 	int capacity = 0;
273 	client_disp_mode_t *ret = NULL;
274 
275 	int i = 0;
276 	for (; s; ++i) {
277 		char *word = NULL;
278 		s = str_get_word(s, &word);
279 		if (!word)
280 			break;
281 		if (capacity <= i + 1) {
282 			capacity += ALLOC_STEP;
283 			ret = srealloc(ret, capacity, client_disp_mode_t);
284 		}
285 		{
286 			bool found = false;
287 			for (int j = 0; j < CARR_LEN(ENTRIES); ++j)
288 				if (!strcmp(word, ENTRIES[j].name)) {
289 					found = true;
290 					ret[i] = ENTRIES[j].mode;
291 				}
292 			if (!found) {
293 				printfef("(\"%s\"): Invalid mode \"%s\" ignored.", s, word);
294 				--i;
295 			}
296 		}
297 		free(word);
298 	}
299 
300 	if (!i) {
301 		free(ret);
302 	}
303 	else {
304 		ret[i] = CLIDISP_NONE;
305 	}
306 
307 	return ret;
308 }
309 
310 static dlist *
update_clients(MainWin * mw,dlist * clients,Bool * touched)311 update_clients(MainWin *mw, dlist *clients, Bool *touched) {
312 	dlist *stack = dlist_first(wm_get_stack(mw->ps));
313 	clients = dlist_first(clients);
314 
315 	if (touched)
316 		*touched = False;
317 
318 	// Terminate clients that are no longer managed
319 	for (dlist *iter = clients; iter; ) {
320 		ClientWin *cw = (ClientWin *) iter->data;
321 		if (dlist_find_data(stack, (void *) cw->src.window)
322 				&& clientwin_update(cw)) {
323 			iter = iter->next;
324 		}
325 		else {
326 			dlist *tmp = iter->next;
327 			clientwin_destroy((ClientWin *) iter->data, True);
328 			clients = dlist_remove(iter);
329 			iter = tmp;
330 			if (touched)
331 				*touched = True;
332 		}
333 	}
334 	XFlush(mw->ps->dpy);
335 
336 	// Add new clients
337 	foreach_dlist (stack) {
338 		ClientWin *cw = (ClientWin *)
339 			dlist_find(clients, clientwin_cmp_func, iter->data);
340 		if (!cw && ((Window) iter->data) != mw->window) {
341 			cw = clientwin_create(mw, (Window)iter->data);
342 			if (!cw) continue;
343 			clients = dlist_add(clients, cw);
344 			clientwin_update(cw);
345 			if (touched)
346 				*touched = True;
347 		}
348 	}
349 
350 	dlist_free(stack);
351 
352 	return clients;
353 }
354 
355 static dlist *
do_layout(MainWin * mw,dlist * clients,Window focus,Window leader)356 do_layout(MainWin *mw, dlist *clients, Window focus, Window leader) {
357 	session_t * const ps = mw->ps;
358 
359 	long desktop = wm_get_current_desktop(ps);
360 	float factor;
361 
362 	/* Update the client table, pick the ones we want and sort them */
363 	clients = update_clients(mw, clients, 0);
364 	if (!clients) {
365 		printfef("(): No client windows found.");
366 		return clients;
367 	}
368 
369 	dlist_free(mw->cod);
370 	mw->cod = NULL;
371 
372 	{
373 		dlist *tmp = dlist_first(dlist_find_all(clients,
374 					(dlist_match_func) clientwin_validate_func, &desktop));
375 		if (!tmp) {
376 			printfef("(): No client window on current desktop found.");
377 			return clients;
378 		}
379 
380 		if (leader) {
381 			mw->cod = dlist_first(dlist_find_all(tmp, clientwin_check_group_leader_func, (void*)&leader));
382 			dlist_free(tmp);
383 		}
384 		else {
385 			mw->cod = tmp;
386 		}
387 	}
388 
389 	if (!mw->cod)
390 		return clients;
391 
392 	dlist_sort(mw->cod, clientwin_sort_func, 0);
393 
394 	/* Move the mini windows around */
395 	{
396 		unsigned int width = 0, height = 0;
397 		layout_run(mw, mw->cod, &width, &height);
398 		factor = (float) (mw->width - 100) / width;
399 		if (factor * height > mw->height - 100)
400 			factor = (float) (mw->height - 100) / height;
401 		if (!ps->o.allowUpscale)
402 			factor = MIN(factor, 1.0f);
403 
404 		int xoff = (mw->width - (float) width * factor) / 2;
405 		int yoff = (mw->height - (float) height * factor) / 2;
406 		mainwin_transform(mw, factor);
407 		foreach_dlist (mw->cod) {
408 			clientwin_move((ClientWin *) iter->data, factor, xoff, yoff);
409 		}
410 	}
411 
412 	foreach_dlist(mw->cod) {
413 		clientwin_update2((ClientWin *) iter->data);
414 	}
415 
416 	// Get the currently focused window and select which mini-window to focus
417 	{
418 		dlist *iter = dlist_find(mw->cod, clientwin_cmp_func, (void *) focus);
419 		if (!iter)
420 			iter = mw->cod;
421 		mw->focus = (ClientWin *) iter->data;
422 		mw->focus->focused = 1;
423 	}
424 
425 	// Map the client windows
426 	foreach_dlist (mw->cod) {
427 		clientwin_map((ClientWin*)iter->data);
428 	}
429 
430 	// Unfortunately it does not work...
431 	focus_miniw_adv(ps, mw->focus, ps->o.movePointerOnStart);
432 
433 	return clients;
434 }
435 
436 static inline const char *
ev_dumpstr_type(const XEvent * ev)437 ev_dumpstr_type(const XEvent *ev) {
438 	switch (ev->type) {
439 		CASESTRRET(KeyPress);
440 		CASESTRRET(KeyRelease);
441 		CASESTRRET(ButtonPress);
442 		CASESTRRET(ButtonRelease);
443 		CASESTRRET(MotionNotify);
444 		CASESTRRET(EnterNotify);
445 		CASESTRRET(LeaveNotify);
446 		CASESTRRET(FocusIn);
447 		CASESTRRET(FocusOut);
448 		CASESTRRET(KeymapNotify);
449 		CASESTRRET(Expose);
450 		CASESTRRET(GraphicsExpose);
451 		CASESTRRET(NoExpose);
452 		CASESTRRET(CirculateRequest);
453 		CASESTRRET(ConfigureRequest);
454 		CASESTRRET(MapRequest);
455 		CASESTRRET(ResizeRequest);
456 		CASESTRRET(CirculateNotify);
457 		CASESTRRET(ConfigureNotify);
458 		CASESTRRET(CreateNotify);
459 		CASESTRRET(DestroyNotify);
460 		CASESTRRET(GravityNotify);
461 		CASESTRRET(MapNotify);
462 		CASESTRRET(MappingNotify);
463 		CASESTRRET(ReparentNotify);
464 		CASESTRRET(UnmapNotify);
465 		CASESTRRET(VisibilityNotify);
466 		CASESTRRET(ColormapNotify);
467 		CASESTRRET(ClientMessage);
468 		CASESTRRET(PropertyNotify);
469 		CASESTRRET(SelectionClear);
470 		CASESTRRET(SelectionNotify);
471 		CASESTRRET(SelectionRequest);
472 	}
473 
474 	return "Unknown";
475 }
476 
477 static inline Window
ev_window(session_t * ps,const XEvent * ev)478 ev_window(session_t *ps, const XEvent *ev) {
479 #define T_SETWID(type, ele) case type: return ev->ele.window
480 	switch (ev->type) {
481 		case KeyPress:
482 		T_SETWID(KeyRelease, xkey);
483 		case ButtonPress:
484 		T_SETWID(ButtonRelease, xbutton);
485 		T_SETWID(MotionNotify, xmotion);
486 		case EnterNotify:
487 		T_SETWID(LeaveNotify, xcrossing);
488 		case FocusIn:
489 		T_SETWID(FocusOut, xfocus);
490 		T_SETWID(KeymapNotify, xkeymap);
491 		T_SETWID(Expose, xexpose);
492 		case GraphicsExpose: return ev->xgraphicsexpose.drawable;
493 		case NoExpose: return ev->xnoexpose.drawable;
494 		T_SETWID(CirculateNotify, xcirculate);
495 		T_SETWID(ConfigureNotify, xconfigure);
496 		T_SETWID(CreateNotify, xcreatewindow);
497 		T_SETWID(DestroyNotify, xdestroywindow);
498 		T_SETWID(GravityNotify, xgravity);
499 		T_SETWID(MapNotify, xmap);
500 		T_SETWID(MappingNotify, xmapping);
501 		T_SETWID(ReparentNotify, xreparent);
502 		T_SETWID(UnmapNotify, xunmap);
503 		T_SETWID(VisibilityNotify, xvisibility);
504 		T_SETWID(ColormapNotify, xcolormap);
505 		T_SETWID(ClientMessage, xclient);
506 		T_SETWID(PropertyNotify, xproperty);
507 		T_SETWID(SelectionClear, xselectionclear);
508 		case SelectionNotify: return ev->xselection.requestor;
509 	}
510 #undef T_SETWID
511     if (ps->xinfo.damage_ev_base + XDamageNotify == ev->type)
512       return ((XDamageNotifyEvent *) ev)->drawable;
513 
514 	printf("(): Failed to find window for event type %d. Troubles ahead.",
515 			ev->type);
516 
517 	return ev->xany.window;
518 }
519 
520 static inline void
ev_dump(session_t * ps,const MainWin * mw,const XEvent * ev)521 ev_dump(session_t *ps, const MainWin *mw, const XEvent *ev) {
522 	if (!ev || (ps->xinfo.damage_ev_base + XDamageNotify) == ev->type) return;
523 	// if (MotionNotify == ev->type) return;
524 
525 	const char *name = ev_dumpstr_type(ev);
526 
527 	Window wid = ev_window(ps, ev);
528 	const char *wextra = "";
529 	if (ps->root == wid) wextra = "(Root)";
530 	if (mw && mw->window == wid) wextra = "(Main)";
531 
532 	print_timestamp(ps);
533 	printfd("Event %-13.13s wid %#010lx %s", name, wid, wextra);
534 }
535 
536 static bool
skippy_run_init(MainWin * mw,Window leader)537 skippy_run_init(MainWin *mw, Window leader) {
538 	session_t *ps = mw->ps;
539 
540 	// Do this window before main window gets mapped
541 	mw->revert_focus_win = wm_get_focused(ps);
542 
543 	// Update the main window's geometry (and Xinerama info if applicable)
544 	mainwin_update(mw);
545 #ifdef CFG_XINERAMA
546 	if (ps->o.xinerama_showAll)
547 		mw->xin_active = 0;
548 #endif /* CFG_XINERAMA */
549 
550 	// Map the main window and run our event loop
551 	if (ps->o.lazyTrans) {
552 		mainwin_map(mw);
553 		XFlush(ps->dpy);
554 	}
555 
556 	mw->client_to_focus = NULL;
557 
558 	mw->clients = do_layout(mw, mw->clients, mw->revert_focus_win, leader);
559 	if (!mw->cod) {
560 		printfef("(): Failed to build layout.");
561 		return false;
562 	}
563 
564 	/* Map the main window and run our event loop */
565 	if (!ps->o.lazyTrans)
566 		mainwin_map(mw);
567 	XFlush(ps->dpy);
568 
569 	return true;
570 }
571 
572 static inline bool
open_pipe(session_t * ps,struct pollfd * r_fd)573 open_pipe(session_t *ps, struct pollfd *r_fd) {
574 	if (ps->fd_pipe >= 0) {
575 		close(ps->fd_pipe);
576 		ps->fd_pipe = -1;
577 		if (r_fd)
578 			r_fd[1].fd = ps->fd_pipe;
579 	}
580 	ps->fd_pipe = open(ps->o.pipePath, O_RDONLY | O_NONBLOCK);
581 	if (ps->fd_pipe >= 0) {
582 		if (r_fd)
583 			r_fd[1].fd = ps->fd_pipe;
584 		return true;
585 	}
586 	else {
587 		printfef("(): Failed to open pipe \"%s\": %d", ps->o.pipePath, errno);
588 		perror("open");
589 	}
590 
591 	return false;
592 }
593 
594 static void
mainloop(session_t * ps,bool activate_on_start)595 mainloop(session_t *ps, bool activate_on_start) {
596 	MainWin *mw = NULL;
597 	bool die = false;
598 	bool activate = activate_on_start;
599 	bool refocus = false;
600 	bool pending_damage = false;
601 	long last_rendered = 0L;
602 
603 	struct pollfd r_fd[2] = {
604 		{
605 			.fd = ConnectionNumber(ps->dpy),
606 			.events = POLLIN,
607 		},
608 		{
609 			.fd = ps->fd_pipe,
610 			.events = POLLIN,
611 		},
612 	};
613 
614 	while (true) {
615 		// Clear revents in pollfd
616 		for (int i = 0; i < CARR_LEN(r_fd); ++i)
617 			r_fd[i].revents = 0;
618 
619 		// Activation goes first, so that it won't be delayed by poll()
620 		if (!mw && activate) {
621 			assert(ps->mainwin);
622 			activate = false;
623 			if (skippy_run_init(ps->mainwin, None)) {
624 				last_rendered = time_in_millis();
625 				mw = ps->mainwin;
626 				refocus = false;
627 				pending_damage = false;
628 			}
629 		}
630 		if (mw)
631 			activate = false;
632 
633 		// Main window destruction, before poll()
634 		if (mw && die) {
635 			// Unmap the main window and all clients, to make sure focus doesn't fall out
636 			// when we start setting focus on client window
637 			mainwin_unmap(mw);
638 			foreach_dlist(mw->cod) { clientwin_unmap((ClientWin *) iter->data); }
639 			XSync(ps->dpy, False);
640 
641 			// Focus the client window only after the main window get unmapped and
642 			// keyboard gets ungrabbed.
643 			if (mw->client_to_focus) {
644 				childwin_focus(mw->client_to_focus);
645 				mw->client_to_focus = NULL;
646 				refocus = false;
647 				pending_damage = false;
648 			}
649 
650 			// Cleanup
651 			dlist_free(mw->cod);
652 			mw->cod = 0;
653 
654 			if (refocus && mw->revert_focus_win) {
655 				// No idea why. Plain XSetInputFocus() no longer works after ungrabbing.
656 				wm_activate_window(ps, mw->revert_focus_win);
657 				refocus = false;
658 			}
659 
660 			// Catch all errors, but remove all events
661 			XSync(ps->dpy, False);
662 			XSync(ps->dpy, True);
663 
664 			mw = NULL;
665 		}
666 		if (!mw)
667 			die = false;
668 		if (activate_on_start && !mw)
669 			return;
670 
671 		// Poll for events
672 		int timeout = (pending_damage && mw && mw->poll_time > 0 ?
673 				MAX(0, mw->poll_time + last_rendered - time_in_millis()): -1);
674 		poll(r_fd, (r_fd[1].fd >= 0 ? 2: 1), timeout);
675 
676 		if (mw) {
677 			// Process X events
678 			XEvent ev = { };
679 			while (XEventsQueued(ps->dpy, QueuedAfterReading)) {
680 				XNextEvent(ps->dpy, &ev);
681 #ifdef DEBUG_EVENTS
682 				ev_dump(ps, mw, &ev);
683 #endif
684 				const Window wid = ev_window(ps, &ev);
685 
686 				if (MotionNotify == ev.type) {
687 					if (mw->tooltip && ps->o.tooltip_followsMouse)
688 						tooltip_move(mw->tooltip,
689 								ev.xmotion.x_root, ev.xmotion.y_root);
690 				}
691 				else if (ev.type == DestroyNotify || ev.type == UnmapNotify) {
692 					dlist *iter = (wid ? dlist_find(mw->clients, clientwin_cmp_func, (void *) wid): NULL);
693 					if (iter) {
694 						ClientWin *cw = (ClientWin *) iter->data;
695 						if (DestroyNotify != ev.type)
696 							cw->mode = clientwin_get_disp_mode(ps, cw);
697 						if (DestroyNotify == ev.type || !cw->mode) {
698 							mw->clients = dlist_first(dlist_remove(iter));
699 							iter = dlist_find(mw->cod, clientwin_cmp_func, (void *) wid);
700 							if (iter)
701 								mw->cod = dlist_first(dlist_remove(iter));
702 							clientwin_destroy(cw, true);
703 							if (!mw->cod) {
704 								printfef("(): Last client window destroyed/unmapped, "
705 										"exiting.");
706 								die = true;
707 							}
708 						}
709 						else {
710 							free_pixmap(ps, &cw->cpixmap);
711 							free_picture(ps, &cw->origin);
712 							free_damage(ps, &cw->damage);
713 							clientwin_update2(cw);
714 							clientwin_render(cw);
715 						}
716 					}
717 				}
718 				else if (ps->xinfo.damage_ev_base + XDamageNotify == ev.type) {
719 					// XDamageNotifyEvent *d_ev = (XDamageNotifyEvent *) &ev;
720 					dlist *iter = dlist_find(mw->cod, clientwin_cmp_func,
721 							(void *) wid);
722 					pending_damage = true;
723 					if (iter) {
724 						if (!mw->poll_time)
725 							clientwin_repair((ClientWin *)iter->data);
726 						else
727 							((ClientWin *)iter->data)->damaged = true;
728 					}
729 
730 				}
731 				else if (KeyRelease == ev.type && (mw->key_q == ev.xkey.keycode
732 							|| mw->key_escape == ev.xkey.keycode)) {
733 					if (mw->pressed_key) {
734 						die = true;
735 						if (mw->key_escape == ev.xkey.keycode)
736 							refocus = true;
737 					}
738 					else
739 						report_key_ignored(&ev);
740 				}
741 				else if (wid == mw->window)
742 					die = mainwin_handle(mw, &ev);
743 				else if (PropertyNotify == ev.type) {
744 					if (!ps->o.background &&
745 							(ESETROOT_PMAP_ID == ev.xproperty.atom
746 							 || _XROOTPMAP_ID == ev.xproperty.atom)) {
747 						mainwin_update_background(mw);
748 						REDUCE(clientwin_render((ClientWin *)iter->data), mw->cod);
749 					}
750 				}
751 				else if (mw->tooltip && wid == mw->tooltip->window)
752 					tooltip_handle(mw->tooltip, &ev);
753 				else if (wid) {
754 					for (dlist *iter = mw->cod; iter; iter = iter->next) {
755 						ClientWin *cw = (ClientWin *) iter->data;
756 						if (cw->mini.window == wid) {
757 							die = clientwin_handle(cw, &ev);
758 							break;
759 						}
760 					}
761 				}
762 			}
763 
764 			// Do delayed painting if it's active
765 			if (mw->poll_time && pending_damage && !die) {
766 				long now = time_in_millis();
767 				if (now >= last_rendered + mw->poll_time) {
768 					pending_damage = false;
769 					foreach_dlist(mw->cod) {
770 						if (((ClientWin *) iter->data)->damaged)
771 							clientwin_repair(iter->data);
772 					}
773 					last_rendered = now;
774 				}
775 			}
776 
777 			XFlush(ps->dpy);
778 		}
779 		else {
780 			// Discards all events so that poll() won't constantly hit data to read
781 			XSync(ps->dpy, True);
782 			assert(!XEventsQueued(ps->dpy, QueuedAfterReading));
783 		}
784 
785 		// Handle daemon commands
786 		if (POLLIN & r_fd[1].revents) {
787 			unsigned char piped_input = 0;
788 			int read_ret = read(ps->fd_pipe, &piped_input, 1);
789 			if (0 == read_ret) {
790 				printfdf("(): EOF reached on pipe \"%s\".", ps->o.pipePath);
791 				open_pipe(ps, r_fd);
792 			}
793 			else if (-1 == read_ret) {
794 				if (EAGAIN != errno)
795 					printfef("(): Reading pipe \"%s\" failed: %d", ps->o.pipePath, errno);
796 			}
797 			else {
798 				assert(1 == read_ret);
799 				printfdf("(): Received pipe command: %d", piped_input);
800 				switch (piped_input) {
801 					case PIPECMD_ACTIVATE_WINDOW_PICKER:
802 						activate = true;
803 						break;
804 					case PIPECMD_DEACTIVATE_WINDOW_PICKER:
805 						if (mw)
806 							die = true;
807 						break;
808 					case PIPECMD_TOGGLE_WINDOW_PICKER:
809 						if (mw)
810 							die = true;
811 						else
812 							activate = true;
813 						break;
814 					case PIPECMD_EXIT_RUNNING_DAEMON:
815 						printfdf("(): Exit command received, killing daemon...");
816 						unlink(ps->o.pipePath);
817 						return;
818 					default:
819 						printfdf("(): Unknown daemon command \"%d\" received.", piped_input);
820 						break;
821 				}
822 			}
823 		}
824 
825 		if (POLLHUP & r_fd[1].revents) {
826 			printfdf("(): PIPEHUP on pipe \"%s\".", ps->o.pipePath);
827 			open_pipe(ps, r_fd);
828 		}
829 	}
830 }
831 
832 static bool
send_command_to_daemon_via_fifo(int command,const char * pipePath)833 send_command_to_daemon_via_fifo(int command, const char *pipePath) {
834 	{
835 		int access_ret = 0;
836 		if ((access_ret = access(pipePath, W_OK))) {
837 			printfef("(): Failed to access() pipe \"%s\": %d", pipePath, access_ret);
838 			perror("access");
839 			exit(1);
840 		}
841 	}
842 
843 	FILE *fp = fopen(pipePath, "w");
844 
845 	printfdf("(): Sending command...");
846 	fputc(command, fp);
847 
848 	fclose(fp);
849 
850 	return true;
851 }
852 
853 static inline bool
activate_window_picker(const char * pipePath)854 activate_window_picker(const char *pipePath) {
855 	printfdf("(): Activating window picker...");
856 	return send_command_to_daemon_via_fifo(PIPECMD_ACTIVATE_WINDOW_PICKER, pipePath);
857 }
858 
859 static inline bool
exit_daemon(const char * pipePath)860 exit_daemon(const char *pipePath) {
861 	printfdf("(): Killing daemon...");
862 	return send_command_to_daemon_via_fifo(PIPECMD_EXIT_RUNNING_DAEMON, pipePath);
863 }
864 
865 static inline bool
deactivate_window_picker(const char * pipePath)866 deactivate_window_picker(const char *pipePath) {
867 	printfdf("(): Deactivating window picker...");
868 	return send_command_to_daemon_via_fifo(PIPECMD_DEACTIVATE_WINDOW_PICKER, pipePath);
869 }
870 
871 static inline bool
toggle_window_picker(const char * pipePath)872 toggle_window_picker(const char *pipePath) {
873 	printfdf("(): Toggling window picker...");
874 	return send_command_to_daemon_via_fifo(PIPECMD_TOGGLE_WINDOW_PICKER, pipePath);
875 }
876 
877 /**
878  * @brief Xlib error handler function.
879  */
880 static int
xerror(Display * dpy,XErrorEvent * ev)881 xerror(Display *dpy, XErrorEvent *ev) {
882 	session_t * const ps = ps_g;
883 
884 	int o;
885 	const char *name = "Unknown";
886 
887 #define CASESTRRET2(s)	 case s: name = #s; break
888 
889 	o = ev->error_code - ps->xinfo.fixes_err_base;
890 	switch (o) {
891 		CASESTRRET2(BadRegion);
892 	}
893 
894 	o = ev->error_code - ps->xinfo.damage_err_base;
895 	switch (o) {
896 		CASESTRRET2(BadDamage);
897 	}
898 
899 	o = ev->error_code - ps->xinfo.render_err_base;
900 	switch (o) {
901 		CASESTRRET2(BadPictFormat);
902 		CASESTRRET2(BadPicture);
903 		CASESTRRET2(BadPictOp);
904 		CASESTRRET2(BadGlyphSet);
905 		CASESTRRET2(BadGlyph);
906 	}
907 
908 	switch (ev->error_code) {
909 		CASESTRRET2(BadAccess);
910 		CASESTRRET2(BadAlloc);
911 		CASESTRRET2(BadAtom);
912 		CASESTRRET2(BadColor);
913 		CASESTRRET2(BadCursor);
914 		CASESTRRET2(BadDrawable);
915 		CASESTRRET2(BadFont);
916 		CASESTRRET2(BadGC);
917 		CASESTRRET2(BadIDChoice);
918 		CASESTRRET2(BadImplementation);
919 		CASESTRRET2(BadLength);
920 		CASESTRRET2(BadMatch);
921 		CASESTRRET2(BadName);
922 		CASESTRRET2(BadPixmap);
923 		CASESTRRET2(BadRequest);
924 		CASESTRRET2(BadValue);
925 		CASESTRRET2(BadWindow);
926 	}
927 
928 #undef CASESTRRET2
929 
930 	print_timestamp(ps);
931 	{
932 		char buf[BUF_LEN] = "";
933 		XGetErrorText(ps->dpy, ev->error_code, buf, BUF_LEN);
934 		printf("error %d (%s) request %d minor %d serial %lu (\"%s\")\n",
935 				ev->error_code, name, ev->request_code,
936 				ev->minor_code, ev->serial, buf);
937 	}
938 
939 	return 0;
940 }
941 
942 #ifndef SKIPPYXD_VERSION
943 #define SKIPPYXD_VERSION "unknown"
944 #endif
945 
946 static void
show_help()947 show_help() {
948 	fputs("skippy-xd (" SKIPPYXD_VERSION ")\n"
949 			"Usage: skippy-xd [command]\n\n"
950 			"The available commands are:\n"
951 			"  --config                    - Read the specified configuration file.\n"
952 			"  --start-daemon              - starts the daemon running.\n"
953 			"  --stop-daemon               - stops the daemon running.\n"
954 			"  --activate-window-picker    - tells the daemon to show the window picker.\n"
955 			"  --deactivate-window-picker  - tells the daemon to hide the window picker.\n"
956 			"  --toggle-window-picker      - tells the daemon to toggle the window picker.\n"
957 			"\n"
958 			"  --help                      - show this message.\n"
959 			"  -S                          - Synchronize X operation (debugging).\n"
960 			, stdout);
961 #ifdef CFG_LIBPNG
962 	spng_about(stdout);
963 #endif
964 }
965 
966 static inline bool
init_xexts(session_t * ps)967 init_xexts(session_t *ps) {
968 	Display * const dpy = ps->dpy;
969 #ifdef CFG_XINERAMA
970 	ps->xinfo.xinerama_exist = XineramaQueryExtension(dpy,
971 			&ps->xinfo.xinerama_ev_base, &ps->xinfo.xinerama_err_base);
972 # ifdef DEBUG_XINERAMA
973 	printfef("(): Xinerama extension: %s",
974 			(ps->xinfo.xinerama_exist ? "yes": "no"));
975 # endif /* DEBUG_XINERAMA */
976 #endif /* CFG_XINERAMA */
977 
978 	if(!XDamageQueryExtension(dpy,
979 				&ps->xinfo.damage_ev_base, &ps->xinfo.damage_err_base)) {
980 		printfef("(): FATAL: XDamage extension not found.");
981 		return false;
982 	}
983 
984 	if(!XCompositeQueryExtension(dpy, &ps->xinfo.composite_ev_base,
985 				&ps->xinfo.composite_err_base)) {
986 		printfef("(): FATAL: XComposite extension not found.");
987 		return false;
988 	}
989 
990 	if(!XRenderQueryExtension(dpy,
991 				&ps->xinfo.render_ev_base, &ps->xinfo.render_err_base)) {
992 		printfef("(): FATAL: XRender extension not found.");
993 		return false;
994 	}
995 
996 	if(!XFixesQueryExtension(dpy,
997 				&ps->xinfo.fixes_ev_base, &ps->xinfo.fixes_err_base)) {
998 		printfef("(): FATAL: XFixes extension not found.");
999 		return false;
1000 	}
1001 
1002 	return true;
1003 }
1004 
1005 /**
1006  * @brief Check if a file exists.
1007  *
1008  * access() may not actually be reliable as according to glibc manual it uses
1009  * real user ID instead of effective user ID, but stat() is just too costly
1010  * for this purpose.
1011  */
1012 static inline bool
fexists(const char * path)1013 fexists(const char *path) {
1014 	return !access(path, F_OK);
1015 }
1016 
1017 /**
1018  * @brief Find path to configuration file.
1019  */
1020 static inline char *
get_cfg_path(void)1021 get_cfg_path(void) {
1022 	static const char *PATH_CONFIG_HOME_SUFFIX = "/skippy-xd/skippy-xd.rc";
1023 	static const char *PATH_CONFIG_HOME = "/.config";
1024 	static const char *PATH_CONFIG_SYS_SUFFIX = "/skippy-xd.rc";
1025 	static const char *PATH_CONFIG_SYS = "/etc/xdg";
1026 
1027 	char *path = NULL;
1028 	const char *dir = NULL;
1029 
1030 	// Check $XDG_CONFIG_HOME
1031 	if ((dir = getenv("XDG_CONFIG_HOME")) && strlen(dir)) {
1032 		path = mstrjoin(dir, PATH_CONFIG_HOME_SUFFIX);
1033 		if (fexists(path))
1034 			goto get_cfg_path_found;
1035 		free(path);
1036 		path = NULL;
1037 	}
1038 	// Check ~/.config
1039 	if ((dir = getenv("HOME")) && strlen(dir)) {
1040 		path = mstrjoin3(dir, PATH_CONFIG_HOME, PATH_CONFIG_HOME_SUFFIX);
1041 		if (fexists(path))
1042 			goto get_cfg_path_found;
1043 		free(path);
1044 		path = NULL;
1045 	}
1046 	// Check $XDG_CONFIG_DIRS
1047 	if (!((dir = getenv("XDG_CONFIG_DIRS")) && strlen(dir)))
1048 		dir = PATH_CONFIG_SYS;
1049 	{
1050 		char *dir_free = mstrdup(dir);
1051 		char *part = strtok(dir_free, ":");
1052 		while (part) {
1053 			path = mstrjoin(part, PATH_CONFIG_SYS_SUFFIX);
1054 			if (fexists(path)) {
1055 				free(dir_free);
1056 				goto get_cfg_path_found;
1057 			}
1058 			free(path);
1059 			path = NULL;
1060 			part = strtok(NULL, ":");
1061 		}
1062 		free(dir_free);
1063 	}
1064 
1065 	return NULL;
1066 
1067 get_cfg_path_found:
1068 	return path;
1069 }
1070 
1071 static void
parse_args(session_t * ps,int argc,char ** argv,bool first_pass)1072 parse_args(session_t *ps, int argc, char **argv, bool first_pass) {
1073 	enum options {
1074 		OPT_CONFIG = 256,
1075 		OPT_ACTV_PICKER,
1076 		OPT_DEACTV_PICKER,
1077 		OPT_TOGGLE_PICKER,
1078 		OPT_DM_START,
1079 		OPT_DM_STOP,
1080 	};
1081 	static const char * opts_short = "hS";
1082 	static const struct option opts_long[] = {
1083 		{ "help",                     no_argument,       NULL, 'h' },
1084 		{ "config",                   required_argument, NULL, OPT_CONFIG },
1085 		{ "activate-window-picker",   no_argument,       NULL, OPT_ACTV_PICKER },
1086 		{ "deactivate-window-picker", no_argument,       NULL, OPT_DEACTV_PICKER },
1087 		{ "toggle-window-picker",     no_argument,       NULL, OPT_TOGGLE_PICKER },
1088 		{ "start-daemon",             no_argument,       NULL, OPT_DM_START },
1089 		{ "stop-daemon",              no_argument,       NULL, OPT_DM_STOP },
1090 		{ NULL, no_argument, NULL, 0 }
1091 	};
1092 
1093 	int o = 0;
1094 	optind = 1;
1095 
1096 	// Only parse --config in first pass
1097 	if (first_pass) {
1098 		while ((o = getopt_long(argc, argv, opts_short, opts_long, NULL)) >= 0) {
1099 			switch (o) {
1100 #define T_CASEBOOL(idx, option) case idx: ps->o.option = true; break
1101 				case OPT_CONFIG:
1102 					ps->o.config_path = mstrdup(optarg);
1103 					break;
1104 				T_CASEBOOL('S', synchronize);
1105 				case '?':
1106 				case 'h':
1107 					show_help();
1108 					// Return a non-zero value on unrecognized option
1109 					exit('h' == o ? RET_SUCCESS: RET_BADARG);
1110 				default:
1111 					break;
1112 			}
1113 		}
1114 		return;
1115 	}
1116 
1117 	while ((o = getopt_long(argc, argv, opts_short, opts_long, NULL)) >= 0) {
1118 		switch (o) {
1119 			case 'S': break;
1120 			case OPT_CONFIG: break;
1121 			case OPT_ACTV_PICKER:
1122 				ps->o.mode = PROGMODE_ACTV_PICKER;
1123 				break;
1124 			case OPT_DEACTV_PICKER:
1125 				ps->o.mode = PROGMODE_DEACTV_PICKER;
1126 				break;
1127 			case OPT_TOGGLE_PICKER:
1128 				ps->o.mode = PROGMODE_TOGGLE_PICKER;
1129 				break;
1130 			T_CASEBOOL(OPT_DM_START, runAsDaemon);
1131 			case OPT_DM_STOP:
1132 				ps->o.mode = PROGMODE_DM_STOP;
1133 				break;
1134 #undef T_CASEBOOL
1135 			default:
1136 				printfef("(0): Unimplemented option %d.", o);
1137 				exit(RET_UNKNOWN);
1138 		}
1139 	}
1140 }
1141 
main(int argc,char * argv[])1142 int main(int argc, char *argv[]) {
1143 	session_t *ps = NULL;
1144 	int ret = RET_SUCCESS;
1145 	Display *dpy = NULL;
1146 
1147 	/* Set program locale */
1148 	setlocale (LC_ALL, "");
1149 
1150 	// Initialize session structure
1151 	{
1152 		static const session_t SESSIONT_DEF = SESSIONT_INIT;
1153 		ps_g = ps = allocchk(malloc(sizeof(session_t)));
1154 		memcpy(ps, &SESSIONT_DEF, sizeof(session_t));
1155 		gettimeofday(&ps->time_start, NULL);
1156 	}
1157 
1158 	// First pass
1159 	parse_args(ps, argc, argv, true);
1160 
1161 	// Open connection to X
1162 	if (!(ps->dpy = dpy = XOpenDisplay(NULL))) {
1163 		printfef("(): FATAL: Couldn't connect to display.");
1164 		ret = RET_XFAIL;
1165 		goto main_end;
1166 	}
1167 	if (!init_xexts(ps)) {
1168 		ret = RET_XFAIL;
1169 		goto main_end;
1170 	}
1171 	if (ps->o.synchronize)
1172 		XSynchronize(ps->dpy, True);
1173 	XSetErrorHandler(xerror);
1174 
1175 	ps->screen = DefaultScreen(dpy);
1176 	ps->root = RootWindow(dpy, ps->screen);
1177 
1178 	wm_get_atoms(ps);
1179 
1180 	// Load configuration file
1181 	{
1182 		dlist *config = NULL;
1183 		{
1184 			bool user_specified_config = ps->o.config_path;
1185 			if (!ps->o.config_path)
1186 				ps->o.config_path = get_cfg_path();
1187 			if (ps->o.config_path)
1188 				config = config_load(ps->o.config_path);
1189 			else
1190 				printfef("(): WARNING: No configuration file found.");
1191 			if (!config && user_specified_config)
1192 				return 1;
1193 		}
1194 
1195 		char *lc_numeric_old = mstrdup(setlocale(LC_NUMERIC, NULL));
1196 		setlocale(LC_NUMERIC, "C");
1197 
1198 		// Read configuration into ps->o, because searching all the time is much
1199 		// less efficient, may introduce inconsistent default value, and
1200 		// occupies a lot more memory for non-string types.
1201 		ps->o.pipePath = mstrdup(config_get(config, "general", "pipePath", "/tmp/skippy-xd-fifo"));
1202 		ps->o.normal_tint = mstrdup(config_get(config, "normal", "tint", "black"));
1203 		ps->o.highlight_tint = mstrdup(config_get(config, "highlight", "tint", "#101020"));
1204 		ps->o.tooltip_border = mstrdup(config_get(config, "tooltip", "border", "#e0e0e0"));
1205 		ps->o.tooltip_background = mstrdup(config_get(config, "tooltip", "background", "#404040"));
1206 		ps->o.tooltip_text = mstrdup(config_get(config, "tooltip", "text", "#e0e0e0"));
1207 		ps->o.tooltip_textShadow = mstrdup(config_get(config, "tooltip", "textShadow", "black"));
1208 		ps->o.tooltip_font = mstrdup(config_get(config, "tooltip", "font", "fixed-11:weight=bold"));
1209 		if (!parse_cliop(ps, config_get(config, "bindings", "miwMouse1", "focus"), &ps->o.bindings_miwMouse[1])
1210 				|| !parse_cliop(ps, config_get(config, "bindings", "miwMouse2", "close-ewmh"), &ps->o.bindings_miwMouse[2])
1211 				|| !parse_cliop(ps, config_get(config, "bindings", "miwMouse3", "iconify"), &ps->o.bindings_miwMouse[3]))
1212 			return RET_BADARG;
1213 		config_get_int_wrap(config, "general", "distance", &ps->o.distance, 1, INT_MAX);
1214 		config_get_bool_wrap(config, "general", "useNetWMFullscreen", &ps->o.useNetWMFullscreen);
1215 		config_get_bool_wrap(config, "general", "ignoreSkipTaskbar", &ps->o.ignoreSkipTaskbar);
1216 		config_get_bool_wrap(config, "general", "acceptOvRedir", &ps->o.acceptOvRedir);
1217 		config_get_bool_wrap(config, "general", "acceptWMWin", &ps->o.acceptWMWin);
1218 		config_get_double_wrap(config, "general", "updateFreq", &ps->o.updateFreq, -1000.0, 1000.0);
1219 		config_get_bool_wrap(config, "general", "lazyTrans", &ps->o.lazyTrans);
1220 		config_get_bool_wrap(config, "general", "useNameWindowPixmap", &ps->o.useNameWindowPixmap);
1221 		config_get_bool_wrap(config, "general", "forceNameWindowPixmap", &ps->o.forceNameWindowPixmap);
1222 		config_get_bool_wrap(config, "general", "includeFrame", &ps->o.includeFrame);
1223 		config_get_bool_wrap(config, "general", "allowUpscale", &ps->o.allowUpscale);
1224 		config_get_int_wrap(config, "general", "preferredIconSize", &ps->o.preferredIconSize, 1, INT_MAX);
1225 		config_get_bool_wrap(config, "general", "includeAllScreens", &ps->o.includeAllScreens);
1226 		config_get_bool_wrap(config, "general", "avoidThumbnailsFromOtherScreens", &ps->o.avoidThumbnailsFromOtherScreens);
1227 		config_get_bool_wrap(config, "general", "showAllDesktops", &ps->o.showAllDesktops);
1228 		config_get_bool_wrap(config, "general", "showUnmapped", &ps->o.showUnmapped);
1229 		config_get_bool_wrap(config, "general", "movePointerOnStart", &ps->o.movePointerOnStart);
1230 		config_get_bool_wrap(config, "general", "movePointerOnSelect", &ps->o.movePointerOnSelect);
1231 		config_get_bool_wrap(config, "general", "movePointerOnRaise", &ps->o.movePointerOnRaise);
1232 		config_get_bool_wrap(config, "general", "switchDesktopOnActivate", &ps->o.switchDesktopOnActivate);
1233 		config_get_bool_wrap(config, "xinerama", "showAll", &ps->o.xinerama_showAll);
1234 		config_get_int_wrap(config, "normal", "tintOpacity", &ps->o.normal_tintOpacity, 0, 256);
1235 		config_get_int_wrap(config, "normal", "opacity", &ps->o.normal_opacity, 0, 256);
1236 		config_get_int_wrap(config, "highlight", "tintOpacity", &ps->o.highlight_tintOpacity, 0, 256);
1237 		config_get_int_wrap(config, "highlight", "opacity", &ps->o.highlight_opacity, 0, 256);
1238 		config_get_bool_wrap(config, "tooltip", "show", &ps->o.tooltip_show);
1239 		config_get_bool_wrap(config, "tooltip", "followsMouse", &ps->o.tooltip_followsMouse);
1240 		config_get_int_wrap(config, "tooltip", "offsetX", &ps->o.tooltip_offsetX, INT_MIN, INT_MAX);
1241 		config_get_int_wrap(config, "tooltip", "offsetY", &ps->o.tooltip_offsetY, INT_MIN, INT_MAX);
1242 		if (!parse_align_full(ps, config_get(config, "tooltip", "align", "left"), &ps->o.tooltip_align))
1243 			return RET_BADARG;
1244 		config_get_int_wrap(config, "tooltip", "tintOpacity", &ps->o.highlight_tintOpacity, 0, 256);
1245 		config_get_int_wrap(config, "tooltip", "opacity", &ps->o.tooltip_opacity, 0, 256);
1246 		{
1247 			const char *s = config_get(config, "general", "clientDisplayModes", NULL);
1248 			if (s && !(ps->o.clientDisplayModes = parse_client_disp_mode(ps, s)))
1249 				return RET_BADARG;
1250 			if (!ps->o.clientDisplayModes) {
1251 				static const client_disp_mode_t DEF_CLIDISPM[] = {
1252 					CLIDISP_THUMBNAIL, CLIDISP_ICON, CLIDISP_FILLED, CLIDISP_NONE
1253 				};
1254 				ps->o.clientDisplayModes = allocchk(malloc(sizeof(DEF_CLIDISPM)));
1255 				memcpy(ps->o.clientDisplayModes, &DEF_CLIDISPM, sizeof(DEF_CLIDISPM));
1256 			}
1257 		}
1258 		{
1259 			const char *sspec = config_get(config, "general", "background", NULL);
1260 			if (sspec && strlen(sspec)) {
1261 				pictspec_t spec = PICTSPECT_INIT;
1262 				if (!parse_pictspec(ps, sspec, &spec))
1263 					return RET_BADARG;
1264 				int root_width = DisplayWidth(ps->dpy, ps->screen),
1265 						root_height = DisplayHeight(ps->dpy, ps->screen);
1266 				if (!(spec.twidth || spec.theight)) {
1267 					spec.twidth = root_width;
1268 					spec.theight = root_height;
1269 				}
1270 				pictw_t *p = simg_load_s(ps, &spec);
1271 				if (!p)
1272 					exit(1);
1273 				if (p->width != root_width || p->height != root_height)
1274 					ps->o.background = simg_postprocess(ps, p, PICTPOSP_ORIG,
1275 							root_width, root_height, spec.alg, spec.valg, &spec.c);
1276 				else
1277 					ps->o.background = p;
1278 				free_pictspec(ps, &spec);
1279 			}
1280 		}
1281 		if (!parse_pictspec(ps, config_get(config, "general", "iconFillSpec", "orig mid mid #FFFFFF"), &ps->o.iconFillSpec)
1282 				|| !parse_pictspec(ps, config_get(config, "general", "fillSpec", "orig mid mid #FFFFFF"), &ps->o.fillSpec))
1283 			return RET_BADARG;
1284 		if (!simg_cachespec(ps, &ps->o.fillSpec))
1285 			return RET_BADARG;
1286 		if (ps->o.iconFillSpec.path
1287 				&& !(ps->o.iconDefault = simg_load(ps, ps->o.iconFillSpec.path,
1288 						PICTPOSP_SCALEK, ps->o.preferredIconSize, ps->o.preferredIconSize,
1289 						ALIGN_MID, ALIGN_MID, NULL)))
1290 			return RET_BADARG;
1291 
1292 		setlocale(LC_NUMERIC, lc_numeric_old);
1293 		free(lc_numeric_old);
1294 		config_free(config);
1295 	}
1296 
1297 	// Second pass
1298 	parse_args(ps, argc, argv, false);
1299 
1300 	const char* pipePath = ps->o.pipePath;
1301 
1302 	// Handle special modes
1303 	switch (ps->o.mode) {
1304 		case PROGMODE_NORMAL:
1305 			break;
1306 		case PROGMODE_ACTV_PICKER:
1307 			activate_window_picker(pipePath);
1308 			goto main_end;
1309 		case PROGMODE_DEACTV_PICKER:
1310 			deactivate_window_picker(pipePath);
1311 			goto main_end;
1312 		case PROGMODE_TOGGLE_PICKER:
1313 			toggle_window_picker(pipePath);
1314 			goto main_end;
1315 		case PROGMODE_DM_STOP:
1316 			exit_daemon(pipePath);
1317 			goto main_end;
1318 	}
1319 
1320 	if (!wm_check(ps)) {
1321 		/* ret = 1;
1322 		goto main_end; */
1323 	}
1324 
1325 	// Main branch
1326 	MainWin *mw = mainwin_create(ps);
1327 	if (!mw) {
1328 		printfef("(): FATAL: Couldn't create main window.");
1329 		ret = 1;
1330 		goto main_end;
1331 	}
1332 	ps->mainwin = mw;
1333 
1334 	XSelectInput(ps->dpy, ps->root, PropertyChangeMask);
1335 
1336 	// Daemon mode
1337 	if (ps->o.runAsDaemon) {
1338 		bool flush_file = false;
1339 
1340 		printfdf("(): Running as daemon...");
1341 
1342 		// Flush file if we could access() it (or, usually, if it exists)
1343 		if (!access(pipePath, R_OK))
1344 			flush_file = true;
1345 
1346 		{
1347 			int result = mkfifo(pipePath, S_IRUSR | S_IWUSR);
1348 			if (result < 0  && EEXIST != errno) {
1349 				printfef("(): Failed to create named pipe \"%s\": %d", pipePath, result);
1350 				perror("mkfifo");
1351 				ret = 2;
1352 				goto main_end;
1353 			}
1354 		}
1355 
1356 		// Opening pipe
1357 		if (!open_pipe(ps, NULL)) {
1358 			ret = 2;
1359 			goto main_end;
1360 		}
1361 		assert(ps->fd_pipe >= 0);
1362 		if (flush_file) {
1363 			char *buf[BUF_LEN];
1364 			while (read(ps->fd_pipe, buf, sizeof(buf)) > 0)
1365 				continue;
1366 			printfdf("(): Finished flushing pipe \"%s\".", pipePath);
1367 		}
1368 
1369 		mainloop(ps, false);
1370 	}
1371 	else {
1372 		printfdf("(): running once then quitting...");
1373 		mainloop(ps, true);
1374 	}
1375 
1376 main_end:
1377 	// Free session data
1378 	if (ps) {
1379 		// Free configuration strings
1380 		{
1381 			free(ps->o.config_path);
1382 			free(ps->o.pipePath);
1383 			free(ps->o.clientDisplayModes);
1384 			free(ps->o.normal_tint);
1385 			free(ps->o.highlight_tint);
1386 			free(ps->o.tooltip_border);
1387 			free(ps->o.tooltip_background);
1388 			free(ps->o.tooltip_text);
1389 			free(ps->o.tooltip_textShadow);
1390 			free(ps->o.tooltip_font);
1391 			free_pictw(ps, &ps->o.background);
1392 			free_pictw(ps, &ps->o.iconDefault);
1393 			free_pictspec(ps, &ps->o.iconFillSpec);
1394 			free_pictspec(ps, &ps->o.fillSpec);
1395 		}
1396 
1397 		if (ps->fd_pipe >= 0)
1398 			close(ps->fd_pipe);
1399 
1400 		if (ps->mainwin)
1401 			mainwin_destroy(ps->mainwin);
1402 
1403 		if (ps->dpy)
1404 			XCloseDisplay(dpy);
1405 
1406 		free(ps);
1407 	}
1408 
1409 	return ret;
1410 }
1411