e16/src/windowmatch.c

992 lines
23 KiB
C

/*
* Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
* Copyright (C) 2005-2021 Kim Woelders
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies of the Software, its documentation and marketing & publicity
* materials, and acknowledgment shall be given in the documentation, materials
* and software packages that this Software was used.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "E.h"
#include "borders.h"
#include "conf.h"
#include "desktops.h"
#include "emodule.h"
#include "ewins.h"
#include "ewin-ops.h"
#include "list.h"
#include "windowmatch.h"
typedef struct _windowmatch WindowMatch;
struct _windowmatch {
dlist_t list;
char *name;
/* Match criteria */
char match;
char op;
char prop;
char qual;
char *value;
int width_min, width_max;
int height_min, height_max;
/* Match actions */
char *args;
Border *border;
};
#define MATCH_TYPE_TITLE 1
#define MATCH_TYPE_WM_NAME 2
#define MATCH_TYPE_WM_CLASS 3
#define MATCH_TYPE_SIZE 4
#define MATCH_TYPE_SIZE_H 5
#define MATCH_TYPE_SIZE_V 6
#define MATCH_TYPE_PROP 7
#define MATCH_PROP_TRANSIENT 1
#define MATCH_PROP_SHAPED 2
#define MATCH_PROP_FIXEDSIZE 3
#define MATCH_PROP_FIXEDSIZE_H 4
#define MATCH_PROP_FIXEDSIZE_V 5
#define MATCH_OP_BORDER 1
#define MATCH_OP_ICON 2
#define MATCH_OP_WINOP 3
static int WindowMatchEobjOpsParse(EObj * eo, const char *ops);
static LIST_HEAD(wm_list);
static const char *const MatchType[] = {
NULL, "Title", "Name", "Class", "Size", "Width", "Height", "Prop", NULL
};
static const char *const MatchProp[] = {
NULL, "Transient", "Shaped", "FixedSize", "FixedWidth", "FixedHeight", NULL
};
static const char *const MatchOp[] = {
NULL, "Border", "Icon", "Winop", NULL
};
static int
MatchFind(const char *const *list, const char *str)
{
int i;
for (i = 1; list[i]; i++)
if (!strcmp(str, list[i]))
return i;
return 0;
}
static WindowMatch *
WindowMatchCreate(const char *name)
{
WindowMatch *b;
b = ECALLOC(WindowMatch, 1);
if (!b)
return NULL;
LIST_PREPEND(WindowMatch, &wm_list, b);
b->name = Estrdup(name);
b->width_max = 99999;
b->height_max = 99999;
return b;
}
static void
WindowMatchDestroy(WindowMatch *wm)
{
if (!wm)
return;
LIST_REMOVE(WindowMatch, &wm_list, wm);
Efree(wm->name);
Efree(wm->value);
Efree(wm->args);
Efree(wm);
}
int
WindowMatchConfigLoad(FILE *fs)
{
int err = 0;
WindowMatch *wm = 0;
char s[FILEPATH_LEN_MAX];
char s2[FILEPATH_LEN_MAX];
char *p2;
int i1;
while (GetLine(s, sizeof(s), fs))
{
i1 = ConfigParseline1(s, s2, &p2, NULL);
switch (i1)
{
case CONFIG_WINDOWMATCH:
err = -1;
break;
case CONFIG_CLASSNAME:
wm = WindowMatchCreate(s2);
break;
case CONFIG_CLOSE:
if (wm)
{
if (!wm->match || !wm->op)
{
Eprintf("%s: Skip invalid '%s'\n", __func__, wm->name);
WindowMatchDestroy(wm);
}
wm = NULL;
err = 0;
}
goto done;
case WINDOWMATCH_MATCHTITLE:
if (!wm)
break;
wm->match = MATCH_TYPE_TITLE;
wm->value = Estrdup(p2);
break;
case WINDOWMATCH_MATCHNAME:
if (!wm)
break;
wm->match = MATCH_TYPE_WM_NAME;
wm->value = Estrdup(p2);
break;
case WINDOWMATCH_MATCHCLASS:
if (!wm)
break;
wm->match = MATCH_TYPE_WM_CLASS;
wm->value = Estrdup(p2);
break;
case WINDOWMATCH_WIDTH:
if (!wm)
break;
if (wm->match == MATCH_TYPE_SIZE_V)
wm->match = MATCH_TYPE_SIZE;
else
wm->match = MATCH_TYPE_SIZE_H;
sscanf(s, "%*s %u %u", &(wm->width_min), &(wm->width_max));
break;
case WINDOWMATCH_HEIGHT:
if (!wm)
break;
if (wm->match == MATCH_TYPE_SIZE_H)
wm->match = MATCH_TYPE_SIZE;
else
wm->match = MATCH_TYPE_SIZE_V;
sscanf(s, "%*s %u %u", &(wm->height_min), &(wm->height_max));
break;
case WINDOWMATCH_TRANSIENT:
if (!wm)
break;
wm->match = MATCH_TYPE_PROP;
wm->prop = MATCH_PROP_TRANSIENT;
wm->qual = !atoi(s2);
break;
case WINDOWMATCH_SHAPED:
if (!wm)
break;
wm->match = MATCH_TYPE_PROP;
wm->prop = MATCH_PROP_SHAPED;
wm->qual = !atoi(s2);
break;
case WINDOWMATCH_NO_RESIZE_H:
if (!wm)
break;
wm->match = MATCH_TYPE_PROP;
if (wm->prop == MATCH_PROP_FIXEDSIZE_V)
wm->prop = MATCH_PROP_FIXEDSIZE;
else
wm->prop = MATCH_PROP_FIXEDSIZE_H;
wm->qual = !atoi(s2);
break;
case WINDOWMATCH_NO_RESIZE_V:
if (!wm)
break;
wm->match = MATCH_TYPE_PROP;
if (wm->prop == MATCH_PROP_FIXEDSIZE_H)
wm->prop = MATCH_PROP_FIXEDSIZE;
else
wm->prop = MATCH_PROP_FIXEDSIZE_V;
wm->qual = !atoi(s2);
break;
case WINDOWMATCH_USEBORDER:
if (!wm)
break;
wm->border = BorderFind(s2);
if (!wm->border)
{
Eprintf("%s: No such border: '%s'\n", __func__, s2);
break;
}
wm->op = MATCH_OP_BORDER;
break;
case WINDOWMATCH_ICON:
if (!wm)
break;
wm->args = Estrdup(s2);
wm->op = MATCH_OP_ICON;
break;
case WINDOWMATCH_DESKTOP:
#if 0 /* This has not been active since at least 0.16.5 */
wm->desk = atoi(s2);
#endif
break;
case WINDOWMATCH_MAKESTICKY:
if (!wm)
break;
wm->args = Estrdupcat2(wm->args, ":", "stick");
wm->op = MATCH_OP_WINOP;
break;
default:
ConfigParseError("WindowMatch", s);
break;
}
}
done:
return err;
}
static void
WindowMatchDecode(const char *line)
{
char match[32], value[1024], op[32];
const char *args;
WindowMatch *wm = NULL;
int err, num, w1, w2, h1, h2;
match[0] = value[0] = op[0] = '\0';
num = sscanf(line, "%31s %1023s %31s %n", match, value, op, &w1);
if (num < 3)
return;
args = line + w1;
if (*args == '\0')
return;
err = 0;
num = MatchFind(MatchType, match);
if (num <= 0)
{
Eprintf("%s: Error (%s): %s\n", __func__, match, line);
err = 1;
goto done;
}
wm = WindowMatchCreate(NULL);
if (!wm)
return;
wm->match = num;
switch (wm->match)
{
case MATCH_TYPE_TITLE:
case MATCH_TYPE_WM_NAME:
case MATCH_TYPE_WM_CLASS:
wm->value = Estrdup(value);
break;
case MATCH_TYPE_SIZE:
num = sscanf(value, "%u-%ux%u-%u", &w1, &w2, &h1, &h2);
if (num < 4)
goto case_error;
wm->width_min = w1;
wm->width_max = w2;
wm->height_min = h1;
wm->height_max = h2;
break;
case MATCH_TYPE_SIZE_H:
num = sscanf(value, "%u-%u", &w1, &w2);
if (num < 2)
goto case_error;
wm->width_min = w1;
wm->width_max = w2;
break;
case MATCH_TYPE_SIZE_V:
num = sscanf(value, "%u-%u", &h1, &h2);
if (num < 2)
goto case_error;
wm->height_min = h1;
wm->height_max = h2;
break;
case MATCH_TYPE_PROP:
num = 0;
if (*value == '!')
{
wm->qual = 1;
num = 1;
}
wm->prop = MatchFind(MatchProp, value + num);
if (wm->prop <= 0)
goto case_error;
break;
case_error:
Eprintf("%s: Error (%s): %s\n", __func__, value, line);
err = 1;
goto done;
}
wm->op = MatchFind(MatchOp, op);
if (wm->op <= 0)
{
Eprintf("%s: Error (%s): %s\n", __func__, op, line);
err = 1;
goto done;
}
switch (wm->op)
{
case MATCH_OP_BORDER:
wm->border = BorderFind(args);
if (!wm->border)
{
Eprintf("%s: No such border: '%s'\n", __func__, args);
err = 1;
goto done;
}
break;
case MATCH_OP_ICON:
/* FIXME - Check if exists */
wm->args = Estrdup(args);
break;
case MATCH_OP_WINOP:
if (WindowMatchEobjOpsParse(NULL, args))
{
Eprintf("%s: Error (%s): %s\n", __func__, args, line);
err = 1;
goto done;
}
wm->args = Estrdup(args);
break;
}
done:
if (err)
{
if (wm)
WindowMatchDestroy(wm);
}
else
{
LIST_APPEND(WindowMatch, &wm_list,
LIST_REMOVE(WindowMatch, &wm_list, wm));
}
}
static char *
WindowMatchEncode(const WindowMatch *wm, char *buf, int len)
{
char s[1024];
const char *qual, *value, *args;
qual = " ";
switch (wm->match)
{
default:
value = wm->value;
break;
case MATCH_TYPE_SIZE:
value = s;
sprintf(s, "%u-%ux%u-%u", wm->width_min, wm->width_max,
wm->height_min, wm->height_max);
break;
case MATCH_TYPE_SIZE_H:
value = s;
sprintf(s, "%u-%u", wm->width_min, wm->width_max);
break;
case MATCH_TYPE_SIZE_V:
value = s;
sprintf(s, "%u-%u", wm->height_min, wm->height_max);
break;
case MATCH_TYPE_PROP:
qual = (wm->qual) ? "!" : " ";
value = MatchProp[(int)wm->prop];
break;
}
switch (wm->op)
{
default:
args = wm->args;
break;
case MATCH_OP_BORDER:
args = BorderGetName(wm->border);
break;
}
Esnprintf(buf, len, "%-8s %s%-16s %s %s", MatchType[(int)wm->match],
qual, value, MatchOp[(int)wm->op], args);
return buf;
}
static int
WindowMatchConfigLoad2(FILE *fs)
{
char s[FILEPATH_LEN_MAX], *ss;
int len;
for (;;)
{
ss = fgets(s, sizeof(s), fs);
if (!ss)
break;
len = strcspn(s, "#\r\n");
if (len <= 0)
continue;
s[len] = '\0';
WindowMatchDecode(s);
}
return 0;
}
static int
WindowMatchEwinTest(const WindowMatch *wm, const EWin *ewin)
{
int match;
match = 0;
switch (wm->match)
{
case MATCH_TYPE_TITLE:
return matchregexp(wm->value, EwinGetIcccmName(ewin));
case MATCH_TYPE_WM_NAME:
return matchregexp(wm->value, EwinGetIcccmCName(ewin));
case MATCH_TYPE_WM_CLASS:
return matchregexp(wm->value, EwinGetIcccmClass(ewin));
case MATCH_TYPE_SIZE:
match = (ewin->client.w >= wm->width_min &&
ewin->client.w <= wm->width_max &&
ewin->client.h >= wm->height_min &&
ewin->client.h <= wm->height_max);
break;
case MATCH_TYPE_SIZE_H:
match = (ewin->client.w >= wm->width_min &&
ewin->client.w <= wm->width_max);
break;
case MATCH_TYPE_SIZE_V:
match = (ewin->client.h >= wm->height_min &&
ewin->client.h <= wm->height_max);
break;
case MATCH_TYPE_PROP:
switch (wm->prop)
{
case MATCH_PROP_TRANSIENT:
match = EwinIsTransient(ewin);
break;
case MATCH_PROP_SHAPED:
match = ewin->state.shaped;
break;
case MATCH_PROP_FIXEDSIZE:
match = ewin->props.no_resize_h && ewin->props.no_resize_v;
break;
case MATCH_PROP_FIXEDSIZE_H:
match = ewin->props.no_resize_h;
break;
case MATCH_PROP_FIXEDSIZE_V:
match = ewin->props.no_resize_v;
break;
}
}
if (wm->qual)
match = !match;
return match;
}
#if USE_COMPOSITE
static int
WindowMatchEobjTest(const WindowMatch *wm, const EObj *eo)
{
int match;
match = 0;
switch (wm->match)
{
case MATCH_TYPE_TITLE:
return matchregexp(wm->value, EobjGetName(eo));
case MATCH_TYPE_WM_NAME:
return matchregexp(wm->value, EobjGetCName(eo));
case MATCH_TYPE_WM_CLASS:
return matchregexp(wm->value, EobjGetClass(eo));
}
if (wm->qual)
match = !match;
return match;
}
#endif
typedef struct {
int type;
const EWin *ewin;
} wmatch_type_data;
static int
WindowMatchTypeMatch(const void *data, const void *match)
{
const WindowMatch *wm = (const WindowMatch *)data;
const wmatch_type_data *wmtd = (const wmatch_type_data *)match;
return !(wm->op == wmtd->type && WindowMatchEwinTest(wm, wmtd->ewin));
}
static WindowMatch *
WindowMatchType(const EWin *ewin, int type)
{
wmatch_type_data wmtd;
wmtd.type = type;
wmtd.ewin = ewin;
return LIST_FIND(WindowMatch, &wm_list, WindowMatchTypeMatch, &wmtd);
}
Border *
WindowMatchEwinBorder(const EWin *ewin)
{
WindowMatch *wm;
wm = WindowMatchType(ewin, MATCH_OP_BORDER);
#if 0
Eprintf("%s: %s %s\n", __func__, EwinGetTitle(ewin),
(wm) ? BorderGetName(wm->border) : "???");
#endif
if (wm)
return wm->border;
return NULL;
}
const char *
WindowMatchEwinIcon(const EWin *ewin)
{
WindowMatch *wm;
wm = WindowMatchType(ewin, MATCH_OP_ICON);
#if 0
Eprintf("%s: %s %s\n", __func__, EwinGetTitle(ewin),
(wm) ? wm->args : "???");
#endif
if (wm)
return wm->args;
return NULL;
}
static int
GetBoolean(const char *value)
{
/* We set off if "0" or "off", otherwise on */
if (!value)
return 1;
else if (!strcmp(value, "0") || !strcmp(value, "off"))
return 0;
return 1;
}
#define WINOP_SET_BOOL(item, val) item = GetBoolean(val)
static void
WindowMatchEwinOpsAction(EWin *ewin, int op, const char *args)
{
int a, b;
/* NB! This must only be used when a new client is being adopted */
switch (op)
{
default:
/* We should not get here */
return;
case EWIN_OP_BORDER:
EwinBorderSetInitially(ewin, args);
break;
case EWIN_OP_TITLE:
EFREE_DUP(EwinGetIcccmName(ewin), args);
break;
case EWIN_OP_ICONIFY:
WINOP_SET_BOOL(ewin->icccm.start_iconified, args);
break;
case EWIN_OP_SHADE:
WINOP_SET_BOOL(ewin->state.shaded, args);
break;
case EWIN_OP_STICK:
WINOP_SET_BOOL(ewin->o.sticky, args);
break;
case EWIN_OP_DESK:
EoSetDesk(ewin, DeskGetValid(atoi(args)));
break;
#if 0 /* Causes crash */
case EWIN_OP_AREA:
a = b = 0;
if (sscanf(args, "%u %u", &a, &b) < 2)
break;
EwinMoveToArea(ewin, a, b); /* FIXME - We should not move here */
break;
#endif
case EWIN_OP_MOVE:
a = ewin->client.x;
b = ewin->client.y;
if (sscanf(args, "%i %i", &a, &b) < 2)
break;
ewin->client.x = a;
ewin->client.y = b;
ewin->state.placed = 1;
break;
case EWIN_OP_SIZE:
a = ewin->client.w;
b = ewin->client.h;
if (sscanf(args, "%u %u", &a, &b) < 2)
break;
ewin->client.w = a;
ewin->client.h = b;
ewin->state.maximized_horz = ewin->state.maximized_vert = 0;
break;
case EWIN_OP_FULLSCREEN:
WINOP_SET_BOOL(ewin->state.fullscreen, args);
break;
case EWIN_OP_LAYER:
EoSetLayer(ewin, atoi(args));
break;
case EWIN_OP_OPACITY:
a = atoi(args);
ewin->props.opacity = OpacityFromPercent(OpacityFix(a, 0));
ewin->ewmh.opacity_update = 1; /* Set opacity on client window */
break;
case EWIN_OP_FOCUSED_OPACITY:
a = atoi(args);
ewin->props.focused_opacity = OpacityFromPercent(OpacityFix(a, 0));
break;
case EWIN_OP_SKIP_LISTS:
WINOP_SET_BOOL(ewin->props.skip_winlist, args);
ewin->props.skip_focuslist = ewin->props.skip_ext_task =
ewin->props.skip_winlist;
break;
case EWIN_OP_FOCUS_CLICK:
WINOP_SET_BOOL(ewin->props.focusclick, args);
break;
case EWIN_OP_NEVER_USE_AREA:
WINOP_SET_BOOL(ewin->props.never_use_area, args);
break;
case EWIN_OP_NO_BUTTON_GRABS:
WINOP_SET_BOOL(ewin->props.no_button_grabs, args);
break;
case EWIN_OP_AUTOSHADE:
WINOP_SET_BOOL(ewin->props.autoshade, args);
break;
case EWIN_OP_INH_APP_FOCUS:
WINOP_SET_BOOL(EwinInhGetApp(ewin, focus), args);
break;
case EWIN_OP_INH_APP_MOVE:
WINOP_SET_BOOL(EwinInhGetApp(ewin, move), args);
break;
case EWIN_OP_INH_APP_SIZE:
WINOP_SET_BOOL(EwinInhGetApp(ewin, size), args);
break;
case EWIN_OP_INH_USER_CLOSE:
WINOP_SET_BOOL(EwinInhGetUser(ewin, close), args);
break;
case EWIN_OP_INH_USER_MOVE:
WINOP_SET_BOOL(EwinInhGetUser(ewin, move), args);
break;
case EWIN_OP_INH_USER_SIZE:
WINOP_SET_BOOL(EwinInhGetUser(ewin, size), args);
break;
case EWIN_OP_INH_WM_FOCUS:
WINOP_SET_BOOL(EwinInhGetWM(ewin, focus), args);
break;
#if USE_COMPOSITE
case EWIN_OP_FADE:
WINOP_SET_BOOL(ewin->o.fade, args);
break;
case EWIN_OP_SHADOW:
WINOP_SET_BOOL(ewin->o.shadow, args);
break;
case EWIN_OP_NO_REDIRECT:
WINOP_SET_BOOL(ewin->o.noredir, args);
break;
case EWIN_OP_NO_ARGB:
WINOP_SET_BOOL(ewin->props.no_argb, args);
break;
#endif
}
}
#if USE_COMPOSITE
static void
WindowMatchEobjOpsAction(EObj *eo, int op, const char *args)
{
int a;
switch (op)
{
default:
/* We should not get here */
return;
case EWIN_OP_OPACITY:
a = atoi(args);
eo->opacity = OpacityFromPercent(OpacityFix(a, 100));
break;
case EWIN_OP_FADE:
WINOP_SET_BOOL(eo->fade, args);
break;
case EWIN_OP_SHADOW:
WINOP_SET_BOOL(eo->shadow, args);
break;
case EWIN_OP_NO_REDIRECT:
WINOP_SET_BOOL(eo->noredir, args);
break;
}
}
#endif
static int
WindowMatchEobjOpsParse(EObj *eo, const char *ops)
{
int err, len;
const WinOp *wop;
char *ops2, *s, *p, op[32];
if (!ops)
return -1;
/* Parse ':' separated operations list, e.g. "layer 3:desk 1: shade" */
p = ops2 = Estrdup(ops);
err = 0;
for (; p; p = s)
{
/* Break at ':' */
s = strchr(p, ':');
if (s)
*s++ = '\0';
len = 0;
sscanf(p, "%31s %n", op, &len);
if (len <= 0)
break;
p += len;
wop = EwinOpFind(op);
if (!wop || !wop->ok_match)
{
err = -1;
break;
}
/* If eo is NULL, we are validating the configuration */
if (!eo)
continue;
#if USE_COMPOSITE
if (eo->type == EOBJ_TYPE_EWIN)
WindowMatchEwinOpsAction((EWin *) eo, wop->op, p);
else
WindowMatchEobjOpsAction(eo, wop->op, p);
#else
WindowMatchEwinOpsAction((EWin *) eo, wop->op, p);
#endif
}
Efree(ops2);
return err;
}
static void
_WindowMatchEwinFunc(const void *_wm, void *_ew)
{
const WindowMatch *wm = (const WindowMatch *)_wm;
EWin *ew = (EWin *) _ew;
if (wm->op != MATCH_OP_WINOP || !WindowMatchEwinTest(wm, ew))
return;
/* Match found - do the ops */
WindowMatchEobjOpsParse(EoObj(ew), wm->args);
}
void
WindowMatchEwinOps(EWin *ew)
{
const WindowMatch *wm;
LIST_FOR_EACH(WindowMatch, &wm_list, wm) _WindowMatchEwinFunc(wm, ew);
}
#if USE_COMPOSITE
static void
_WindowMatchEobjFunc(void *_wm, void *_eo)
{
const WindowMatch *wm = (WindowMatch *) _wm;
EObj *eo = (EObj *) _eo;
if (wm->op != MATCH_OP_WINOP || !WindowMatchEobjTest(wm, eo))
return;
/* Match found - do the ops */
WindowMatchEobjOpsParse(eo, wm->args);
}
void
WindowMatchEobjOps(EObj *eo)
{
WindowMatch *wm;
LIST_FOR_EACH(WindowMatch, &wm_list, wm) _WindowMatchEobjFunc(wm, eo);
}
#endif
/*
* Winmatch module
*/
static void
WindowMatchSighan(int sig, void *prm __UNUSED__)
{
switch (sig)
{
case ESIGNAL_CONFIGURE:
#if 0 /* Done as part of theme loading */
ConfigFileLoad("windowmatches.cfg", Mode.theme.path,
WindowMatchConfigLoad, 1);
#endif
ConfigFileLoad("matches.cfg", NULL, WindowMatchConfigLoad2, 0);
break;
}
}
static void
WindowMatchIpc(const char *params)
{
const char *p;
char cmd[128], prm[4096], buf[4096];
int len;
cmd[0] = prm[0] = '\0';
p = params;
if (p)
{
len = 0;
sscanf(p, "%100s %4000s %n", cmd, prm, &len);
p += len;
}
if (!p || cmd[0] == '?')
{
}
else if (!strncmp(cmd, "list", 2))
{
WindowMatch *wm;
LIST_FOR_EACH(WindowMatch, &wm_list, wm)
IpcPrintf("%s\n", WindowMatchEncode(wm, buf, sizeof(buf)));
}
}
static const IpcItem WindowMatchIpcArray[] = {
{
WindowMatchIpc,
"wmatch", "wma",
"Window match functions",
" wmatch list List window matches\n" }
,
};
/*
* Module descriptor
*/
extern const EModule ModWindowMatch;
const EModule ModWindowMatch = {
"winmatch", NULL,
WindowMatchSighan,
MOD_ITEMS(WindowMatchIpcArray),
{ 0, NULL }
};