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