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