1 /*
2  * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
3  * Copyright (C) 2004-2021 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 "dialog.h"
27 #include "emodule.h"
28 #include "ewins.h"
29 #include "groups.h"
30 #include "list.h"
31 #include "settings.h"
32 #include "snaps.h"
33 
34 #define DEBUG_GROUPS 0
35 #if DEBUG_GROUPS
36 #define Dprintf(fmt, ...) Eprintf("%s: " fmt, __func__, __VA_ARGS__)
37 #else
38 #define Dprintf(fmt...)
39 #endif
40 
41 #define USE_GROUP_SHOWHIDE 1	/* Enable group borders */
42 
43 #define GROUP_SELECT_ALL             0
44 #define GROUP_SELECT_EWIN_ONLY       1
45 #define GROUP_SELECT_ALL_EXCEPT_EWIN 2
46 
47 #define SET_OFF    0
48 #define SET_ON     1
49 #define SET_TOGGLE 2
50 
51 typedef struct _groupconfig {
52    char                iconify;
53    char                kill;
54    char                move;
55    char                raise;
56    char                set_border;
57    char                shade;
58    char                stick;
59 } GroupConfig;
60 
61 struct _group {
62    dlist_t             list;
63    int                 index;
64    EWin              **members;
65    int                 num_members;
66    GroupConfig         cfg;
67    char                keep;	/* Don't destroy when empty */
68    char                used;	/* Don't discard when saving */
69 };
70 
71 static              LIST_HEAD(group_list);
72 
73 static struct {
74    GroupConfig         dflt;
75    char                swapmove;
76 } Conf_groups;
77 
78 static struct {
79    Group              *current;
80 } Mode_groups;
81 
82 static void         _GroupsSave(void);
83 
84 int
GroupsGetSwapmove(void)85 GroupsGetSwapmove(void)
86 {
87    return Conf_groups.swapmove;
88 }
89 
90 static Group       *
_GroupCreate(int gid,int cfg_update)91 _GroupCreate(int gid, int cfg_update)
92 {
93    Group              *g;
94 
95    g = ECALLOC(Group, 1);
96    if (!g)
97       return NULL;
98 
99    LIST_APPEND(Group, &group_list, g);
100 
101    if (gid == -1)
102      {
103 	/* Create new group id */
104 	/* ... using us time. Should really be checked for uniqueness. */
105 	g->index = (int)GetTimeUs();
106      }
107    else
108      {
109 	/* Use given group id */
110 	g->index = gid;
111      }
112    g->cfg.iconify = Conf_groups.dflt.iconify;
113    g->cfg.kill = Conf_groups.dflt.kill;
114    g->cfg.move = Conf_groups.dflt.move;
115    g->cfg.raise = Conf_groups.dflt.raise;
116    g->cfg.set_border = Conf_groups.dflt.set_border;
117    g->cfg.stick = Conf_groups.dflt.stick;
118    g->cfg.shade = Conf_groups.dflt.shade;
119 
120    Dprintf("%s: grp=%p gid=%d\n", __func__, g, g->index);
121 
122    if (cfg_update)
123       _GroupsSave();
124 
125    return g;
126 }
127 
128 static void
_GroupDestroy(Group * g,int cfg_update)129 _GroupDestroy(Group * g, int cfg_update)
130 {
131    if (!g)
132       return;
133 
134    Dprintf("%s: grp=%p gid=%d\n", __func__, g, g->index);
135 
136    LIST_REMOVE(Group, &group_list, g);
137 
138    if (g == Mode_groups.current)
139       Mode_groups.current = NULL;
140 
141    Efree(g->members);
142    Efree(g);
143 
144    if (cfg_update)
145       _GroupsSave();
146 }
147 
148 static int
_GroupMatchId(const void * data,const void * match)149 _GroupMatchId(const void *data, const void *match)
150 {
151    return ((const Group *)data)->index != PTR2INT(match);
152 }
153 
154 static Group       *
_GroupFind(int gid)155 _GroupFind(int gid)
156 {
157    return LIST_FIND(Group, &group_list, _GroupMatchId, INT2PTR(gid));
158 }
159 
160 EWin               *const *
GroupGetMembers(const Group * g,int * num)161 GroupGetMembers(const Group * g, int *num)
162 {
163    *num = g->num_members;
164    return g->members;
165 }
166 
167 int
GroupGetIndex(const Group * g)168 GroupGetIndex(const Group * g)
169 {
170    return g->index;
171 }
172 
173 void
GroupSetUsed(int gid)174 GroupSetUsed(int gid)
175 {
176    Group              *g;
177 
178    g = _GroupFind(gid);
179    if (!g)
180       return;
181 
182    g->used = 1;
183 }
184 
185 int
GroupMatchAction(const Group * g,int action)186 GroupMatchAction(const Group * g, int action)
187 {
188    int                 match;
189 
190    switch (action)
191      {
192      default:
193 	match = 0;
194 	break;
195      case GROUP_ACTION_ANY:
196 	match = 1;
197 	break;
198      case GROUP_ACTION_SET_WINDOW_BORDER:
199 	match = g->cfg.set_border;
200 	break;
201      case GROUP_ACTION_ICONIFY:
202 	match = g->cfg.iconify;
203 	break;
204      case GROUP_ACTION_MOVE:
205 	match = g->cfg.move;
206 	break;
207      case GROUP_ACTION_STACKING:
208 	match = g->cfg.raise;
209 	break;
210      case GROUP_ACTION_STICK:
211 	match = g->cfg.stick;
212 	break;
213      case GROUP_ACTION_SHADE:
214 	match = g->cfg.shade;
215 	break;
216      case GROUP_ACTION_KILL:
217 	match = g->cfg.kill;
218 	break;
219      }
220 
221    return match;
222 }
223 
224 static Group       *
_GroupFind2(const char * groupid)225 _GroupFind2(const char *groupid)
226 {
227    int                 gid;
228 
229    if (groupid[0] == '*' || groupid[0] == '\0')
230       return Mode_groups.current;
231 
232 #if ENABLE_DIALOGS
233 #define GROUP_CHOOSE ((Group*)1)
234    if (groupid[0] == '=')
235       return GROUP_CHOOSE;
236 #endif
237 
238    gid = 0;
239    sscanf(groupid, "%d", &gid);
240    if (gid == 0)
241       return NULL;
242 
243    return _GroupFind(gid);
244 }
245 
246 static int
_EwinGroupIndex(const EWin * ewin,const Group * g)247 _EwinGroupIndex(const EWin * ewin, const Group * g)
248 {
249    int                 i;
250 
251    for (i = 0; i < ewin->num_groups; i++)
252       if (ewin->groups[i] == g)
253 	 return i;
254 
255    return -1;
256 }
257 
258 static int
_GroupEwinIndex(const Group * g,const EWin * ewin)259 _GroupEwinIndex(const Group * g, const EWin * ewin)
260 {
261    int                 i;
262 
263    for (i = 0; i < g->num_members; i++)
264       if (g->members[i] == ewin)
265 	 return i;
266 
267    return -1;
268 }
269 
270 static void
_GroupEwinAdd(Group * g,EWin * ewin,int snap_update)271 _GroupEwinAdd(Group * g, EWin * ewin, int snap_update)
272 {
273    if (!ewin || !g)
274       return;
275 
276    if (_EwinGroupIndex(ewin, g) >= 0)
277       return;			/* Already there */
278 
279    Dprintf("%s: gid=%8d: %s\n", __func__, g->index, EoGetName(ewin));
280 
281    ewin->num_groups++;
282    ewin->groups = EREALLOC(Group *, ewin->groups, ewin->num_groups);
283    ewin->groups[ewin->num_groups - 1] = g;
284    g->num_members++;
285    g->members = EREALLOC(EWin *, g->members, g->num_members);
286    g->members[g->num_members - 1] = ewin;
287 
288    if (snap_update)
289       SnapshotEwinUpdate(ewin, SNAP_USE_GROUPS);
290 }
291 
292 static void
_GroupEwinRemove(Group * g,EWin * ewin,int snap_update)293 _GroupEwinRemove(Group * g, EWin * ewin, int snap_update)
294 {
295    int                 i, ie, ig;
296 
297    if (!ewin || !g)
298       return;
299 
300    Dprintf("%s: gid=%8d: %s\n", __func__, g->index, EoGetName(ewin));
301 
302    ie = _EwinGroupIndex(ewin, g);
303    if (ie < 0)
304      {
305 	/* Should not happen */
306 	Dprintf("%s: g=%p gid=%8d: %s: Group not found?!?\n", __func__,
307 		g, g->index, EoGetName(ewin));
308 	return;
309      }
310 
311    ig = _GroupEwinIndex(g, ewin);
312    if (ig < 0)
313      {
314 	/* Should not happen */
315 	Dprintf("%s: g=%p gid=%8d: %s: Ewin not found?!?\n", __func__,
316 		g, g->index, EoGetName(ewin));
317 	return;
318      }
319 
320    Dprintf("%s: gid=%8d index=%d/%d: %s\n", __func__,
321 	   g->index, ie, ig, EoGetName(ewin));
322 
323    /* remove it from the group */
324    g->num_members--;
325    for (i = ig; i < g->num_members; i++)
326       g->members[i] = g->members[i + 1];
327 
328    if (g->num_members > 0)
329       g->members = EREALLOC(EWin *, g->members, g->num_members);
330    else if (g->keep)
331       EFREE_NULL(g->members);
332    else
333       _GroupDestroy(g, snap_update);
334 
335    /* and remove the group from the groups that the window is in */
336    ewin->num_groups--;
337    for (i = ie; i < ewin->num_groups; i++)
338       ewin->groups[i] = ewin->groups[i + 1];
339 
340    if (ewin->num_groups > 0)
341       ewin->groups = EREALLOC(Group *, ewin->groups, ewin->num_groups);
342    else
343       EFREE_NULL(ewin->groups);
344 
345    if (snap_update)
346       SnapshotEwinUpdate(ewin, SNAP_USE_GROUPS);
347 }
348 
349 static void
_GroupDelete(Group * g)350 _GroupDelete(Group * g)
351 {
352    if (!g)
353       return;
354 
355    Dprintf("%s: gid=%d\n", __func__, g->index);
356 
357    g->keep = 1;
358    while (g->num_members > 0)
359       _GroupEwinRemove(g, g->members[0], 1);
360    _GroupDestroy(g, 1);
361 }
362 
363 Group             **
GroupsGetList(int * pnum)364 GroupsGetList(int *pnum)
365 {
366    return LIST_GET_ITEMS(Group, &group_list, pnum);
367 }
368 
369 #if ENABLE_DIALOGS
370 static Group      **
_EwinListGroups(const EWin * ewin,char group_select,int * num)371 _EwinListGroups(const EWin * ewin, char group_select, int *num)
372 {
373    Group             **groups;
374    Group             **groups2;
375    int                 i, j, n, killed;
376 
377    groups = NULL;
378    *num = 0;
379 
380    switch (group_select)
381      {
382      case GROUP_SELECT_EWIN_ONLY:
383 	*num = n = ewin->num_groups;
384 	if (n <= 0)
385 	   break;
386 	groups = EMALLOC(Group *, n);
387 	if (!groups)
388 	   break;
389 	memcpy(groups, ewin->groups, n * sizeof(Group *));
390 	break;
391      case GROUP_SELECT_ALL_EXCEPT_EWIN:
392 	groups2 = GroupsGetList(num);
393 	if (!groups2)
394 	   break;
395 	n = *num;
396 	for (i = killed = 0; i < n; i++)
397 	  {
398 	     for (j = 0; j < ewin->num_groups; j++)
399 	       {
400 		  if (ewin->groups[j] == groups2[i])
401 		    {
402 		       groups2[i] = NULL;
403 		       killed++;
404 		    }
405 	       }
406 	  }
407 	if (n - killed > 0)
408 	  {
409 	     groups = EMALLOC(Group *, n - killed);
410 	     if (groups)
411 	       {
412 		  for (i = j = 0; i < n; i++)
413 		     if (groups2[i])
414 			groups[j++] = groups2[i];
415 		  *num = n - killed;
416 	       }
417 	  }
418 	Efree(groups2);
419 	break;
420      case GROUP_SELECT_ALL:
421      default:
422 	groups = GroupsGetList(num);
423 	break;
424      }
425 
426    return groups;
427 }
428 #endif /* ENABLE_DIALOGS */
429 
430 /* Update groups on snapped window appearance */
431 void
GroupsEwinAdd(EWin * ewin,const int * pgid,int ngid)432 GroupsEwinAdd(EWin * ewin, const int *pgid, int ngid)
433 {
434    Group              *g;
435    int                 i, gid;
436 
437    for (i = 0; i < ngid; i++)
438      {
439 	gid = pgid[i];
440 	g = _GroupFind(gid);
441 	Dprintf("ewin=%p gid=%d grp=%p\n", ewin, gid, g);
442 	if (!g)
443 	  {
444 	     /* This should not happen, but may if group/snap configs are corrupted */
445 	     g = _GroupCreate(gid, 1);
446 	  }
447 	_GroupEwinAdd(g, ewin, 0);
448      }
449 }
450 
451 /* Update groups on snapped window disappearance */
452 void
GroupsEwinRemove(EWin * ewin)453 GroupsEwinRemove(EWin * ewin)
454 {
455    while (ewin->num_groups > 0)
456       _GroupEwinRemove(ewin->groups[0], ewin, 0);
457 }
458 
459 Group              *
EwinsInGroup(const EWin * ewin1,const EWin * ewin2)460 EwinsInGroup(const EWin * ewin1, const EWin * ewin2)
461 {
462    int                 i;
463 
464    if (ewin1 && ewin2)
465      {
466 	for (i = 0; i < ewin1->num_groups; i++)
467 	  {
468 	     if (_GroupEwinIndex(ewin1->groups[i], ewin2) >= 0)
469 		return ewin1->groups[i];
470 	  }
471      }
472    return NULL;
473 }
474 
475 #if ENABLE_DIALOGS
476 static char       **
_GrouplistMemberNames(Group ** groups,int num)477 _GrouplistMemberNames(Group ** groups, int num)
478 {
479    int                 i, j, len;
480    char              **group_member_strings;
481    const char         *name;
482 
483    group_member_strings = ECALLOC(char *, num);
484 
485    if (!group_member_strings)
486       return NULL;
487 
488    for (i = 0; i < num; i++)
489      {
490 	group_member_strings[i] = EMALLOC(char, 1024);
491 
492 	if (!group_member_strings[i])
493 	   break;
494 
495 	len = 0;
496 	for (j = 0; j < groups[i]->num_members; j++)
497 	  {
498 	     name = EwinGetTitle(groups[i]->members[j]);
499 	     if (!name)		/* Should never happen */
500 		continue;
501 	     len += Esnprintf(group_member_strings[i] + len, 1024 - len,
502 			      "%s\n", name);
503 	     if (len >= 1024)
504 		break;
505 	  }
506 	if (len == 0)
507 	   snprintf(group_member_strings[i], 1024, "(empty)");
508      }
509 
510    return group_member_strings;
511 }
512 #endif /* ENABLE_DIALOGS */
513 
514 #if USE_GROUP_SHOWHIDE
515 static void
_EwinGroupsShowHide(EWin * ewin,int group_index,char onoff)516 _EwinGroupsShowHide(EWin * ewin, int group_index, char onoff)
517 {
518    EWin              **gwins;
519    int                 i, num;
520    const Border       *b = NULL;
521 
522    if (!ewin || group_index >= ewin->num_groups)
523       return;
524 
525    if (group_index < 0)
526      {
527 	gwins = ListWinGroupMembersForEwin(ewin, GROUP_ACTION_ANY, 0, &num);
528      }
529    else
530      {
531 	gwins = ewin->groups[group_index]->members;
532 	num = ewin->groups[group_index]->num_members;
533      }
534 
535    if (onoff == SET_TOGGLE)
536       onoff = (ewin->border == ewin->normal_border) ? SET_ON : SET_OFF;
537 
538    for (i = 0; i < num; i++)
539      {
540 	if (onoff == SET_ON)
541 	   b = EwinBorderGetGroupBorder(gwins[i]);
542 	else
543 	   b = gwins[i]->normal_border;
544 
545 	EwinBorderChange(gwins[i], b, 0);
546      }
547    if (group_index < 0)
548       Efree(gwins);
549 }
550 #else
551 
552 #define _EwinGroupsShowHide(ewin, group_index, onoff)
553 
554 #endif /* USE_GROUP_SHOWHIDE */
555 
556 static void
_GroupsSave(void)557 _GroupsSave(void)
558 {
559    Group              *g;
560    FILE               *f;
561    char                s[1024];
562 
563    Dprintf("%s\n", __func__);
564 
565    Esnprintf(s, sizeof(s), "%s.groups", EGetSavePrefix());
566    f = fopen(s, "w");
567    if (!f)
568       return;
569 
570    LIST_FOR_EACH(Group, &group_list, g)
571    {
572       fprintf(f, "NEW: %i\n", g->index);
573       fprintf(f, "ICONIFY: %i\n", g->cfg.iconify);
574       fprintf(f, "KILL: %i\n", g->cfg.kill);
575       fprintf(f, "MOVE: %i\n", g->cfg.move);
576       fprintf(f, "RAISE: %i\n", g->cfg.raise);
577       fprintf(f, "SET_BORDER: %i\n", g->cfg.set_border);
578       fprintf(f, "STICK: %i\n", g->cfg.stick);
579       fprintf(f, "SHADE: %i\n", g->cfg.shade);
580    }
581 
582    fclose(f);
583 }
584 
585 void
GroupsPrune(void)586 GroupsPrune(void)
587 {
588    Group              *g, *tmp;
589    int                 pruned;
590 
591    pruned = 0;
592 
593    LIST_FOR_EACH_SAFE(Group, &group_list, g, tmp)
594    {
595       if (g->used)
596 	 continue;
597       if (g->members)
598 	 continue;
599       _GroupDestroy(g, 0);
600       pruned += 1;
601    }
602 
603    Dprintf("%s: Pruned=%d\n", __func__, pruned);
604 
605    if (pruned)
606       _GroupsSave();
607 }
608 
609 static int
_GroupsLoad(FILE * fs)610 _GroupsLoad(FILE * fs)
611 {
612    char                s[1024];
613    Group              *g = NULL;
614 
615    while (fgets(s, sizeof(s), fs))
616      {
617 	char                ss[128];
618 	int                 ii;
619 
620 	if (strlen(s) > 0)
621 	   s[strlen(s) - 1] = 0;
622 	ii = 0;
623 	sscanf(s, "%100s %d", ss, &ii);
624 
625 	if (!strcmp(ss, "NEW:"))
626 	  {
627 	     g = _GroupCreate(ii, 0);
628 	     continue;
629 	  }
630 	if (!g)
631 	   continue;
632 
633 	if (!strcmp(ss, "ICONIFY:"))
634 	  {
635 	     g->cfg.iconify = ii;
636 	  }
637 	else if (!strcmp(ss, "KILL:"))
638 	  {
639 	     g->cfg.kill = ii;
640 	  }
641 	else if (!strcmp(ss, "MOVE:"))
642 	  {
643 	     g->cfg.move = ii;
644 	  }
645 	else if (!strcmp(ss, "RAISE:"))
646 	  {
647 	     g->cfg.raise = ii;
648 	  }
649 	else if (!strcmp(ss, "SET_BORDER:"))
650 	  {
651 	     g->cfg.set_border = ii;
652 	  }
653 	else if (!strcmp(ss, "STICK:"))
654 	  {
655 	     g->cfg.stick = ii;
656 	  }
657 	else if (!strcmp(ss, "SHADE:"))
658 	  {
659 	     g->cfg.shade = ii;
660 	  }
661      }
662 
663    return 0;
664 }
665 
666 void
GroupsLoad(void)667 GroupsLoad(void)
668 {
669    char                s[4096];
670 
671    Dprintf("%s\n", __func__);
672 
673    Esnprintf(s, sizeof(s), "%s.groups", EGetSavePrefix());
674 
675    ConfigFileLoad(s, NULL, _GroupsLoad, 0);
676 }
677 
678 #if ENABLE_DIALOGS
679 
680 #define GROUP_OP_ADD	1
681 #define GROUP_OP_DEL	2
682 #define GROUP_OP_BREAK	3
683 
684 typedef struct {
685    EWin               *ewin;
686    int                 action;
687    const char         *message;
688    Group             **groups;
689    int                 group_num;
690    int                 cur_grp;	/* Current  group */
691    int                 prv_grp;	/* Previous group */
692 } GroupSelDlgData;
693 
694 static void
_DlgApplyGroupChoose(Dialog * d,int val __UNUSED__,void * data __UNUSED__)695 _DlgApplyGroupChoose(Dialog * d, int val __UNUSED__, void *data __UNUSED__)
696 {
697    GroupSelDlgData    *dd = DLG_DATA_GET(d, GroupSelDlgData);
698 
699    if (!dd->groups)
700       return;
701 
702    switch (dd->action)
703      {
704      case GROUP_OP_ADD:
705 	_GroupEwinAdd(dd->groups[dd->cur_grp], dd->ewin, 1);
706 	break;
707      case GROUP_OP_DEL:
708 	_GroupEwinRemove(dd->groups[dd->cur_grp], dd->ewin, 1);
709 	break;
710      case GROUP_OP_BREAK:
711 	_GroupDelete(dd->groups[dd->cur_grp]);
712 	break;
713      default:
714 	break;
715      }
716 }
717 
718 static void
_DlgExitGroupChoose(Dialog * d)719 _DlgExitGroupChoose(Dialog * d)
720 {
721    GroupSelDlgData    *dd = DLG_DATA_GET(d, GroupSelDlgData);
722 
723    if (!dd->groups)
724       return;
725    _EwinGroupsShowHide(dd->ewin, dd->cur_grp, SET_OFF);
726    Efree(dd->groups);
727 }
728 
729 static void
_DlgSelectCbGroupChoose(Dialog * d,int val,void * data __UNUSED__)730 _DlgSelectCbGroupChoose(Dialog * d, int val, void *data __UNUSED__)
731 {
732    GroupSelDlgData    *dd = DLG_DATA_GET(d, GroupSelDlgData);
733 
734    /* val is equal to dd->cur_grp */
735    _EwinGroupsShowHide(dd->ewin, dd->prv_grp, SET_OFF);
736    _EwinGroupsShowHide(dd->ewin, val, SET_ON);
737    dd->prv_grp = val;
738 }
739 
740 static void
_DlgFillGroupChoose(Dialog * d,DItem * table,void * data)741 _DlgFillGroupChoose(Dialog * d, DItem * table, void *data)
742 {
743    GroupSelDlgData    *dd = DLG_DATA_GET(d, GroupSelDlgData);
744    DItem              *di, *radio;
745    int                 i, num_groups;
746    char              **group_member_strings;
747 
748    *dd = *(GroupSelDlgData *) data;
749 
750    DialogItemTableSetOptions(table, 2, 0, 0, 0);
751 
752    di = DialogAddItem(table, DITEM_TEXT);
753    DialogItemSetColSpan(di, 2);
754    DialogItemSetAlign(di, 0, 512);
755    DialogItemSetText(di, dd->message);
756 
757    num_groups = dd->group_num;
758    group_member_strings = _GrouplistMemberNames(dd->groups, num_groups);
759    if (!group_member_strings)
760       return;			/* Silence clang - It should not be possible to go here */
761 
762    radio = NULL;		/* Avoid warning */
763    for (i = 0; i < num_groups; i++)
764      {
765 	di = DialogAddItem(table, DITEM_RADIOBUTTON);
766 	if (i == 0)
767 	   radio = di;
768 	DialogItemSetColSpan(di, 2);
769 	DialogItemSetCallback(di, _DlgSelectCbGroupChoose, i, NULL);
770 	DialogItemSetText(di, group_member_strings[i]);
771 	DialogItemRadioButtonSetFirst(di, radio);
772 	DialogItemRadioButtonGroupSetVal(di, i);
773      }
774    DialogItemRadioButtonGroupSetValPtr(radio, &dd->cur_grp);
775 
776    StrlistFree(group_member_strings, num_groups);
777 }
778 
779 static const DialogDef DlgGroupChoose = {
780    "GROUP_SELECTION",
781    NULL, N_("Window Group Selection"),
782    sizeof(GroupSelDlgData),
783    SOUND_SETTINGS_GROUP,
784    "pix/group.png",
785    N_("Enlightenment Window Group\n" "Selection Dialog"),
786    _DlgFillGroupChoose,
787    DLG_OC, _DlgApplyGroupChoose, _DlgExitGroupChoose
788 };
789 
790 static void
_EwinGroupChooseDialog(EWin * ewin,int action)791 _EwinGroupChooseDialog(EWin * ewin, int action)
792 {
793    int                 group_sel;
794    GroupSelDlgData     gsdd, *dd = &gsdd;
795 
796    if (!ewin)
797       return;
798    dd->ewin = ewin;
799 
800    dd->action = action;
801    dd->cur_grp = dd->prv_grp = 0;
802 
803    switch (action)
804      {
805      default:
806 	return;
807      case GROUP_OP_ADD:
808 	dd->message = _("Pick the group the window will belong to:");
809 	group_sel = GROUP_SELECT_ALL_EXCEPT_EWIN;
810 	break;
811      case GROUP_OP_DEL:
812 	dd->message = _("Select the group to remove the window from:");
813 	group_sel = GROUP_SELECT_EWIN_ONLY;
814 	break;
815      case GROUP_OP_BREAK:
816 	dd->message = _("Select the group to break:");
817 	group_sel = GROUP_SELECT_EWIN_ONLY;
818 	break;
819      }
820 
821    dd->groups = _EwinListGroups(dd->ewin, group_sel, &dd->group_num);
822 
823    if (!dd->groups)
824      {
825 	if (action == GROUP_OP_BREAK || action == GROUP_OP_DEL)
826 	  {
827 	     DialogOK(_("Window Group Error"),
828 		      _
829 		      ("This window currently does not belong to any groups.\n"
830 		       "You can only destroy groups or remove windows from groups\n"
831 		       "through a window that actually belongs to at least one group."));
832 	     return;
833 	  }
834 
835 	if (group_sel == GROUP_SELECT_ALL_EXCEPT_EWIN)
836 	  {
837 	     DialogOK(_("Window Group Error"),
838 		      _("Currently, no groups exist or this window\n"
839 			"already belongs to all existing groups.\n"
840 			"You have to start other groups first."));
841 	     return;
842 	  }
843 
844 	DialogOK(_("Window Group Error"),
845 		 _
846 		 ("Currently, no groups exist. You have to start a group first."));
847 	return;
848      }
849 
850    _EwinGroupsShowHide(dd->ewin, 0, SET_ON);
851 
852    DialogShowSimple(&DlgGroupChoose, dd);
853 }
854 
855 typedef struct {
856    EWin               *ewin;
857    GroupConfig         cfg;	/* Dialog data for current group */
858    GroupConfig        *cfgs;	/* Work copy of ewin group cfgs */
859    int                 ngrp;
860    int                 cur_grp;	/* Current  group */
861    int                 prv_grp;	/* Previous group */
862 } EwinGroupDlgData;
863 
864 static void
_DlgApplyGroups(Dialog * d,int val __UNUSED__,void * data __UNUSED__)865 _DlgApplyGroups(Dialog * d, int val __UNUSED__, void *data __UNUSED__)
866 {
867    EwinGroupDlgData   *dd = DLG_DATA_GET(d, EwinGroupDlgData);
868    EWin               *ewin;
869    int                 i;
870 
871    /* Check ewin */
872    ewin = EwinFindByPtr(dd->ewin);
873    if (ewin && ewin->num_groups != dd->ngrp)
874       ewin = NULL;
875 
876    if (ewin)
877      {
878 	dd->cfgs[dd->cur_grp] = dd->cfg;
879 	for (i = 0; i < ewin->num_groups; i++)
880 	   ewin->groups[i]->cfg = dd->cfgs[i];
881      }
882 
883    _GroupsSave();
884 }
885 
886 static void
_DlgExitGroups(Dialog * d)887 _DlgExitGroups(Dialog * d)
888 {
889    EwinGroupDlgData   *dd = DLG_DATA_GET(d, EwinGroupDlgData);
890    EWin               *ewin;
891 
892    ewin = EwinFindByPtr(dd->ewin);
893    _EwinGroupsShowHide(ewin, dd->cur_grp, SET_OFF);
894 
895    Efree(dd->cfgs);
896 }
897 
898 static void
_DlgSelectCbGroups(Dialog * d,int val,void * data __UNUSED__)899 _DlgSelectCbGroups(Dialog * d, int val, void *data __UNUSED__)
900 {
901    EwinGroupDlgData   *dd = DLG_DATA_GET(d, EwinGroupDlgData);
902 
903    /* val is equal to dd->cur_grp */
904    dd->cfgs[dd->prv_grp] = dd->cfg;
905    dd->cfg = dd->cfgs[val];
906    DialogRedraw(d);
907    _EwinGroupsShowHide(dd->ewin, dd->prv_grp, SET_OFF);
908    _EwinGroupsShowHide(dd->ewin, val, SET_ON);
909    dd->prv_grp = val;
910 }
911 
912 static void
_DlgFillGroups(Dialog * d,DItem * table,void * data)913 _DlgFillGroups(Dialog * d, DItem * table, void *data)
914 {
915    EwinGroupDlgData   *dd = DLG_DATA_GET(d, EwinGroupDlgData);
916    EWin               *ewin = (EWin *) data;
917    DItem              *radio, *di;
918    int                 i;
919    char              **group_member_strings;
920 
921    dd->ewin = ewin;
922    dd->cfgs = EMALLOC(GroupConfig, ewin->num_groups);
923    dd->ngrp = ewin->num_groups;
924    dd->cur_grp = dd->prv_grp = 0;
925    for (i = 0; i < ewin->num_groups; i++)
926       dd->cfgs[i] = ewin->groups[i]->cfg;
927    if (ewin->num_groups > 0)	/* Avoid compiler warning */
928       dd->cfg = dd->cfgs[0];
929 
930    _EwinGroupsShowHide(ewin, 0, SET_ON);
931 
932    DialogItemTableSetOptions(table, 2, 0, 0, 0);
933 
934    di = DialogAddItem(table, DITEM_TEXT);
935    DialogItemSetColSpan(di, 2);
936    DialogItemSetAlign(di, 0, 512);
937    DialogItemSetText(di, _("Pick the group to configure:"));
938 
939    group_member_strings = _GrouplistMemberNames(ewin->groups, ewin->num_groups);
940    if (!group_member_strings)
941       return;			/* Silence clang - It should not be possible to go here */
942 
943    radio = NULL;		/* Avoid warning */
944    for (i = 0; i < ewin->num_groups; i++)
945      {
946 	di = DialogAddItem(table, DITEM_RADIOBUTTON);
947 	if (i == 0)
948 	   radio = di;
949 	DialogItemSetColSpan(di, 2);
950 	DialogItemSetCallback(di, _DlgSelectCbGroups, i, d);
951 	DialogItemSetText(di, group_member_strings[i]);
952 	DialogItemRadioButtonSetFirst(di, radio);
953 	DialogItemRadioButtonGroupSetVal(di, i);
954      }
955    DialogItemRadioButtonGroupSetValPtr(radio, &dd->cur_grp);
956 
957    StrlistFree(group_member_strings, ewin->num_groups);
958 
959    di = DialogAddItem(table, DITEM_SEPARATOR);
960    DialogItemSetColSpan(di, 2);
961 
962    di = DialogAddItem(table, DITEM_TEXT);
963    DialogItemSetColSpan(di, 2);
964    DialogItemSetAlign(di, 0, 512);
965    DialogItemSetText(di, _("The following actions are\n"
966 			   "applied to all group members:"));
967 
968    di = DialogAddItem(table, DITEM_CHECKBUTTON);
969    DialogItemSetColSpan(di, 2);
970    DialogItemSetText(di, _("Changing Border Style"));
971    DialogItemCheckButtonSetPtr(di, &(dd->cfg.set_border));
972 
973    di = DialogAddItem(table, DITEM_CHECKBUTTON);
974    DialogItemSetColSpan(di, 2);
975    DialogItemSetText(di, _("Iconifying"));
976    DialogItemCheckButtonSetPtr(di, &(dd->cfg.iconify));
977 
978    di = DialogAddItem(table, DITEM_CHECKBUTTON);
979    DialogItemSetColSpan(di, 2);
980    DialogItemSetText(di, _("Killing"));
981    DialogItemCheckButtonSetPtr(di, &(dd->cfg.kill));
982 
983    di = DialogAddItem(table, DITEM_CHECKBUTTON);
984    DialogItemSetColSpan(di, 2);
985    DialogItemSetText(di, _("Moving"));
986    DialogItemCheckButtonSetPtr(di, &(dd->cfg.move));
987 
988    di = DialogAddItem(table, DITEM_CHECKBUTTON);
989    DialogItemSetColSpan(di, 2);
990    DialogItemSetText(di, _("Raising/Lowering"));
991    DialogItemCheckButtonSetPtr(di, &(dd->cfg.raise));
992 
993    di = DialogAddItem(table, DITEM_CHECKBUTTON);
994    DialogItemSetColSpan(di, 2);
995    DialogItemSetText(di, _("Sticking"));
996    DialogItemCheckButtonSetPtr(di, &(dd->cfg.stick));
997 
998    di = DialogAddItem(table, DITEM_CHECKBUTTON);
999    DialogItemSetColSpan(di, 2);
1000    DialogItemSetText(di, _("Shading"));
1001    DialogItemCheckButtonSetPtr(di, &(dd->cfg.shade));
1002 }
1003 
1004 static const DialogDef DlgGroups = {
1005    "CONFIGURE_GROUP",
1006    NULL, N_("Window Group Settings"),
1007    sizeof(EwinGroupDlgData),
1008    SOUND_SETTINGS_GROUP,
1009    "pix/group.png",
1010    N_("Enlightenment Window Group\n" "Settings Dialog"),
1011    _DlgFillGroups,
1012    DLG_OAC, _DlgApplyGroups, _DlgExitGroups
1013 };
1014 
1015 static void
_EwinGroupsConfig(EWin * ewin)1016 _EwinGroupsConfig(EWin * ewin)
1017 {
1018    if (!ewin)
1019       return;
1020 
1021    if (ewin->num_groups == 0)
1022      {
1023 	DialogOK(_("Window Group Error"),
1024 		 _("This window currently does not belong to any groups."));
1025 	return;
1026      }
1027 
1028    DialogShowSimple(&DlgGroups, ewin);
1029 }
1030 
1031 typedef struct {
1032    GroupConfig         group_cfg;
1033    char                group_swap;
1034 } GroupCfgDlgData;
1035 
1036 static void
_DlgApplyGroupDefaults(Dialog * d,int val __UNUSED__,void * data __UNUSED__)1037 _DlgApplyGroupDefaults(Dialog * d, int val __UNUSED__, void *data __UNUSED__)
1038 {
1039    GroupCfgDlgData    *dd = DLG_DATA_GET(d, GroupCfgDlgData);
1040 
1041    Conf_groups.dflt = dd->group_cfg;
1042    Conf_groups.swapmove = dd->group_swap;
1043 
1044    autosave();
1045 }
1046 
1047 static void
_DlgFillGroupDefaults(Dialog * d,DItem * table,void * data __UNUSED__)1048 _DlgFillGroupDefaults(Dialog * d, DItem * table, void *data __UNUSED__)
1049 {
1050    GroupCfgDlgData    *dd = DLG_DATA_GET(d, GroupCfgDlgData);
1051    DItem              *di;
1052 
1053    dd->group_cfg = Conf_groups.dflt;
1054    dd->group_swap = Conf_groups.swapmove;
1055 
1056    DialogItemTableSetOptions(table, 2, 0, 0, 0);
1057 
1058    di = DialogAddItem(table, DITEM_TEXT);
1059    DialogItemSetColSpan(di, 2);
1060    DialogItemSetAlign(di, 0, 512);
1061    DialogItemSetText(di, _("Per-group settings:"));
1062 
1063    di = DialogAddItem(table, DITEM_SEPARATOR);
1064    DialogItemSetColSpan(di, 2);
1065 
1066    di = DialogAddItem(table, DITEM_CHECKBUTTON);
1067    DialogItemSetColSpan(di, 2);
1068    DialogItemSetText(di, _("Changing Border Style"));
1069    DialogItemCheckButtonSetPtr(di, &dd->group_cfg.set_border);
1070 
1071    di = DialogAddItem(table, DITEM_CHECKBUTTON);
1072    DialogItemSetColSpan(di, 2);
1073    DialogItemSetText(di, _("Iconifying"));
1074    DialogItemCheckButtonSetPtr(di, &dd->group_cfg.iconify);
1075 
1076    di = DialogAddItem(table, DITEM_CHECKBUTTON);
1077    DialogItemSetColSpan(di, 2);
1078    DialogItemSetText(di, _("Killing"));
1079    DialogItemCheckButtonSetPtr(di, &dd->group_cfg.kill);
1080 
1081    di = DialogAddItem(table, DITEM_CHECKBUTTON);
1082    DialogItemSetColSpan(di, 2);
1083    DialogItemSetText(di, _("Moving"));
1084    DialogItemCheckButtonSetPtr(di, &dd->group_cfg.move);
1085 
1086    di = DialogAddItem(table, DITEM_CHECKBUTTON);
1087    DialogItemSetColSpan(di, 2);
1088    DialogItemSetText(di, _("Raising/Lowering"));
1089    DialogItemCheckButtonSetPtr(di, &dd->group_cfg.raise);
1090 
1091    di = DialogAddItem(table, DITEM_CHECKBUTTON);
1092    DialogItemSetColSpan(di, 2);
1093    DialogItemSetText(di, _("Sticking"));
1094    DialogItemCheckButtonSetPtr(di, &dd->group_cfg.stick);
1095 
1096    di = DialogAddItem(table, DITEM_CHECKBUTTON);
1097    DialogItemSetColSpan(di, 2);
1098    DialogItemSetText(di, _("Shading"));
1099    DialogItemCheckButtonSetPtr(di, &dd->group_cfg.shade);
1100 
1101    di = DialogAddItem(table, DITEM_SEPARATOR);
1102    DialogItemSetColSpan(di, 2);
1103 
1104    di = DialogAddItem(table, DITEM_TEXT);
1105    DialogItemSetColSpan(di, 2);
1106    DialogItemSetAlign(di, 0, 512);
1107    DialogItemSetText(di, _("Global settings:"));
1108 
1109    di = DialogAddItem(table, DITEM_CHECKBUTTON);
1110    DialogItemSetColSpan(di, 2);
1111    DialogItemSetText(di, _("Swap Window Locations"));
1112    DialogItemCheckButtonSetPtr(di, &dd->group_swap);
1113 }
1114 
1115 const DialogDef     DlgGroupDefaults = {
1116    "CONFIGURE_DEFAULT_GROUP_CONTROL",
1117    N_("Groups"), N_("Default Group Control Settings"),
1118    sizeof(GroupCfgDlgData),
1119    SOUND_SETTINGS_GROUP,
1120    "pix/group.png",
1121    N_("Enlightenment Default\n" "Group Control Settings Dialog"),
1122    _DlgFillGroupDefaults,
1123    DLG_OAC, _DlgApplyGroupDefaults, NULL
1124 };
1125 #endif /* ENABLE_DIALOGS */
1126 
1127 /*
1128  * Groups module
1129  */
1130 
1131 static void
_GroupShowEwins(Group * g)1132 _GroupShowEwins(Group * g)
1133 {
1134    int                 i;
1135 
1136    IpcPrintf(" gid=%8d: ", g->index);
1137    for (i = 0; i < g->num_members; i++)
1138       IpcPrintf(" %s", EoGetName(g->members[i]));
1139    IpcPrintf("\n");
1140 }
1141 
1142 static void
_GroupsShowGroups(void)1143 _GroupsShowGroups(void)
1144 {
1145    Group              *g;
1146 
1147    IpcPrintf("%s:\n", __func__);
1148    LIST_FOR_EACH(Group, &group_list, g)
1149    {
1150       _GroupShowEwins(g);
1151    }
1152 }
1153 
1154 static void
_GroupsShow(void)1155 _GroupsShow(void)
1156 {
1157    _GroupsShowGroups();
1158 }
1159 
1160 static void
_GroupShow(Group * g)1161 _GroupShow(Group * g)
1162 {
1163    int                 j;
1164 
1165    for (j = 0; j < g->num_members; j++)
1166       IpcPrintf("%d: %s\n", g->index, EwinGetIcccmName(g->members[j]));
1167 
1168    IpcPrintf("        index: %d\n" "  num_members: %d\n"
1169 	     "      iconify: %d\n" "         kill: %d\n"
1170 	     "         move: %d\n" "        raise: %d\n"
1171 	     "   set_border: %d\n" "        stick: %d\n"
1172 	     "        shade: %d\n",
1173 	     g->index, g->num_members,
1174 	     g->cfg.iconify, g->cfg.kill,
1175 	     g->cfg.move, g->cfg.raise,
1176 	     g->cfg.set_border, g->cfg.stick, g->cfg.shade);
1177 }
1178 
1179 static void
IPC_GroupOps(const char * params)1180 IPC_GroupOps(const char *params)
1181 {
1182    Group              *group;
1183    char                windowid[128];
1184    char                operation[128];
1185    char                groupid[128];
1186    unsigned int        win;
1187    EWin               *ewin;
1188 
1189    if (!params)
1190      {
1191 	IpcPrintf("Error: no window specified\n");
1192 	return;
1193      }
1194 
1195    windowid[0] = operation[0] = groupid[0] = '\0';
1196    sscanf(params, "%100s %100s %100s", windowid, operation, groupid);
1197    win = 0;
1198    sscanf(windowid, "%x", &win);
1199 
1200    if (!operation[0])
1201      {
1202 	IpcPrintf("Error: no operation specified\n");
1203 	return;
1204      }
1205 
1206    ewin = EwinFindByExpr(windowid);
1207    if (!ewin)
1208      {
1209 	IpcPrintf("Error: no such window: %s\n", windowid);
1210 	return;
1211      }
1212 
1213 #if ENABLE_DIALOGS
1214    if (!strcmp(operation, "cfg"))
1215      {
1216 	_EwinGroupsConfig(ewin);
1217      }
1218    else
1219 #endif
1220    if (!strcmp(operation, "start"))
1221      {
1222 	group = _GroupCreate(-1, 1);
1223 	Mode_groups.current = group;
1224 	_GroupEwinAdd(group, ewin, 1);
1225      }
1226    else if (!strcmp(operation, "add"))
1227      {
1228 	group = _GroupFind2(groupid);
1229 #if ENABLE_DIALOGS
1230 	if (group == GROUP_CHOOSE)
1231 	   _EwinGroupChooseDialog(ewin, GROUP_OP_ADD);
1232 	else
1233 	   _GroupEwinAdd(group, ewin, 1);
1234 #endif
1235      }
1236    else if (!strcmp(operation, "del"))
1237      {
1238 	group = _GroupFind2(groupid);
1239 #if ENABLE_DIALOGS
1240 	if (group == GROUP_CHOOSE)
1241 	   _EwinGroupChooseDialog(ewin, GROUP_OP_DEL);
1242 	else
1243 #endif
1244 	   _GroupEwinRemove(group, ewin, 1);
1245      }
1246    else if (!strcmp(operation, "break"))
1247      {
1248 	group = _GroupFind2(groupid);
1249 #if ENABLE_DIALOGS
1250 	if (group == GROUP_CHOOSE)
1251 	   _EwinGroupChooseDialog(ewin, GROUP_OP_BREAK);
1252 	else
1253 #endif
1254 	   _GroupDelete(group);
1255      }
1256    else if (!strcmp(operation, "showhide"))
1257      {
1258 	_EwinGroupsShowHide(ewin, -1, SET_TOGGLE);
1259      }
1260    else
1261      {
1262 	IpcPrintf("Error: no such operation: %s\n", operation);
1263 	return;
1264      }
1265 }
1266 
1267 static void
IPC_Group(const char * params)1268 IPC_Group(const char *params)
1269 {
1270    char                groupid[128];
1271    char                operation[128];
1272    char                param1[128];
1273    Group              *group;
1274    int                 onoff;
1275 
1276    if (!params)
1277      {
1278 	IpcPrintf("Error: no operation specified\n");
1279 	return;
1280      }
1281 
1282    groupid[0] = operation[0] = param1[0] = '\0';
1283    sscanf(params, "%100s %100s %100s", groupid, operation, param1);
1284 
1285    if (!strcmp(groupid, "info"))
1286      {
1287 	IpcPrintf("Number of groups: %d\n", LIST_GET_COUNT(&group_list));
1288 	LIST_FOR_EACH(Group, &group_list, group) _GroupShow(group);
1289 	return;
1290      }
1291    else if (!strcmp(groupid, "list"))
1292      {
1293 	_GroupsShow();
1294 	return;
1295      }
1296 
1297    if (!operation[0])
1298      {
1299 	IpcPrintf("Error: no operation specified\n");
1300 	return;
1301      }
1302 
1303    group = _GroupFind2(groupid);
1304    if (!group)
1305      {
1306 	IpcPrintf("Error: no such group: %s\n", groupid);
1307 	return;
1308      }
1309 
1310    if (!strcmp(operation, "info"))
1311      {
1312 	_GroupShow(group);
1313 	return;
1314      }
1315    else if (!strcmp(operation, "del"))
1316      {
1317 	_GroupDelete(group);
1318 	return;
1319      }
1320 
1321    if (!param1[0])
1322      {
1323 	IpcPrintf("Error: no mode specified\n");
1324 	return;
1325      }
1326 
1327    onoff = -1;
1328    if (!strcmp(param1, "on"))
1329       onoff = 1;
1330    else if (!strcmp(param1, "off"))
1331       onoff = 0;
1332 
1333    if (onoff == -1 && strcmp(param1, "?"))
1334      {
1335 	IpcPrintf("Error: unknown mode specified\n");
1336      }
1337    else if (!strcmp(operation, "iconify"))
1338      {
1339 	if (onoff >= 0)
1340 	   group->cfg.iconify = onoff;
1341 	else
1342 	   onoff = group->cfg.iconify;
1343      }
1344    else if (!strcmp(operation, "kill"))
1345      {
1346 	if (onoff >= 0)
1347 	   group->cfg.kill = onoff;
1348 	else
1349 	   onoff = group->cfg.kill;
1350      }
1351    else if (!strcmp(operation, "move"))
1352      {
1353 	if (onoff >= 0)
1354 	   group->cfg.move = onoff;
1355 	else
1356 	   onoff = group->cfg.move;
1357      }
1358    else if (!strcmp(operation, "raise"))
1359      {
1360 	if (onoff >= 0)
1361 	   group->cfg.raise = onoff;
1362 	else
1363 	   onoff = group->cfg.raise;
1364      }
1365    else if (!strcmp(operation, "set_border"))
1366      {
1367 	if (onoff >= 0)
1368 	   group->cfg.set_border = onoff;
1369 	else
1370 	   onoff = group->cfg.set_border;
1371      }
1372    else if (!strcmp(operation, "stick"))
1373      {
1374 	if (onoff >= 0)
1375 	   group->cfg.stick = onoff;
1376 	else
1377 	   onoff = group->cfg.stick;
1378      }
1379    else if (!strcmp(operation, "shade"))
1380      {
1381 	if (onoff >= 0)
1382 	   group->cfg.shade = onoff;
1383 	else
1384 	   onoff = group->cfg.shade;
1385      }
1386    else
1387      {
1388 	IpcPrintf("Error: no such operation: %s\n", operation);
1389 	onoff = -1;
1390      }
1391 
1392    if (onoff == 1)
1393       IpcPrintf("%s: on\n", operation);
1394    else if (onoff == 0)
1395       IpcPrintf("%s: off\n", operation);
1396 
1397    if (onoff >= 0)
1398       _GroupsSave();
1399 }
1400 
1401 static const IpcItem GroupsIpcArray[] = {
1402    {
1403     IPC_GroupOps,
1404     "group_op", "gop",
1405     "Group operations",
1406     "use \"group_op <windowid> <property> [<value>]\" to perform "
1407     "group operations on a window.\n"
1408     "Available group_op commands are:\n"
1409     "  group_op <windowid> cfg\n"
1410     "  group_op <windowid> start\n"
1411     "  group_op <windowid> add [<groupid>]\n"
1412     "  group_op <windowid> del [<groupid>]\n"
1413     "  group_op <windowid> break [<groupid>]\n"
1414     "  group_op <windowid> showhide\n"}
1415    ,
1416    {
1417     IPC_Group,
1418     "group", "grp",
1419     "Group commands",
1420     "use \"group <groupid> <property> <value>\" to set group properties.\n"
1421     "Available group commands are:\n"
1422     "  group info\n"
1423     "  group <groupid> info\n"
1424     "  group <groupid> del\n"
1425     "  group <groupid> iconify <on/off/?>\n"
1426     "  group <groupid> kill <on/off/?>\n"
1427     "  group <groupid> move <on/off/?>\n"
1428     "  group <groupid> raise <on/off/?>\n"
1429     "  group <groupid> set_border <on/off/?>\n"
1430     "  group <groupid> stick <on/off/?>\n"
1431     "  group <groupid> shade <on/off/?>\n"}
1432    ,
1433 };
1434 
1435 /*
1436  * Configuration items
1437  */
1438 static const CfgItem GroupsCfgItems[] = {
1439    CFG_ITEM_BOOL(Conf_groups, dflt.iconify, 1),
1440    CFG_ITEM_BOOL(Conf_groups, dflt.kill, 0),
1441    CFG_ITEM_BOOL(Conf_groups, dflt.move, 1),
1442    CFG_ITEM_BOOL(Conf_groups, dflt.raise, 0),
1443    CFG_ITEM_BOOL(Conf_groups, dflt.set_border, 1),
1444    CFG_ITEM_BOOL(Conf_groups, dflt.stick, 1),
1445    CFG_ITEM_BOOL(Conf_groups, dflt.shade, 1),
1446    CFG_ITEM_BOOL(Conf_groups, swapmove, 1),
1447 };
1448 
1449 extern const EModule ModGroups;
1450 
1451 const EModule       ModGroups = {
1452    "groups", "grp",
1453    NULL,
1454    MOD_ITEMS(GroupsIpcArray),
1455    MOD_ITEMS(GroupsCfgItems)
1456 };
1457