1 /*
2  * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
3  * Copyright (C) 2004-2020 Kim Woelders
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy
6  * of this software and associated documentation files (the "Software"), to
7  * deal in the Software without restriction, including without limitation the
8  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9  * sell copies of the Software, and to permit persons to whom the Software is
10  * furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies of the Software, its documentation and marketing & publicity
14  * materials, and acknowledgment shall be given in the documentation, materials
15  * and software packages that this Software was used.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20  * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23  */
24 #include "E.h"
25 #include "borders.h"
26 #include "desktops.h"
27 #include "dialog.h"
28 #include "ewins.h"
29 #include "file.h"
30 #include "groups.h"
31 #include "ipc.h"
32 #include "list.h"
33 #include "settings.h"
34 #include "snaps.h"
35 #include "timers.h"
36 #include "xwin.h"
37 
38 struct _snapshot {
39    dlist_t             list;
40    char               *name;
41    char               *win_title;
42    char               *win_name;
43    char               *win_class;
44    char               *win_role;
45    EX_Window           win;
46    EWin               *used;
47    unsigned int        startup_id;
48    char                track_changes;
49    unsigned int        match_flags;
50    unsigned int        use_flags;
51 
52    char               *border_name;
53    int                 desktop;
54    int                 area_x, area_y;
55    int                 x, y;
56    int                 w, h;
57    int                 layer;
58    char                sticky;
59    char                shaded;
60    unsigned int        flags[2];
61    char               *cmd;
62    int                *groups;
63    int                 num_groups;
64    char                skiptask;
65    char                skipfocus;
66    char                skipwinlist;
67 #if USE_COMPOSITE
68    int                 opacity;
69    int                 focused_opacity;
70    char                shadow;
71 #endif
72 };
73 
74 static              LIST_HEAD(ss_list);
75 static Timer       *ss_timer = NULL;
76 
77 static Snapshot    *
_SnapCreate(const char * name)78 _SnapCreate(const char *name)
79 {
80    Snapshot           *sn;
81 
82    sn = ECALLOC(Snapshot, 1);
83    if (!sn)
84       return NULL;
85 
86    LIST_APPEND(Snapshot, &ss_list, sn);
87 
88    sn->name = Estrdup(name);
89 
90    return sn;
91 }
92 
93 static void
_SnapDestroy(Snapshot * sn)94 _SnapDestroy(Snapshot * sn)
95 {
96    LIST_REMOVE(Snapshot, &ss_list, sn);
97 
98    if (sn->used)
99       sn->used->snap = NULL;
100 
101    Efree(sn->name);
102    Efree(sn->win_title);
103    Efree(sn->win_name);
104    Efree(sn->win_class);
105    Efree(sn->win_role);
106    Efree(sn->border_name);
107    Efree(sn->cmd);
108    Efree(sn->groups);
109 
110    Efree(sn);
111 }
112 
113 /*
114  * Stupid hack to fix apps that set WM_WINDOW_ROLE to
115  * a <name>-<pid>-<something>-<time> like thing.
116  * Is this even ICCCM compliant?
117  */
118 static char        *
_ParseRole(const char * role,char * buf,int len)119 _ParseRole(const char *role, char *buf, int len)
120 {
121    int                 l1, l2;
122 
123    if (!role)
124       return NULL;
125 
126    l1 = strlen(role);
127    if (l1 >= len)
128       l1 = len - 1;
129 
130    for (l2 = l1; l2 > 0; l2--)
131      {
132 	if (role[l2 - 1] != '-' &&
133 	    !(role[l2 - 1] >= '0' && role[l2 - 1] <= '9'))
134 	   break;
135      }
136    if (l1 - l2 > 8)
137       l1 = l2;
138    memcpy(buf, role, l1);
139    buf[l1] = '\0';
140 
141    return buf;
142 }
143 
144 #define SEQ(s1, s2) ((s1) && (s2) && !strcmp(s1, s2))
145 
146 static int
_SnapEwinMatch(const Snapshot * sn,const EWin * ewin)147 _SnapEwinMatch(const Snapshot * sn, const EWin * ewin)
148 {
149    char                buf[256], *s;
150 
151    /* Don't allow matching anything */
152    if (!sn->match_flags)
153       return 0;
154 
155    if (ewin->state.identified)
156       return sn->win == EwinGetClientXwin(ewin);
157 
158    if (sn->startup_id && !sn->cmd)
159       return 0;
160 
161    if (sn->match_flags & SNAP_MATCH_TITLE
162        && !SEQ(sn->win_title, EwinGetIcccmName(ewin)))
163       return 0;
164 
165    if (sn->match_flags & SNAP_MATCH_NAME
166        && !SEQ(sn->win_name, EwinGetIcccmCName(ewin)))
167       return 0;
168 
169    if (sn->match_flags & SNAP_MATCH_CLASS
170        && !SEQ(sn->win_class, EwinGetIcccmClass(ewin)))
171       return 0;
172 
173    if (sn->match_flags & SNAP_MATCH_ROLE)
174      {
175 	s = _ParseRole(ewin->icccm.wm_role, buf, sizeof(buf));
176 	if (!SEQ(sn->win_role, s))
177 	   return 0;
178      }
179 
180    /* Match! */
181    return 1;
182 }
183 
184 static int
_SnapEwinFindMatchCmd(const void * data,const void * match)185 _SnapEwinFindMatchCmd(const void *data, const void *match)
186 {
187    const Snapshot     *sn = (Snapshot *) data;
188    const EWin         *ewin = (EWin *) match;
189 
190    return sn->used ||
191       !(sn->startup_id && SEQ(sn->cmd, ewin->icccm.wm_command) &&
192 	_SnapEwinMatch(sn, ewin));
193 }
194 
195 static int
_SnapEwinFindMatch(const void * data,const void * match)196 _SnapEwinFindMatch(const void *data, const void *match)
197 {
198    const Snapshot     *sn = (Snapshot *) data;
199    const EWin         *ewin = (EWin *) match;
200 
201    return sn->used || !_SnapEwinMatch(sn, ewin);
202 }
203 
204 /* find a snapshot state that applies to this ewin */
205 static Snapshot    *
_SnapEwinFind(EWin * ewin)206 _SnapEwinFind(EWin * ewin)
207 {
208    Snapshot           *sn;
209 
210    if (ewin->snap)
211       return ewin->snap;
212 
213    if (LIST_IS_EMPTY(&ss_list))
214       return NULL;
215 
216    /* If exec'ed by snap try matching command exactly */
217    sn = LIST_FIND(Snapshot, &ss_list, _SnapEwinFindMatchCmd, ewin);
218    if (sn && sn->startup_id > 0)
219      {
220 	/* Assuming we were started by snap */
221 	sn->startup_id = 0;	/* Only the first time */
222 	ewin->state.snapstarted = 1;
223      }
224 
225    if (!sn)
226       sn = LIST_FIND(Snapshot, &ss_list, _SnapEwinFindMatch, ewin);
227 
228    if (sn && !(sn->match_flags & SNAP_MATCH_MULTIPLE))
229      {
230 	sn->used = ewin;
231 	ewin->snap = sn;
232      }
233 
234    return sn;
235 }
236 
237 #define ST(s) ((s) ? (s) : "")
238 
239 /* find a snapshot state that applies to this ewin Or if that doesnt exist */
240 /* create a new one */
241 static Snapshot    *
_SnapEwinGet(EWin * ewin,unsigned int match_flags)242 _SnapEwinGet(EWin * ewin, unsigned int match_flags)
243 {
244    Snapshot           *sn;
245    char                buf[1024], *s;
246 
247    sn = _SnapEwinFind(ewin);
248    if (sn)
249       return sn;
250 
251    if ((match_flags & SNAP_MATCH_TITLE) && !EwinGetIcccmName(ewin))
252       match_flags ^= SNAP_MATCH_TITLE;
253    if ((match_flags & SNAP_MATCH_NAME) && !EwinGetIcccmCName(ewin))
254       match_flags ^= SNAP_MATCH_NAME;
255    if ((match_flags & SNAP_MATCH_CLASS) && !EwinGetIcccmClass(ewin))
256       match_flags ^= SNAP_MATCH_CLASS;
257    if ((match_flags & SNAP_MATCH_ROLE) && !ewin->icccm.wm_role)
258       match_flags ^= SNAP_MATCH_ROLE;
259    if (match_flags == 0)
260      {
261 	if (!EwinGetIcccmName(ewin))
262 	   return NULL;
263 	match_flags = SNAP_MATCH_TITLE;
264      }
265 
266    sn = _SnapCreate(NULL);
267    if (!sn)
268       return NULL;
269 
270    sn->match_flags = match_flags;
271    if (match_flags & SNAP_MATCH_TITLE)
272       sn->win_title = Estrdup(EwinGetIcccmName(ewin));
273    if (match_flags & SNAP_MATCH_NAME)
274       sn->win_name = Estrdup(EwinGetIcccmCName(ewin));
275    if (match_flags & SNAP_MATCH_CLASS)
276       sn->win_class = Estrdup(EwinGetIcccmClass(ewin));
277    if (match_flags & SNAP_MATCH_ROLE)
278      {
279 	s = _ParseRole(ewin->icccm.wm_role, buf, sizeof(buf));
280 	sn->win_role = Estrdup(s);
281      }
282 
283    /* Set the snap name. Has no particular significance. */
284    if ((sn->win_name || sn->win_class) && sn->win_role)
285       Esnprintf(buf, sizeof(buf), "%s.%s:%s", ST(sn->win_name),
286 		ST(sn->win_class), sn->win_role);
287    else if (sn->win_name || sn->win_class)
288       Esnprintf(buf, sizeof(buf), "%s.%s", ST(sn->win_name), ST(sn->win_class));
289    else if (sn->win_title)
290       Esnprintf(buf, sizeof(buf), "TITLE.%s", sn->win_title);
291    else				/* We should not go here */
292       Esnprintf(buf, sizeof(buf), "TITLE.%s", EwinGetIcccmName(ewin));
293    sn->name = Estrdup(buf);
294 
295    if (!(sn->match_flags & SNAP_MATCH_MULTIPLE))
296      {
297 	sn->used = ewin;
298 	ewin->snap = sn;
299      }
300 
301    return sn;
302 }
303 
304 /* record info about this Ewin's attributes */
305 
306 static void
_SnapUpdateEwinBorder(Snapshot * sn,const EWin * ewin)307 _SnapUpdateEwinBorder(Snapshot * sn, const EWin * ewin)
308 {
309    EFREE_DUP(sn->border_name, BorderGetName(ewin->normal_border));
310 }
311 
312 static void
_SnapUpdateEwinDesktop(Snapshot * sn,const EWin * ewin)313 _SnapUpdateEwinDesktop(Snapshot * sn, const EWin * ewin)
314 {
315    sn->desktop = EoGetDeskNum(ewin);
316 }
317 
318 static void
_SnapUpdateEwinSize(Snapshot * sn,const EWin * ewin)319 _SnapUpdateEwinSize(Snapshot * sn, const EWin * ewin)
320 {
321    sn->w = ewin->client.w;
322    sn->h = ewin->client.h;
323 }
324 
325 static void
_SnapUpdateEwinLocation(Snapshot * sn,const EWin * ewin)326 _SnapUpdateEwinLocation(Snapshot * sn, const EWin * ewin)
327 {
328    int                 ax, ay;
329 
330    sn->x = EoGetX(ewin);
331    sn->y = EoGetY(ewin);
332    sn->area_x = ewin->area_x;
333    sn->area_y = ewin->area_y;
334    if (!EoIsSticky(ewin))
335      {
336 	DeskGetArea(EoGetDesk(ewin), &ax, &ay);
337 	sn->x += ((ax - sn->area_x) * WinGetW(VROOT));
338 	sn->y += ((ay - sn->area_y) * WinGetH(VROOT));
339      }
340 }
341 
342 static void
_SnapUpdateEwinLayer(Snapshot * sn,const EWin * ewin)343 _SnapUpdateEwinLayer(Snapshot * sn, const EWin * ewin)
344 {
345    sn->layer = EoGetLayer(ewin);
346 }
347 
348 static void
_SnapUpdateEwinSticky(Snapshot * sn,const EWin * ewin)349 _SnapUpdateEwinSticky(Snapshot * sn, const EWin * ewin)
350 {
351    sn->sticky = EoIsSticky(ewin);
352 }
353 
354 static void
_SnapUpdateEwinShade(Snapshot * sn,const EWin * ewin)355 _SnapUpdateEwinShade(Snapshot * sn, const EWin * ewin)
356 {
357    sn->shaded = ewin->state.shaded;
358 }
359 
360 static void
_SnapUpdateEwinSkipLists(Snapshot * sn,const EWin * ewin)361 _SnapUpdateEwinSkipLists(Snapshot * sn, const EWin * ewin)
362 {
363    sn->skiptask = ewin->props.skip_ext_task;
364    sn->skipwinlist = ewin->props.skip_winlist;
365    sn->skipfocus = ewin->props.skip_focuslist;
366 }
367 
368 static void
_SnapUpdateEwinFlags(Snapshot * sn,const EWin * ewin)369 _SnapUpdateEwinFlags(Snapshot * sn, const EWin * ewin)
370 {
371    EwinFlagsEncode(ewin, sn->flags);
372 }
373 
374 static void
_SnapUpdateEwinCmd(Snapshot * sn,const EWin * ewin)375 _SnapUpdateEwinCmd(Snapshot * sn, const EWin * ewin)
376 {
377    if (ewin->icccm.wm_machine &&
378        strcmp(ewin->icccm.wm_machine, Mode.wm.machine_name))
379       return;
380 
381    EFREE_DUP(sn->cmd, ewin->icccm.wm_command);
382 }
383 
384 static void
_SnapUpdateEwinGroups(Snapshot * sn,const EWin * ewin,char onoff)385 _SnapUpdateEwinGroups(Snapshot * sn, const EWin * ewin, char onoff)
386 {
387    EWin              **gwins, *ewin2;
388    int                 i, j, num;
389 
390    if (!ewin)
391       return;
392 
393    if (!ewin->groups)
394      {
395 	EFREE_NULL(sn->groups);
396 	sn->num_groups = 0;
397 	return;
398      }
399 
400    gwins =
401       ListWinGroupMembersForEwin(ewin, GROUP_ACTION_ANY, Mode.nogroup, &num);
402    for (i = 0; i < num; i++)
403      {
404 	ewin2 = gwins[i];
405 
406 	if (onoff)
407 	  {
408 	     if (!ewin2->groups)
409 		continue;
410 
411 	     sn = ewin2->snap;
412 	     if (!sn)
413 		sn = _SnapEwinGet(ewin2, SNAP_MATCH_DEFAULT);
414 	     if (!sn)
415 		continue;
416 
417 	     sn->num_groups = ewin2->num_groups;
418 	     EFREE_SET(sn->groups, EMALLOC(int, ewin2->num_groups));
419 
420 	     for (j = 0; j < ewin2->num_groups; j++)
421 		sn->groups[j] = GroupGetIndex(ewin2->groups[j]);
422 	  }
423 	else
424 	  {
425 	     if (ewin->snap)
426 	       {
427 		  sn = ewin2->snap;
428 		  if (sn)
429 		    {
430 		       EFREE_NULL(sn->groups);
431 		       sn->num_groups = 0;
432 		    }
433 	       }
434 	  }
435      }
436    Efree(gwins);
437 }
438 
439 #if USE_COMPOSITE
440 
441 static void
_SnapUpdateEwinOpacity(Snapshot * sn,const EWin * ewin)442 _SnapUpdateEwinOpacity(Snapshot * sn, const EWin * ewin)
443 {
444    sn->opacity = OpacityToPercent(ewin->props.opacity);
445    sn->focused_opacity = OpacityToPercent(ewin->props.focused_opacity);
446 }
447 
448 static void
_SnapUpdateEwinShadow(Snapshot * sn,const EWin * ewin)449 _SnapUpdateEwinShadow(Snapshot * sn, const EWin * ewin)
450 {
451    sn->shadow = EoGetShadow(ewin);
452 }
453 
454 #endif
455 
456 static void
_SnapUpdateEwin(Snapshot * sn,const EWin * ewin,unsigned int flags)457 _SnapUpdateEwin(Snapshot * sn, const EWin * ewin, unsigned int flags)
458 {
459    /* FIXME - We should check if anything is actually changed */
460 
461    if (flags & SNAP_USE_BORDER)
462       _SnapUpdateEwinBorder(sn, ewin);
463    if (flags & SNAP_USE_COMMAND)
464       _SnapUpdateEwinCmd(sn, ewin);
465    if (flags & SNAP_USE_DESK)
466       _SnapUpdateEwinDesktop(sn, ewin);
467    if (flags & SNAP_USE_POS)
468       _SnapUpdateEwinLocation(sn, ewin);
469    if (flags & SNAP_USE_SIZE)
470       _SnapUpdateEwinSize(sn, ewin);
471    if (flags & SNAP_USE_LAYER)
472       _SnapUpdateEwinLayer(sn, ewin);
473    if (flags & SNAP_USE_STICKY)
474       _SnapUpdateEwinSticky(sn, ewin);
475    if (flags & SNAP_USE_SHADED)
476       _SnapUpdateEwinShade(sn, ewin);
477    if (flags & SNAP_USE_SKIP_LISTS)
478       _SnapUpdateEwinSkipLists(sn, ewin);
479    if (flags & SNAP_USE_FLAGS)
480       _SnapUpdateEwinFlags(sn, ewin);
481 #if USE_COMPOSITE
482    if (flags & SNAP_USE_OPACITY)
483       _SnapUpdateEwinOpacity(sn, ewin);
484    if (flags & SNAP_USE_SHADOW)
485       _SnapUpdateEwinShadow(sn, ewin);
486 #endif
487    if (flags & SNAP_USE_GROUPS)
488       _SnapUpdateEwinGroups(sn, ewin, ewin->num_groups);
489 
490    SnapshotsSave();
491 }
492 
493 static void
_EwinSnapSet(EWin * ewin,unsigned int match_flags,unsigned int use_flags)494 _EwinSnapSet(EWin * ewin, unsigned int match_flags, unsigned int use_flags)
495 {
496    Snapshot           *sn;
497 
498    /* Quit if nothing to be saved */
499    if (!match_flags || !(use_flags & SNAP_USE_ALL))
500       return;
501 
502    sn = _SnapEwinGet(ewin, match_flags);
503    if (!sn)
504       return;
505 
506    if (use_flags & SNAP_AUTO)
507       sn->track_changes = 1;
508 
509    sn->use_flags = use_flags & SNAP_USE_ALL;
510 
511    _SnapUpdateEwin(sn, ewin, use_flags);
512 }
513 
514 void
SnapshotEwinUpdate(const EWin * ewin,unsigned int flags)515 SnapshotEwinUpdate(const EWin * ewin, unsigned int flags)
516 {
517    Snapshot           *sn;
518 
519    sn = ewin->snap;
520    if (!sn || !sn->track_changes)
521       return;
522 
523 #if 0
524    Eprintf("%s: %s: %#x\n", __func__, EwinGetTitle(ewin), flags);
525 #endif
526 
527    if (flags & sn->use_flags)
528       _SnapUpdateEwin(sn, ewin, flags);
529 }
530 
531 /* unsnapshot any saved info about this ewin */
532 static void
_EwinSnapRemove(EWin * ewin)533 _EwinSnapRemove(EWin * ewin)
534 {
535    if (ewin->snap)
536       _SnapDestroy(ewin->snap);
537    ewin->snap = NULL;
538 }
539 
540 #if ENABLE_DIALOGS
541 /*
542  * Snapshot dialogs
543  */
544 typedef struct {
545    EX_Window           client;
546 
547    struct {
548       char                title;
549       char                name;
550       char                clss;
551       char                role;
552    } match;
553 
554    char                track_changes;
555    char                snap_border;
556    char                snap_desktop;
557    char                snap_size;
558    char                snap_location;
559    char                snap_layer;
560    char                snap_sticky;
561    char                snap_shaded;
562    char                snap_cmd;
563    char                snap_group;
564    char                snap_skiplists;
565    char                snap_flags;
566 
567 #if USE_COMPOSITE
568    char                snap_opacity;
569    char                snap_shadow;
570 #endif
571 } SnapDlgData;
572 
573 static void
_DlgApplySnap(Dialog * d,int val __UNUSED__,void * data __UNUSED__)574 _DlgApplySnap(Dialog * d, int val __UNUSED__, void *data __UNUSED__)
575 {
576    EWin               *ewin;
577    SnapDlgData        *sd = DLG_DATA_GET(d, SnapDlgData);
578    unsigned int        match_flags, use_flags;
579 
580    ewin = EwinFindByClient(sd->client);
581    if (!ewin)
582       goto done;
583 
584    _EwinSnapRemove(ewin);
585 
586    match_flags = 0;
587    if (sd->match.title)
588       match_flags |= SNAP_MATCH_TITLE;
589    if (sd->match.name)
590       match_flags |= SNAP_MATCH_NAME;
591    if (sd->match.clss)
592       match_flags |= SNAP_MATCH_CLASS;
593    if (sd->match.role)
594       match_flags |= SNAP_MATCH_ROLE;
595 
596    if (!match_flags)
597       goto done;
598 
599    use_flags = 0;
600    if (sd->track_changes)
601       use_flags |= SNAP_AUTO;
602    if (sd->snap_border)
603       use_flags |= SNAP_USE_BORDER;
604    if (sd->snap_cmd)
605       use_flags |= SNAP_USE_COMMAND;
606    if (sd->snap_desktop)
607       use_flags |= SNAP_USE_DESK;
608    if (sd->snap_location)
609       use_flags |= SNAP_USE_POS;
610    if (sd->snap_size)
611       use_flags |= SNAP_USE_SIZE;
612    if (sd->snap_layer)
613       use_flags |= SNAP_USE_LAYER;
614    if (sd->snap_sticky)
615       use_flags |= SNAP_USE_STICKY;
616    if (sd->snap_shaded)
617       use_flags |= SNAP_USE_SHADED;
618    if (sd->snap_skiplists)
619       use_flags |= SNAP_USE_SKIP_LISTS;
620    if (sd->snap_flags)
621       use_flags |= SNAP_USE_FLAGS;
622 #if USE_COMPOSITE
623    if (sd->snap_opacity)
624       use_flags |= SNAP_USE_OPACITY;
625    if (sd->snap_shadow)
626       use_flags |= SNAP_USE_SHADOW;
627 #endif
628    if (sd->snap_group)
629       use_flags |= SNAP_USE_GROUPS;
630 
631    if (!use_flags)
632       goto done;
633 
634    _EwinSnapSet(ewin, match_flags, use_flags);
635 
636  done:
637    SnapshotsSave();
638 }
639 
640 static void
_DlgFillSnap(Dialog * d,DItem * table,void * data)641 _DlgFillSnap(Dialog * d, DItem * table, void *data)
642 {
643    SnapDlgData        *sd = DLG_DATA_GET(d, SnapDlgData);
644    DItem              *di;
645    Snapshot           *sn;
646    char                s[1024];
647    const EWin         *ewin = (EWin *) data;
648 
649    sd->client = EwinGetClientXwin(ewin);
650 
651    sn = ewin->snap;
652    if (sn)
653      {
654 	sd->match.title = (sn->match_flags & SNAP_MATCH_TITLE) != 0;
655 	sd->match.name = (sn->match_flags & SNAP_MATCH_NAME) != 0;
656 	sd->match.clss = (sn->match_flags & SNAP_MATCH_CLASS) != 0;
657 	sd->match.role = (sn->match_flags & SNAP_MATCH_ROLE) != 0;
658 
659 	if (sn->track_changes)
660 	   sd->track_changes = 1;
661 	if (sn->use_flags & SNAP_USE_BORDER)
662 	   sd->snap_border = 1;
663 	if (sn->use_flags & SNAP_USE_COMMAND)
664 	   sd->snap_cmd = 1;
665 	if (sn->use_flags & SNAP_USE_DESK)
666 	   sd->snap_desktop = 1;
667 	if (sn->use_flags & SNAP_USE_POS)
668 	   sd->snap_location = 1;
669 	if (sn->use_flags & SNAP_USE_SIZE)
670 	   sd->snap_size = 1;
671 	if (sn->use_flags & SNAP_USE_LAYER)
672 	   sd->snap_layer = 1;
673 	if (sn->use_flags & SNAP_USE_STICKY)
674 	   sd->snap_sticky = 1;
675 	if (sn->use_flags & SNAP_USE_SHADED)
676 	   sd->snap_shaded = 1;
677 	if (sn->use_flags & SNAP_USE_SKIP_LISTS)
678 	   sd->snap_skiplists = 1;
679 	if (sn->use_flags & SNAP_USE_FLAGS)
680 	   sd->snap_flags = 1;
681 #if USE_COMPOSITE
682 	if (sn->use_flags & SNAP_USE_OPACITY)
683 	   sd->snap_opacity = 1;
684 	if (sn->use_flags & SNAP_USE_SHADOW)
685 	   sd->snap_shadow = 1;
686 #endif
687 	if (sn->use_flags & SNAP_USE_GROUPS)
688 	   sd->snap_group = 1;
689      }
690    else
691      {
692 	if (EwinGetIcccmCName(ewin))
693 	  {
694 	     sd->match.name = 1;
695 	     sd->match.clss = 1;
696 	     sd->match.role = !!ewin->icccm.wm_role;
697 	  }
698 	else
699 	  {
700 	     sd->match.title = !!EwinGetIcccmName(ewin);
701 	  }
702      }
703 
704    table = DialogAddItem(table, DITEM_TABLE);
705    DialogItemTableSetOptions(table, 4, 0, 0, 0);
706 
707    di = DialogAddItem(table, DITEM_CHECKBUTTON);
708    DialogItemSetAlign(di, 0, 512);
709    DialogItemSetText(di, _("Title:"));
710    DialogItemCheckButtonSetPtr(di, &sd->match.title);
711 
712    di = DialogAddItem(table, DITEM_TEXT);
713    DialogItemSetColSpan(di, 3);
714    DialogItemSetAlign(di, 1024, 512);
715    DialogItemSetText(di, EwinGetIcccmName(ewin));
716 
717    if (EwinGetIcccmCName(ewin))
718      {
719 	di = DialogAddItem(table, DITEM_CHECKBUTTON);
720 	DialogItemSetAlign(di, 0, 512);
721 	DialogItemSetText(di, _("Name:"));
722 	DialogItemCheckButtonSetPtr(di, &sd->match.name);
723 
724 	di = DialogAddItem(table, DITEM_TEXT);
725 	DialogItemSetColSpan(di, 3);
726 	DialogItemSetAlign(di, 1024, 512);
727 	DialogItemSetText(di, EwinGetIcccmCName(ewin));
728      }
729 
730    if (EwinGetIcccmClass(ewin))
731      {
732 	di = DialogAddItem(table, DITEM_CHECKBUTTON);
733 	DialogItemSetAlign(di, 0, 512);
734 	DialogItemSetText(di, _("Class:"));
735 	DialogItemCheckButtonSetPtr(di, &sd->match.clss);
736 
737 	di = DialogAddItem(table, DITEM_TEXT);
738 	DialogItemSetColSpan(di, 3);
739 	DialogItemSetAlign(di, 1024, 512);
740 	DialogItemSetText(di, EwinGetIcccmClass(ewin));
741      }
742 
743    if (ewin->icccm.wm_role)
744      {
745 	di = DialogAddItem(table, DITEM_CHECKBUTTON);
746 	DialogItemSetAlign(di, 0, 512);
747 	DialogItemSetText(di, _("Role:"));
748 	DialogItemCheckButtonSetPtr(di, &sd->match.role);
749 
750 	di = DialogAddItem(table, DITEM_TEXT);
751 	DialogItemSetColSpan(di, 3);
752 	DialogItemSetAlign(di, 1024, 512);
753 	DialogItemSetText(di, ewin->icccm.wm_role);
754      }
755 
756    if (ewin->icccm.wm_command)
757      {
758 	di = DialogAddItem(table, DITEM_TEXT);
759 	DialogItemSetAlign(di, 0, 512);
760 	DialogItemSetText(di, _("Command:"));
761 
762 	di = DialogAddItem(table, DITEM_TEXT);
763 	DialogItemSetColSpan(di, 3);
764 	DialogItemSetAlign(di, 1024, 512);
765 
766 	/* if the command is long, cut in into slices of about 80 characters */
767 	if (strlen(ewin->icccm.wm_command) > 80)
768 	  {
769 	     int                 i = 0, slice, last;
770 
771 	     s[0] = 0;
772 	     while ((i <= (int)strlen(ewin->icccm.wm_command))
773 		    && (i < (int)(sizeof(s) / 4)))
774 	       {
775 		  last = i;
776 		  i += 64;
777 		  slice = 64;
778 		  /* and make sure that we don't cut in the middle of a word. */
779 		  while ((ewin->icccm.wm_command[i++] != ' ')
780 			 && (i < (int)(sizeof(s) / 4)))
781 		     slice++;
782 		  strncat(s, ewin->icccm.wm_command + last, slice);
783 		  if (i < (int)(sizeof(s) / 4))
784 		     strcat(s, "\n");
785 		  else
786 		     strcat(s, "...\n");
787 	       }
788 	     DialogItemSetText(di, s);
789 	  }
790 	else
791 	   DialogItemSetText(di, ewin->icccm.wm_command);
792      }
793 
794    di = DialogAddItem(table, DITEM_SEPARATOR);
795    DialogItemSetColSpan(di, 4);
796 
797    di = DialogAddItem(table, DITEM_CHECKBUTTON);
798    DialogItemSetColSpan(di, 4);
799    DialogItemSetText(di, _("Track Changes"));
800    DialogItemCheckButtonSetPtr(di, &sd->track_changes);
801 
802    di = DialogAddItem(table, DITEM_CHECKBUTTON);
803    DialogItemSetColSpan(di, 2);
804    DialogItemSetText(di, _("Location"));
805    DialogItemCheckButtonSetPtr(di, &sd->snap_location);
806 
807    di = DialogAddItem(table, DITEM_CHECKBUTTON);
808    DialogItemSetColSpan(di, 2);
809    DialogItemSetText(di, _("Border style"));
810    DialogItemCheckButtonSetPtr(di, &sd->snap_border);
811 
812    di = DialogAddItem(table, DITEM_CHECKBUTTON);
813    DialogItemSetColSpan(di, 2);
814    DialogItemSetText(di, _("Size"));
815    DialogItemCheckButtonSetPtr(di, &sd->snap_size);
816 
817    di = DialogAddItem(table, DITEM_CHECKBUTTON);
818    DialogItemSetColSpan(di, 2);
819    DialogItemSetText(di, _("Desktop"));
820    DialogItemCheckButtonSetPtr(di, &sd->snap_desktop);
821 
822    di = DialogAddItem(table, DITEM_CHECKBUTTON);
823    DialogItemSetColSpan(di, 2);
824    DialogItemSetText(di, _("Shaded state"));
825    DialogItemCheckButtonSetPtr(di, &sd->snap_shaded);
826 
827    di = DialogAddItem(table, DITEM_CHECKBUTTON);
828    DialogItemSetColSpan(di, 2);
829    DialogItemSetText(di, _("Sticky state"));
830    DialogItemCheckButtonSetPtr(di, &sd->snap_sticky);
831 
832    di = DialogAddItem(table, DITEM_CHECKBUTTON);
833    DialogItemSetColSpan(di, 2);
834    DialogItemSetText(di, _("Stacking layer"));
835    DialogItemCheckButtonSetPtr(di, &sd->snap_layer);
836 
837    di = DialogAddItem(table, DITEM_CHECKBUTTON);
838    DialogItemSetColSpan(di, 2);
839    DialogItemSetText(di, _("Window List Skip"));
840    DialogItemCheckButtonSetPtr(di, &sd->snap_skiplists);
841 
842 #if USE_COMPOSITE
843    di = DialogAddItem(table, DITEM_CHECKBUTTON);
844    DialogItemSetColSpan(di, 2);
845    DialogItemSetText(di, _("Opacity"));
846    DialogItemCheckButtonSetPtr(di, &sd->snap_opacity);
847 
848    di = DialogAddItem(table, DITEM_CHECKBUTTON);
849    DialogItemSetColSpan(di, 2);
850    DialogItemSetText(di, _("Shadowing"));
851    DialogItemCheckButtonSetPtr(di, &sd->snap_shadow);
852 #endif
853 
854    di = DialogAddItem(table, DITEM_CHECKBUTTON);
855    DialogItemSetColSpan(di, 2);
856    DialogItemSetText(di, _("Flags"));
857    DialogItemCheckButtonSetPtr(di, &sd->snap_flags);
858 
859    di = DialogAddItem(table, DITEM_NONE);
860    DialogItemSetColSpan(di, 2);
861 
862    if (ewin->icccm.wm_command)
863      {
864 	char                ok = 1;
865 
866 	if (ewin->icccm.wm_machine)
867 	  {
868 	     if (strcmp(ewin->icccm.wm_machine, Mode.wm.machine_name))
869 		ok = 0;
870 	  }
871 	if (ok)
872 	  {
873 	     di = DialogAddItem(table, DITEM_CHECKBUTTON);
874 	     DialogItemSetColSpan(di, 4);
875 	     DialogItemSetText(di, _("Restart application on login"));
876 	     DialogItemCheckButtonSetPtr(di, &sd->snap_cmd);
877 	  }
878 	else
879 	  {
880 	     di = DialogAddItem(table, DITEM_NONE);
881 	     DialogItemSetColSpan(di, 4);
882 	  }
883      }
884    else
885      {
886 	di = DialogAddItem(table, DITEM_NONE);
887 	DialogItemSetColSpan(di, 4);
888      }
889 
890    if (ewin->groups)
891      {
892 	di = DialogAddItem(table, DITEM_CHECKBUTTON);
893 	DialogItemSetColSpan(di, 4);
894 	DialogItemSetText(di, _("Remember this window's group(s)"));
895 	DialogItemCheckButtonSetPtr(di, &sd->snap_group);
896      }
897 }
898 
899 static const DialogDef DlgSnap = {
900    NULL,
901    NULL, N_("Remembered Application Attributes"),
902    sizeof(SnapDlgData),
903    SOUND_NONE,
904    "pix/snapshots.png",
905    N_("Select the attributes of this\n"
906       "window you wish to Remember\n" "from now on\n"),
907    _DlgFillSnap,
908    DLG_OAC, _DlgApplySnap, NULL
909 };
910 
911 static void
_EwinSnapDialog(const EWin * ewin)912 _EwinSnapDialog(const EWin * ewin)
913 {
914    char                s[1024];
915 
916    Esnprintf(s, sizeof(s), "SNAPSHOT_WINDOW-%#x", EwinGetClientXwin(ewin));
917 
918    DialogShowSimpleWithName(&DlgSnap, s, (void *)ewin);
919 }
920 
921 /* list of remembered items for the remember dialog -- it's either
922  * _another_ global var, or a wrapper struct to pass data to the
923  * callback funcs besides the dialog itself -- this is much easier */
924 
925 typedef struct _remwinlist {
926    Snapshot           *snap;
927    char                remove;
928 } RememberWinList;
929 
930 static RememberWinList *rd_ewin_list;
931 
932 static void
_DlgApplyRemember(Dialog * d __UNUSED__,int val __UNUSED__,void * data __UNUSED__)933 _DlgApplyRemember(Dialog * d __UNUSED__,
934 		  int val __UNUSED__, void *data __UNUSED__)
935 {
936    int                 i;
937 
938    if (!rd_ewin_list)
939       return;
940 
941    for (i = 0; rd_ewin_list[i].snap; i++)
942      {
943 	if (!rd_ewin_list[i].remove)
944 	   continue;
945 
946 	_SnapDestroy(rd_ewin_list[i].snap);
947      }
948    /* save snapshot info to disk */
949    SnapshotsSave();
950 }
951 
952 static void
_DlgExitRemember(Dialog * d __UNUSED__)953 _DlgExitRemember(Dialog * d __UNUSED__)
954 {
955    EFREE_NULL(rd_ewin_list);
956 }
957 
958 static void
CB_RememberWindowSettings(Dialog * d __UNUSED__,int val __UNUSED__,void * data)959 CB_RememberWindowSettings(Dialog * d __UNUSED__, int val __UNUSED__, void *data)
960 {
961    RememberWinList    *rd = (RememberWinList *) data;
962    Snapshot           *sn;
963 
964    if (!rd)
965       return;
966 
967    /* Make sure its still there */
968    sn = LIST_CHECK(Snapshot, &ss_list, rd->snap);
969 
970    if (!sn || !sn->used)
971       return;
972    _EwinSnapDialog(sn->used);
973 }
974 
975 static void
_DlgFillRemember(Dialog * d __UNUSED__,DItem * table,void * data __UNUSED__)976 _DlgFillRemember(Dialog * d __UNUSED__, DItem * table, void *data __UNUSED__)
977 {
978    DItem              *di;
979    Snapshot           *sn;
980    int                 i, num;
981    char                buf[128];
982    const char         *s;
983 
984    DialogItemTableSetOptions(table, 3, 0, 0, 0);
985 
986    num = LIST_GET_COUNT(&ss_list);
987    rd_ewin_list = EMALLOC(RememberWinList, num + 1);
988 
989    if (num > 0)
990      {
991 	di = DialogAddItem(table, DITEM_TEXT);
992 	DialogItemSetColSpan(di, 3);
993 	DialogItemSetFill(di, 0, 0);
994 	DialogItemSetAlign(di, 0, 512);
995 	DialogItemSetText(di, _("Delete"));
996      }
997 
998    i = 0;
999    LIST_FOR_EACH(Snapshot, &ss_list, sn)
1000    {
1001       rd_ewin_list[i].snap = sn;
1002       rd_ewin_list[i].remove = 0;
1003 
1004       di = DialogAddItem(table, DITEM_CHECKBUTTON);
1005       DialogItemSetColSpan(di, 2);
1006       DialogItemSetAlign(di, 0, 512);
1007       if (sn->used)
1008 	 s = EwinGetTitle(sn->used);
1009       else if (sn->win_title)
1010 	 s = sn->win_title;
1011       else
1012 	{
1013 	   Esnprintf(buf, sizeof(buf), "%s.%s", sn->win_name, sn->win_class);
1014 	   s = buf;
1015 	}
1016       DialogItemSetText(di, s);
1017       DialogItemCheckButtonSetPtr(di, &(rd_ewin_list[i].remove));
1018 
1019       if (sn->used)
1020 	{
1021 	   di = DialogAddItem(table, DITEM_BUTTON);
1022 	   DialogItemSetAlign(di, 0, 512);
1023 	   DialogItemSetText(di, _("Remembered Settings..."));
1024 	   DialogItemSetCallback(di, CB_RememberWindowSettings, 0,
1025 				 (char *)(&rd_ewin_list[i]));
1026 	}
1027       else
1028 	{
1029 	   di = DialogAddItem(table, DITEM_TEXT);
1030 	   DialogItemSetText(di, _("Unused"));
1031 	}
1032       i++;
1033    }
1034    rd_ewin_list[num].snap = NULL;
1035 
1036    /* finish remember window */
1037    if (!num)
1038      {
1039 	di = DialogAddItem(table, DITEM_TEXT);
1040 	DialogItemSetColSpan(di, 3);
1041 	DialogItemSetText(di,
1042 			  _
1043 			  ("There are no active windows with remembered attributes."));
1044      }
1045 }
1046 
1047 const DialogDef     DlgRemember = {
1048    "CONFIGURE_PAGER",
1049    N_("Remember"), N_("Remembered Windows Settings"),
1050    0,
1051    SOUND_SETTINGS_PAGER,
1052    "pix/snapshots.png",
1053    N_("Enlightenment Remembered\n" "Windows Settings Dialog"),
1054    _DlgFillRemember,
1055    DLG_OC, _DlgApplyRemember, _DlgExitRemember
1056 };
1057 #endif /* ENABLE_DIALOGS */
1058 
1059 /* ... combine writes, only save after a timeout */
1060 static int
_SnapshotsSaveReal(void * data __UNUSED__)1061 _SnapshotsSaveReal(void *data __UNUSED__)
1062 {
1063    SnapshotsSaveReal();
1064    return 0;
1065 }
1066 
1067 void
SnapshotsSave(void)1068 SnapshotsSave(void)
1069 {
1070    TIMER_DEL(ss_timer);
1071    TIMER_ADD(ss_timer, 5000, _SnapshotsSaveReal, NULL);
1072 }
1073 
1074 /* save out all snapped info to disk */
1075 void
SnapshotsSaveReal(void)1076 SnapshotsSaveReal(void)
1077 {
1078    Snapshot           *sn;
1079    int                 j;
1080    char                buf[4096], s[4096];
1081    FILE               *f;
1082 
1083    if (!Mode.wm.save_ok)
1084       goto done;
1085 
1086    Etmp(s);
1087    f = fopen(s, "w");
1088    if (!f)
1089       goto done;
1090 
1091    LIST_FOR_EACH(Snapshot, &ss_list, sn)
1092    {
1093       fprintf(f, "NEW: %s\n", sn->name);
1094       if (sn->used)
1095 	 fprintf(f, "WIN: %#x\n", EwinGetClientXwin(sn->used));
1096       if ((sn->match_flags & SNAP_MATCH_TITLE) && sn->win_title)
1097 	 fprintf(f, "TITLE: %s\n", sn->win_title);
1098       if ((sn->match_flags & SNAP_MATCH_NAME) && sn->win_name)
1099 	 fprintf(f, "NAME: %s\n", sn->win_name);
1100       if ((sn->match_flags & SNAP_MATCH_CLASS) && sn->win_class)
1101 	 fprintf(f, "CLASS: %s\n", sn->win_class);
1102       if ((sn->match_flags & SNAP_MATCH_ROLE) && sn->win_role)
1103 	 fprintf(f, "ROLE: %s\n", sn->win_role);
1104       if (sn->track_changes)
1105 	 fprintf(f, "AUTO: yes\n");
1106       if ((sn->use_flags & SNAP_USE_BORDER) && sn->border_name)
1107 	 fprintf(f, "BORDER: %s\n", sn->border_name);
1108       if ((sn->use_flags & SNAP_USE_COMMAND) && sn->cmd)
1109 	 fprintf(f, "CMD: %s\n", sn->cmd);
1110       if (sn->use_flags & SNAP_USE_DESK)
1111 	 fprintf(f, "DESKTOP: %i\n", sn->desktop);
1112       if (sn->use_flags & SNAP_USE_POS)
1113 	 fprintf(f, "RES: %i %i\n", WinGetW(VROOT), WinGetH(VROOT));
1114       if (sn->use_flags & SNAP_USE_SIZE)
1115 	 fprintf(f, "WH: %i %i\n", sn->w, sn->h);
1116       if (sn->use_flags & SNAP_USE_POS)
1117 	 fprintf(f, "XY: %i %i %i %i\n", sn->x, sn->y, sn->area_x, sn->area_y);
1118       if (sn->use_flags & SNAP_USE_LAYER)
1119 	 fprintf(f, "LAYER: %i\n", sn->layer);
1120       if (sn->use_flags & SNAP_USE_STICKY)
1121 	 fprintf(f, "STICKY: %i\n", sn->sticky);
1122       if (sn->use_flags & SNAP_USE_SHADED)
1123 	 fprintf(f, "SHADE: %i\n", sn->shaded);
1124       if (sn->use_flags & SNAP_USE_SKIP_LISTS)
1125 	{
1126 	   fprintf(f, "SKIPTASK: %i\n", sn->skiptask);
1127 	   fprintf(f, "SKIPWINLIST: %i\n", sn->skipwinlist);
1128 	   fprintf(f, "SKIPFOCUS: %i\n", sn->skipfocus);
1129 	}
1130       if (sn->use_flags & SNAP_USE_FLAGS)
1131 	 fprintf(f, "FLAGS: %#x %#x\n", sn->flags[0], sn->flags[1]);
1132 #if USE_COMPOSITE
1133       if (sn->use_flags & SNAP_USE_OPACITY)
1134 	 fprintf(f, "OPACITY: %i %i\n", sn->opacity, sn->focused_opacity);
1135       if (sn->use_flags & SNAP_USE_SHADOW)
1136 	 fprintf(f, "SHADOW: %i\n", sn->shadow);
1137 #endif
1138       if (sn->use_flags & SNAP_USE_GROUPS)
1139 	{
1140 	   fprintf(f, "GROUP:");
1141 	   for (j = 0; j < sn->num_groups; j++)
1142 	      fprintf(f, " %i", sn->groups[j]);
1143 	   fprintf(f, "\n");
1144 	}
1145       fprintf(f, "\n");
1146    }
1147 
1148    fclose(f);
1149 
1150    Esnprintf(buf, sizeof(buf), "%s.snapshots", EGetSavePrefix());
1151 
1152    if (EDebug(EDBUG_TYPE_SNAPS))
1153       Eprintf("%s: %s\n", __func__, buf);
1154    E_mv(s, buf);
1155    if (!isfile(buf))
1156       Alert(_("Error saving snaps file"));
1157 
1158  done:
1159    TIMER_DEL(ss_timer);
1160 }
1161 
1162 void
SnapshotsSpawn(void)1163 SnapshotsSpawn(void)
1164 {
1165    Snapshot           *sn;
1166 
1167    LIST_FOR_EACH(Snapshot, &ss_list, sn)
1168    {
1169       if ((sn->use_flags & SNAP_USE_COMMAND) && (sn->cmd) &&
1170 	  !sn->used && !(sn->match_flags & SNAP_MATCH_MULTIPLE))
1171 	{
1172 	   sn->startup_id = ++Mode.apps.startup_id;
1173 	   Espawn("%s", sn->cmd);
1174 	}
1175    }
1176 }
1177 
1178 /* load all snapped info */
1179 static int
_SnapshotsLoad(FILE * fs)1180 _SnapshotsLoad(FILE * fs)
1181 {
1182    Snapshot           *sn = NULL;
1183    char                buf[4096], *s;
1184    int                 res_w, res_h, a, b, c, d;
1185 
1186    res_w = WinGetW(VROOT);
1187    res_h = WinGetH(VROOT);
1188    while (fgets(buf, sizeof(buf), fs))
1189      {
1190 	s = strchr(buf, ':');
1191 	if (!s)
1192 	   continue;
1193 	*s++ = '\0';
1194 	s = Estrtrim(s);
1195 	if (!buf[0])
1196 	   continue;
1197 	if (!strcmp(buf, "NEW"))
1198 	  {
1199 	     res_w = WinGetW(VROOT);
1200 	     res_h = WinGetH(VROOT);
1201 	     sn = _SnapCreate(s);
1202 	  }
1203 	else if (sn)
1204 	  {
1205 	     if (!strcmp(buf, "WIN"))
1206 	       {
1207 		  sn->win = strtoul(s, NULL, 0);
1208 	       }
1209 	     else if (!strcmp(buf, "TITLE"))
1210 	       {
1211 		  sn->win_title = Estrdup(s);
1212 		  sn->match_flags |= SNAP_MATCH_TITLE;
1213 	       }
1214 	     else if (!strcmp(buf, "NAME"))
1215 	       {
1216 		  sn->win_name = Estrdup(s);
1217 		  sn->match_flags |= SNAP_MATCH_NAME;
1218 	       }
1219 	     else if (!strcmp(buf, "CLASS"))
1220 	       {
1221 		  sn->win_class = Estrdup(s);
1222 		  sn->match_flags |= SNAP_MATCH_CLASS;
1223 	       }
1224 	     else if (!strcmp(buf, "ROLE"))
1225 	       {
1226 		  sn->win_role = Estrdup(s);
1227 		  sn->match_flags |= SNAP_MATCH_ROLE;
1228 	       }
1229 	     else if (!strcmp(buf, "AUTO"))
1230 	       {
1231 		  sn->track_changes = 1;
1232 	       }
1233 	     else if (!strcmp(buf, "BORDER"))
1234 	       {
1235 		  sn->use_flags |= SNAP_USE_BORDER;
1236 		  sn->border_name = Estrdup(s);
1237 	       }
1238 	     else if (!strcmp(buf, "CMD"))
1239 	       {
1240 		  sn->use_flags |= SNAP_USE_COMMAND;
1241 		  sn->cmd = Estrdup(s);
1242 	       }
1243 	     else if (!strcmp(buf, "DESKTOP"))
1244 	       {
1245 		  sn->use_flags |= SNAP_USE_DESK;
1246 		  sn->desktop = atoi(s);
1247 	       }
1248 	     else if (!strcmp(buf, "RES"))
1249 	       {
1250 		  if (sscanf(s, "%u %u", &a, &b) < 2)
1251 		     continue;
1252 		  if (a <= 0 || b <= 0)
1253 		     continue;
1254 		  res_w = a;
1255 		  res_h = b;
1256 	       }
1257 	     else if (!strcmp(buf, "WH"))
1258 	       {
1259 		  if (sscanf(s, "%u %u", &a, &b) < 2)
1260 		     continue;
1261 		  if (a <= 0 || b <= 0)
1262 		     continue;
1263 		  sn->use_flags |= SNAP_USE_SIZE;
1264 		  sn->w = a;
1265 		  sn->h = b;
1266 	       }
1267 	     else if (!strcmp(buf, "XY"))
1268 	       {
1269 		  if (sscanf(s, "%d %d %u %u", &a, &b, &c, &d) < 4)
1270 		     continue;
1271 		  sn->use_flags |= SNAP_USE_POS;
1272 		  sn->x = a;
1273 		  sn->y = b;
1274 		  /* we changed reses since we last used this snapshot file */
1275 		  if (res_w != WinGetW(VROOT))
1276 		    {
1277 		       if (sn->use_flags & SNAP_USE_SIZE)
1278 			 {
1279 			    if ((res_w - sn->w) <= 0)
1280 			       sn->x = 0;
1281 			    else
1282 			       sn->x =
1283 				  (sn->x * (WinGetW(VROOT) - sn->w)) /
1284 				  (res_w - sn->w);
1285 			 }
1286 		       else
1287 			 {
1288 			    if (sn->x >= WinGetW(VROOT))
1289 			       sn->x = WinGetW(VROOT) - 32;
1290 			 }
1291 		    }
1292 		  if (res_h != WinGetH(VROOT))
1293 		    {
1294 		       if (sn->use_flags & SNAP_USE_SIZE)
1295 			 {
1296 			    if ((res_h - sn->h) <= 0)
1297 			       sn->y = 0;
1298 			    else
1299 			       sn->y =
1300 				  (sn->y * (WinGetH(VROOT) - sn->h)) /
1301 				  (res_h - sn->h);
1302 			 }
1303 		       else
1304 			 {
1305 			    if (sn->y >= WinGetH(VROOT))
1306 			       sn->y = WinGetH(VROOT) - 32;
1307 			 }
1308 		    }
1309 		  sn->area_x = c;
1310 		  sn->area_y = d;
1311 	       }
1312 	     else if (!strcmp(buf, "LAYER"))
1313 	       {
1314 		  sn->use_flags |= SNAP_USE_LAYER;
1315 		  sn->layer = atoi(s);
1316 	       }
1317 	     else if (!strcmp(buf, "STICKY"))
1318 	       {
1319 		  sn->use_flags |= SNAP_USE_STICKY;
1320 		  sn->sticky = atoi(s);
1321 	       }
1322 	     else if (!strcmp(buf, "SHADE"))
1323 	       {
1324 		  sn->use_flags |= SNAP_USE_SHADED;
1325 		  sn->shaded = atoi(s);
1326 	       }
1327 	     else if (!strcmp(buf, "SKIPFOCUS"))
1328 	       {
1329 		  sn->use_flags |= SNAP_USE_SKIP_LISTS;
1330 		  sn->skipfocus = atoi(s);
1331 	       }
1332 	     else if (!strcmp(buf, "SKIPTASK"))
1333 	       {
1334 		  sn->use_flags |= SNAP_USE_SKIP_LISTS;
1335 		  sn->skiptask = atoi(s);
1336 	       }
1337 	     else if (!strcmp(buf, "SKIPWINLIST"))
1338 	       {
1339 		  sn->use_flags |= SNAP_USE_SKIP_LISTS;
1340 		  sn->skipwinlist = atoi(s);
1341 	       }
1342 	     else if (!strcmp(buf, "FLAGS"))
1343 	       {
1344 		  sn->use_flags |= SNAP_USE_FLAGS;
1345 		  sn->flags[0] = sn->flags[1] = 0;
1346 		  sscanf(s, "%i %i", sn->flags, sn->flags + 1);
1347 	       }
1348 	     else if (!strcmp(buf, "GROUP"))
1349 	       {
1350 		  sn->use_flags |= SNAP_USE_GROUPS;
1351 		  for (;; s += b)
1352 		    {
1353 		       b = 0;
1354 		       sscanf(s, "%d %n", &a, &b);
1355 		       if (b <= 0)
1356 			  break;
1357 		       sn->num_groups++;
1358 		       sn->groups = EREALLOC(int, sn->groups, sn->num_groups);
1359 		       sn->groups[sn->num_groups - 1] = a;
1360 		       GroupSetUsed(a);
1361 		    }
1362 	       }
1363 #if USE_COMPOSITE
1364 	     else if (!strcmp(buf, "OPACITY"))
1365 	       {
1366 		  sn->use_flags |= SNAP_USE_OPACITY;
1367 		  a = 100;
1368 		  b = 100;
1369 		  sscanf(s, "%i %i", &a, &b);
1370 		  if (b == 1)
1371 		     b = 100;	/* BW compat - focused is opaque */
1372 		  sn->opacity = a;
1373 		  sn->focused_opacity = b;
1374 	       }
1375 	     else if (!strcmp(buf, "SHADOW"))
1376 	       {
1377 		  sn->use_flags |= SNAP_USE_SHADOW;
1378 		  sn->shadow = atoi(s);
1379 	       }
1380 #endif
1381 	  }
1382      }
1383 
1384    return 0;
1385 }
1386 
1387 void
SnapshotsLoad(void)1388 SnapshotsLoad(void)
1389 {
1390    char                s[4096];
1391 
1392    GroupsLoad();
1393 
1394    Esnprintf(s, sizeof(s), "%s.snapshots", EGetSavePrefix());
1395 
1396    ConfigFileLoad(s, NULL, _SnapshotsLoad, 0);
1397 
1398    /* Remove groups not referenced by snapshots */
1399    GroupsPrune();
1400 }
1401 
1402 /* make a client window conform to snapshot info */
1403 void
SnapshotEwinApply(EWin * ewin)1404 SnapshotEwinApply(EWin * ewin)
1405 {
1406    Snapshot           *sn;
1407    int                 ax, ay;
1408    unsigned int        use_flags;
1409 
1410    _SnapEwinFind(ewin);		/* Find a saved settings match */
1411 
1412    sn = ewin->snap;
1413    if (!sn)
1414      {
1415 	if (ewin->props.autosave)
1416 	   _EwinSnapSet(ewin, SNAP_MATCH_DEFAULT, SNAP_USE_ALL | SNAP_AUTO);
1417 	return;
1418      }
1419 
1420    if (ewin->props.autosave)
1421       sn->track_changes = 1;
1422 
1423    use_flags = sn->use_flags;
1424    /* If restarting don't override stuff set in attributes/properties */
1425    if (ewin->state.identified)
1426       use_flags &= SNAP_USE_LAYER | SNAP_USE_SHADOW | SNAP_USE_GROUPS |
1427 	 SNAP_USE_OPACITY;
1428 
1429    if (use_flags & SNAP_USE_STICKY)
1430       EoSetSticky(ewin, sn->sticky);
1431 
1432    if (use_flags & SNAP_USE_DESK)
1433       EoSetDesk(ewin, DeskGetValid(sn->desktop));
1434 
1435    if (use_flags & SNAP_USE_SIZE)
1436      {
1437 	ewin->client.w = sn->w;
1438 	ewin->client.h = sn->h;
1439 	ewin->state.maximized_horz = ewin->state.maximized_vert = 0;
1440      }
1441 
1442    if (use_flags & SNAP_USE_POS)
1443      {
1444 	ewin->state.placed = 1;
1445 	ewin->client.x = sn->x;
1446 	ewin->client.y = sn->y;
1447 	ewin->icccm.grav = NorthWestGravity;
1448 #if 0				/* No, do later in EwinDetermineArea() */
1449 	ewin->area_x = sn->area_x;
1450 	ewin->area_y = sn->area_y;
1451 #endif
1452 	if (!EoIsSticky(ewin))
1453 	  {
1454 	     DeskGetArea(EoGetDesk(ewin), &ax, &ay);
1455 	     ewin->client.x += ((sn->area_x - ax) * WinGetW(VROOT));
1456 	     ewin->client.y += ((sn->area_y - ay) * WinGetH(VROOT));
1457 	  }
1458      }
1459 
1460    if (use_flags & SNAP_USE_LAYER)
1461       EoSetLayer(ewin, sn->layer);
1462 
1463    if (use_flags & SNAP_USE_SKIP_LISTS)
1464      {
1465 	ewin->props.skip_focuslist = sn->skipfocus;
1466 	ewin->props.skip_ext_task = sn->skiptask;
1467 	ewin->props.skip_winlist = sn->skipwinlist;
1468      }
1469 
1470    if (use_flags & SNAP_USE_FLAGS)
1471       EwinFlagsDecode(ewin, sn->flags);
1472 
1473    if (use_flags & SNAP_USE_SHADED)
1474       ewin->state.shaded = sn->shaded;
1475 
1476    if (use_flags & SNAP_USE_BORDER)
1477       EwinBorderSetInitially(ewin, sn->border_name);
1478 
1479    if (use_flags & SNAP_USE_GROUPS)
1480       GroupsEwinAdd(ewin, sn->groups, sn->num_groups);
1481 
1482 #if USE_COMPOSITE
1483    if (use_flags & SNAP_USE_OPACITY)
1484      {
1485 	sn->opacity = OpacityFix(sn->opacity, 0);
1486 	sn->focused_opacity = OpacityFix(sn->focused_opacity, 0);
1487 	ewin->props.opacity = OpacityFromPercent(sn->opacity);
1488 	ewin->props.focused_opacity = OpacityFromPercent(sn->focused_opacity);
1489 	ewin->ewmh.opacity_update = 1;	/* Set opacity on client window */
1490      }
1491 
1492    if (use_flags & SNAP_USE_SHADOW)
1493       EoSetShadow(ewin, sn->shadow);
1494 #endif
1495 
1496    if (EDebug(EDBUG_TYPE_SNAPS))
1497       Eprintf("Snap get snap  %#x: %4d+%4d %4dx%4d: %s\n",
1498 	      EwinGetClientXwin(ewin), ewin->client.x, ewin->client.y,
1499 	      ewin->client.w, ewin->client.h, EwinGetTitle(ewin));
1500 }
1501 
1502 /* Detach snapshot from ewin */
1503 void
SnapshotEwinUnmatch(EWin * ewin)1504 SnapshotEwinUnmatch(EWin * ewin)
1505 {
1506    Snapshot           *sn;
1507 
1508    sn = ewin->snap;
1509    if (!sn)
1510       return;
1511 
1512    ewin->snap = NULL;
1513    sn->used = NULL;
1514 }
1515 
1516 void
SnapshotEwinParse(EWin * ewin,const char * params)1517 SnapshotEwinParse(EWin * ewin, const char *params)
1518 {
1519    char                param[1024];
1520    const char         *p;
1521    int                 len;
1522    unsigned int        match_flags, use_flags;
1523 
1524    p = params;
1525    if (!p)
1526       return;
1527 
1528    match_flags = SNAP_MATCH_DEFAULT;
1529    use_flags = 0;
1530 
1531    for (;;)
1532      {
1533 	param[0] = '\0';
1534 	len = 0;
1535 	sscanf(p, "%s %n", param, &len);
1536 	if (len <= 0)
1537 	   break;
1538 	p += len;
1539 
1540 	if (!strcmp(param, "all"))
1541 	  {
1542 	     use_flags = SNAP_USE_ALL;
1543 	  }
1544 #if ENABLE_DIALOGS
1545 	else if (!strcmp(param, "dialog"))
1546 	  {
1547 	     _EwinSnapDialog(ewin);
1548 	     break;
1549 	  }
1550 #endif
1551 	else if (!strcmp(param, "none"))
1552 	   _EwinSnapRemove(ewin);
1553 	else if (!strcmp(param, "auto"))
1554 	   use_flags |= SNAP_AUTO;
1555 	else if (!strcmp(param, "border"))
1556 	   use_flags |= SNAP_USE_BORDER;
1557 	else if (!strcmp(param, "command"))
1558 	   use_flags |= SNAP_USE_COMMAND;
1559 	else if (!strcmp(param, "desktop"))
1560 	   use_flags |= SNAP_USE_DESK;
1561 	else if (!strcmp(param, "location"))
1562 	   use_flags |= SNAP_USE_POS;
1563 	else if (!strcmp(param, "size"))
1564 	   use_flags |= SNAP_USE_SIZE;
1565 	else if (!strcmp(param, "layer"))
1566 	   use_flags |= SNAP_USE_LAYER;
1567 	else if (!strcmp(param, "shade"))
1568 	   use_flags |= SNAP_USE_SHADED;
1569 	else if (!strcmp(param, "sticky"))
1570 	   use_flags |= SNAP_USE_STICKY;
1571 #if USE_COMPOSITE
1572 	else if (!strcmp(param, "opacity"))
1573 	   use_flags |= SNAP_USE_OPACITY;
1574 	else if (!strcmp(param, "shadow"))
1575 	   use_flags |= SNAP_USE_SHADOW;
1576 #endif
1577 	else if (!strcmp(param, "group"))
1578 	   use_flags |= SNAP_USE_GROUPS;
1579      }
1580 
1581    if (ewin->snap)
1582      {
1583 	match_flags = ewin->snap->match_flags;
1584 	use_flags |= ewin->snap->use_flags;
1585      }
1586 
1587    _EwinSnapSet(ewin, match_flags, use_flags);
1588 
1589    SnapshotsSave();
1590 }
1591 
1592 /*
1593  * IPC functions
1594  * A bit ugly...
1595  */
1596 const char          SnapshotsIpcText[] =
1597    "usage:\n" "  list_remember [full]\n"
1598    "  Retrieve a list of remembered windows.  with full, the list\n"
1599    "  includes the window's remembered attributes\n";
1600 
1601 #define SS(s) ((s) ? (s) : NoText)
1602 static const char   NoText[] = "-NONE-";
1603 
1604 static void
_SnapShow(void * data,void * prm)1605 _SnapShow(void *data, void *prm)
1606 {
1607    Snapshot           *sn = (Snapshot *) data;
1608    int                 full = !!prm;
1609    char                buf[FILEPATH_LEN_MAX];
1610    const char         *name;
1611 
1612    name = (sn->name) ? sn->name : "???";
1613 
1614    if (!full)
1615      {
1616 	if (sn->used)
1617 	   IpcPrintf("%s\n", name);
1618 	else
1619 	   IpcPrintf("%s (unused)\n", name);
1620 	return;
1621      }
1622 
1623 #define SU(sn, item) ((sn->match_flags & item) ? '>' : ':')
1624 
1625    if (sn->used)
1626       Esnprintf(buf, sizeof(buf), "In use - %#x", EwinGetClientXwin(sn->used));
1627    else
1628       Esnprintf(buf, sizeof(buf), "*** Unused ***");
1629    IpcPrintf(" Snapshot  Name: %s    %s\n", name, buf);
1630    if (sn->win_title)
1631       IpcPrintf("   Window Title%c %s\n", SU(sn, SNAP_MATCH_TITLE),
1632 		sn->win_title);
1633    if (sn->win_name)
1634       IpcPrintf("   Window  Name%c %s\n", SU(sn, SNAP_MATCH_NAME),
1635 		sn->win_name);
1636    if (sn->win_class)
1637       IpcPrintf("   Window Class%c %s\n", SU(sn, SNAP_MATCH_CLASS),
1638 		sn->win_class);
1639    if (sn->win_role)
1640       IpcPrintf("   Window  Role%c %s\n", SU(sn, SNAP_MATCH_ROLE),
1641 		sn->win_role);
1642 
1643    if (sn->track_changes)
1644       IpcPrintf("      Tracking changes\n");
1645    if (sn->use_flags & SNAP_USE_BORDER)
1646       IpcPrintf("      Border Name: %s\n", SS(sn->border_name));
1647    if (sn->use_flags & SNAP_USE_DESK)
1648       IpcPrintf("          desktop: %d\n", sn->desktop);
1649    if (sn->use_flags & SNAP_USE_POS)
1650       IpcPrintf("           (x, y): %d, %d    area (x, y): %d, %d\n",
1651 		sn->x, sn->y, sn->area_x, sn->area_y);
1652    if (sn->use_flags & SNAP_USE_SIZE)
1653       IpcPrintf("           (w, h): %d, %d\n", sn->w, sn->h);
1654    if (sn->use_flags & SNAP_USE_LAYER)
1655       IpcPrintf("            layer: %d\n", sn->layer);
1656    if (sn->use_flags & SNAP_USE_STICKY)
1657       IpcPrintf("           sticky: %d\n", sn->sticky);
1658    if (sn->use_flags & SNAP_USE_SHADED)
1659       IpcPrintf("            shade: %d\n", sn->shaded);
1660    if (sn->use_flags & SNAP_USE_COMMAND)
1661       IpcPrintf("          command: %s\n", SS(sn->cmd));
1662    if (sn->use_flags & SNAP_USE_SKIP_LISTS)
1663       IpcPrintf
1664 	 ("         skiptask: %d    skipfocus: %d    skipwinlist: %d\n",
1665 	  sn->skiptask, sn->skipfocus, sn->skipwinlist);
1666    if (sn->use_flags & SNAP_USE_FLAGS)
1667       IpcPrintf("            flags: %#x %#x\n", sn->flags[0], sn->flags[1]);
1668    IpcPrintf("\n");
1669 }
1670 
1671 void
SnapshotsIpcFunc(const char * params)1672 SnapshotsIpcFunc(const char *params)
1673 {
1674    const char         *p;
1675    char                cmd[128], prm[4096];
1676    int                 len;
1677    Snapshot           *sn;
1678 
1679    cmd[0] = prm[0] = '\0';
1680    p = params;
1681    if (p)
1682      {
1683 	len = 0;
1684 	sscanf(p, "%100s %4000s %n", cmd, prm, &len);
1685 	p += len;
1686      }
1687 
1688    if (LIST_IS_EMPTY(&ss_list))
1689      {
1690 	IpcPrintf("No remembered windows\n");
1691 	return;
1692      }
1693 
1694    if (!p || cmd[0] == '?')
1695      {
1696 	LIST_FOR_EACH(Snapshot, &ss_list, sn) _SnapShow(sn, NULL);
1697      }
1698    else
1699      {
1700 	LIST_FOR_EACH(Snapshot, &ss_list, sn) _SnapShow(sn, (void *)1L);
1701      }
1702 }
1703