/*
 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
 * Copyright (C) 2004-2024 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 "config.h"

#include <X11/Xlib.h>

#include "E.h"
#include "aclass.h"
#include "borders.h"
#include "desktops.h"
#include "emodule.h"
#include "eobj.h"
#include "ewins.h"
#include "ewin-ops.h"
#include "focus.h"
#include "grabs.h"
#include "hints.h"
#include "screen.h"
#include "session.h"
#include "settings.h"
#include "snaps.h"
#include "timers.h"
#include "xwin.h"

#define ENABLE_IPC_INSERT_KEYS  0

#define SS(s) ((s) ? (s) : NoText)
static const char NoText[] = "-NONE-";

static char    *ipc_bufptr = NULL;
static size_t   ipc_bufsiz = 0;
static char     ipc_active = 0;

static void
IpcPrintInit(void)
{
    ipc_bufptr = NULL;
    ipc_bufsiz = 0;
    ipc_active = 1;
}

static void
IpcPrintDone(void)
{
    EFREE_NULL(ipc_bufptr);
    ipc_bufsiz = 0;
    ipc_active = 0;
}

static const char *
IpcPrintGetBuffer(void)
{
    if (!ipc_bufptr)
        return NULL;
    ipc_bufptr[ipc_bufsiz] = '\0';
    return ipc_bufptr;
}

void
IpcPrintf(const char *fmt, ...)
{
    char            tmp[16384];
    int             len;
    va_list         args;

    if (!ipc_active)
        return;

    va_start(args, fmt);
    len = Evsnprintf(tmp, sizeof(tmp), fmt, args);
    if (len >= (int)sizeof(tmp))
        len = sizeof(tmp) - 1;
    va_end(args);

    ipc_bufptr = EREALLOC(char, ipc_bufptr, ipc_bufsiz + len + 1);

    memcpy(ipc_bufptr + ipc_bufsiz, tmp, len);
    ipc_bufsiz += len;
}

static int
SetEwinBoolean(const char *txt, char *item, const char *value, int set)
{
    int             vold, vnew;

    vnew = vold = *item != 0;   /* Remember old value */

    if (!value || value[0] == '\0')
        vnew = !vold;
    else if (!strcmp(value, "on"))
        vnew = 1;
    else if (!strcmp(value, "off"))
        vnew = 0;
    else if (!strcmp(value, "?"))
        IpcPrintf("%s: %s\n", txt, (vold) ? "on" : "off");
    else
        IpcPrintf("Error: %s\n", value);

    if (vnew != vold)
    {
        if (set)
            *item = vnew;
        return 1;
    }

    return 0;
}

/* The IPC functions */

static void
IPC_Screen(const char *params)
{
    char            param[1024];
    int             l;
    const char     *p;

    param[0] = '\0';
    p = params;
    if (p)
    {
        l = 0;
        sscanf(p, "%1000s %n", param, &l);
        p += l;
    }

    if (!p || !strncmp(param, "list", 2))
    {
        ScreenShowInfo(p);
    }
    else if (!strncmp(param, "size", 2))
    {
        IpcPrintf("Screen %d  size %dx%d\n", Dpy.screen,
                  WinGetW(VROOT), WinGetH(VROOT));
    }
    else if (!strcmp(param, "split"))
    {
        unsigned int    nx, ny;

        nx = 2;
        ny = 1;
        sscanf(p, "%u %u\n", &nx, &ny);
        ScreenSplit(nx, ny);
    }
}

static void
IPC_Nop(const char *params __UNUSED__)
{
    IpcPrintf("nop\n");
}

/* Should be elsewhere */
static void
IPC_Border(const char *params)
{
    if (!params)
    {
        IpcPrintf("Please specify...\n");
        return;
    }

    if (!strncmp(params, "list", 2))
    {
        Border        **lst;
        int             i, num;

        lst = BordersGetList(&num);
        for (i = 0; i < num; i++)
            IpcPrintf("%s\n", BorderGetName(lst[i]));
        Efree(lst);
    }
}

#if ENABLE_DIALOGS
static void
IPC_DialogOK(const char *params)
{
    char            tbuf[128];
    const char     *title;
    int             nr;

    if (!params)
    {
        IpcPrintf("Error: No text for dialog specified\n");
        return;
    }

    nr = 0;
    sscanf(params, "[%127[^]]] %n", tbuf, &nr);
    if (nr > 0)
    {
        title = tbuf;
        params += nr;
    }
    else
    {
        title = _("Message");
    }

    DialogOKstr(title, params);
}
#endif                          /* ENABLE_DIALOGS */

static int
CfgStrlistIndex(const char *const *list, const char *str)
{
    int             i;

    for (i = 0; list[i]; i++)
        if (!strcmp(list[i], str))
            return i;
    return -1;
}

static const char *const MovResCfgMoveResizeModeNames[] = {
    "opaque", "lined", "box", "shaded", "semi-solid", "translucent", "techop",
    NULL
};

static const char *const MovResCfgInfoModes[] = {
    "never", "center", "corner", NULL
};

static void
IPC_MoveResize(const char *params)
{
    char            param1[32];
    char            param2[32];
    int             i;

    if (!params)
    {
        IpcPrintf("Please specify...\n");
        return;
    }

    param1[0] = param2[0] = '\0';
    sscanf(params, "%31s %31s", param1, param2);

    if (!strncmp(param1, "move", 2))
    {
        if (param2[0] == '\n' || param2[0] == '?')
        {
            Conf.movres.mode_move =
                MoveResizeModeValidateMove(Conf.movres.mode_move);
            IpcPrintf("Move mode: %s\n",
                      MovResCfgMoveResizeModeNames[Conf.movres.mode_move]);
            return;
        }

        i = CfgStrlistIndex(MovResCfgMoveResizeModeNames, param2);
        if (i >= 0)
        {
            Conf.movres.mode_move = i;
        }
        else
        {
            IpcPrintf("Move mode not found: %s\n", param2);
        }

    }
    else if (!strncmp(param1, "resize", 2))
    {
        if (param2[0] == '\n' || param2[0] == '?')
        {
            Conf.movres.mode_resize =
                MoveResizeModeValidateResize(Conf.movres.mode_resize);
            IpcPrintf("Resize mode: %s\n",
                      MovResCfgMoveResizeModeNames[Conf.movres.mode_resize]);
            return;
        }

        i = CfgStrlistIndex(MovResCfgMoveResizeModeNames, param2);
        if (i >= 0)
        {
            Conf.movres.mode_resize = i;
        }
        else
        {
            IpcPrintf("Resize mode not found: %s\n", param2);
        }

    }
    else if (!strncmp(param1, "info", 2))
    {
        if (param2[0] == '\n' || param2[0] == '?')
        {
            if (Conf.movres.mode_info < 0 || Conf.movres.mode_info > 2)
                Conf.movres.mode_info = 1;
            IpcPrintf("Info mode: %s\n",
                      MovResCfgInfoModes[Conf.movres.mode_info]);
            return;
        }

        i = CfgStrlistIndex(MovResCfgInfoModes, param2);
        if (i >= 0)
        {
            Conf.movres.mode_info = i;
        }
        else
        {
            IpcPrintf("Info mode not found: %s\n", param2);
        }

    }
}

static void
IPC_WinListFmt(char *buf, unsigned int len, const char *fmt, EWin *e)
{
    static const char *const TxtPG[] = { "NW", "NE", "SW", "SE" };
    int             nw, chf, chp;

    for (nw = 0, chp = '\0';; chp = chf)
    {
        chf = *fmt++;
        if (chf == '\0')
            break;

        if (chf == '_')
        {
            chf = ' ';
            goto do_char;
        }

        if (chf != '%' || (chf == '%' && chf == chp))
            goto do_char;

        chf = *fmt++;
        if (chf == '\0')
            break;

        switch (chf)
        {
        case 'A':              /* Area x y */
            nw += snprintf(buf + nw, len - nw, "%d %d", e->area_x, e->area_y);
            break;
        case 'D':              /* Desk */
            nw += snprintf(buf + nw, len - nw, "%2d",
                           EoIsSticky(e) ? -1 : (int)EoGetDeskNum(e));
            break;
        case 'G':              /* Gravity */
            nw += snprintf(buf + nw, len - nw, "%s %4d,%4d %2d,%2d",
                           TxtPG[e->place.gravity & 3], e->place.gx,
                           e->place.gy, e->place.ax, e->place.ay);
            break;
        case 'P':              /* Win X Y */
            nw += snprintf(buf + nw, len - nw, "%5d %5d", EoGetX(e), EoGetY(e));
            break;
        case 'S':              /* Win WxH */
            nw += snprintf(buf + nw, len - nw, "%4dx%4d", EoGetW(e), EoGetH(e));
            break;
        case 'T':              /* Client WxH */
            nw += snprintf(buf + nw, len - nw, "%4dx%4d",
                           e->client.w, e->client.h);
            break;
        case 'i':              /* Client XID */
            nw += snprintf(buf + nw, len - nw, "%#10x", EwinGetClientXwin(e));
            break;
        case 'c':              /* CName */
            nw += snprintf(buf + nw, len - nw, "%s", SS(EwinGetIcccmCName(e)));
            break;
        case 'd':              /* Class */
            nw += snprintf(buf + nw, len - nw, "%s", SS(EwinGetIcccmClass(e)));
            break;
        case 'n':              /* Name */
            nw += snprintf(buf + nw, len - nw, "%s", SS(EwinGetIcccmName(e)));
            break;
        case 'r':              /* Role */
            nw += snprintf(buf + nw, len - nw, "%s", SS(e->icccm.wm_role));
            break;
        }
        continue;

      do_char:
        buf[nw++] = chf;
        if (nw >= (int)len - 2)
            break;
    }

    buf[nw] = '\0';

    IpcPrintf("%s\n", buf);
}

static void
IPC_WinList(const char *params)
{
    char            buf[1024];
    char            format[64];
    const char     *match, *fmt;
    EWin          **lst, *e;
    int             num, i;

    format[0] = '\0';
    match = params;
    if (params)
    {
        num = 0;
        sscanf(params, "%63s %n", format, &num);
        match += num;
    }
    if (!match || !match[0])
        match = "all";

    lst = EwinsFindByExpr(match, &num, NULL);
    if (!lst)
    {
        IpcPrintf("No windows matching %s\n", match);
        return;
    }

    for (i = 0; i < num; i++)
    {
        e = lst[i];
        switch (format[0])
        {
        case '\0':
            fmt = "%i : %n";
            break;
        default:
            fmt = "%i : %n :: %D : %A : %P %S";
            break;
        case 'a':
            fmt = "%i : %P %S :: %D : %A : %n";
            break;
        case 'g':
            fmt = "%i : %P %S :: %D : %G : %n";
            break;
        case 'p':
            fmt = "%i : %P %S :: %D : \"%c\" \"%d\" : \"%n\" : \"%r\"";
            break;
        case '+':
            fmt = format + 1;
            break;
        }
        IPC_WinListFmt(buf, sizeof(buf), fmt, e);
    }
    Efree(lst);
}

static Timer   *op_timer = NULL;

static int
OpacityTimeout(void *data)
{
    EWin           *ewin = (EWin *) data;

    if (!EwinFindByPtr(ewin))   /* May be gone */
        goto done;

    if (ewin->state.active)
        EoChangeOpacity(ewin, ewin->props.focused_opacity);

  done:
    ewin->state.show_coords = 0;
    op_timer = NULL;
    return 0;
}

static void
IpcWinop(const WinOp *wop, EWin *ewin, const char *prm)
{
    char            param1[128], param2[128];
    unsigned int    i, val;
    char            on;
    int             a, b, d;

    param1[0] = param2[0] = '\0';
    sscanf(prm, "%127s %127s", param1, param2);

    switch (wop->op)
    {
    default:
        /* We should not get here */
        IpcPrintf("Error: unknown operation\n");
        return;

    case EWIN_OP_BORDER:
        if (!param1[0])
        {
            IpcPrintf("Error: no border specified\n");
            goto done;
        }
        if (!strcmp(param1, "?"))
        {
            IpcPrintf("window border: %s\n", BorderGetName(ewin->border));
            goto done;
        }
        EwinOpSetBorder(ewin, OPSRC_USER, param1);
        break;

    case EWIN_OP_TITLE:
        if (!prm[0])
        {
            IpcPrintf("Error: no title specified\n");
            goto done;
        }
        if (!strcmp(prm, "?"))
        {
            IpcPrintf("title: %s\n", EwinGetIcccmName(ewin));
            goto done;
        }
        EwinOpSetTitle(ewin, prm);
        break;

    case EWIN_OP_CLOSE:
        EwinOpClose(ewin, OPSRC_USER);
        break;

    case EWIN_OP_KILL:
        EwinOpKill(ewin, OPSRC_USER);
        break;

    case EWIN_OP_ICONIFY:
        on = ewin->state.iconified;
        if (SetEwinBoolean("window iconified", &on, param1, 1))
            EwinOpIconify(ewin, OPSRC_USER, on);
        break;

    case EWIN_OP_ALONE:
        EwinAlone(ewin);
        break;

    case EWIN_OP_SHADE:
        on = ewin->state.shaded;
        if (SetEwinBoolean(wop->name, &on, param1, 1))
            EwinOpShade(ewin, OPSRC_USER, on);
        break;

    case EWIN_OP_STICK:
        on = EoIsSticky(ewin);
        if (SetEwinBoolean(wop->name, &on, param1, 1))
            EwinOpStick(ewin, OPSRC_USER, on);
        break;

    case EWIN_OP_FOCUS:
        if (!strcmp(param1, "?"))
        {
            IpcPrintf("focused: %s\n", (ewin == GetFocusEwin())? "yes" : "no");
            goto done;
        }
        EwinOpActivate(ewin, OPSRC_USER, 1);
        break;

    case EWIN_OP_PIN:
        if (!param1[0])
        {
            IpcPrintf("Error: no parameters supplied\n");
            goto done;
        }
        else if (!strcmp(param1, "?"))
        {
            for (i = 0; i < ewin->num_pinned; i++)
                IpcPrintf("%2d: %d,%d\n", ewin->pinned[i].desk,
                          ewin->pinned[i].ax, ewin->pinned[i].ay);
        }
        else if (!strcmp(param1, "clear"))
        {
            EwinPinOn(ewin, -2, 0, 0, 0);
        }
        else
        {
            if (!strcmp(param1, "on"))
                on = 1;
            else if (!strcmp(param1, "off"))
                on = 0;
            else if (!strcmp(param1, "tgl"))
                on = -1;
            else
                break;

            if (param2[0] == '*')
            {
                d = EoGetDeskNum(ewin);
                DeskGetArea(EoGetDesk(ewin), &a, &b);
            }
            else
            {
                a = b = d = -1;
                sscanf(prm, "%*s %u %u %u", &d, &a, &b);
            }
            EwinPinOn(ewin, on, d, a, b);
        }
        break;

    case EWIN_OP_DESK:
        if (!param1[0])
        {
            IpcPrintf("Error: no desktop supplied\n");
            goto done;
        }
        if (!strncmp(param1, "next", 1))
        {
            EwinOpMoveToDesk(ewin, OPSRC_USER, EoGetDesk(ewin), 1);
        }
        else if (!strncmp(param1, "prev", 1))
        {
            EwinOpMoveToDesk(ewin, OPSRC_USER, EoGetDesk(ewin), -1);
        }
        else if (!strcmp(param1, "?"))
        {
            IpcPrintf("window desk: %d\n", EoGetDeskNum(ewin));
        }
        else
        {
            EwinOpMoveToDesk(ewin, OPSRC_USER, NULL, atoi(param1));
        }
        break;

    case EWIN_OP_AREA:
        if (!param1[0])
        {
            IpcPrintf("Error: no area supplied\n");
            goto done;
        }
        if (!strcmp(param1, "?"))
        {
            IpcPrintf("window area: %d %d\n", ewin->area_x, ewin->area_y);
        }
        else if (!strcmp(param1, "move"))
        {
            a = b = 0;
            sscanf(prm, "%*s %i %i", &a, &b);
            EwinMoveToArea(ewin, ewin->area_x + a, ewin->area_y + b);
        }
        else
        {
            a = ewin->area_x;
            b = ewin->area_y;
            sscanf(param1, "%i", &a);
            sscanf(param2, "%i", &b);
            EwinMoveToArea(ewin, a, b);
        }
        break;

    case EWIN_OP_MOVE:
        if (!param1[0])
        {
            IpcPrintf("Error: no coords supplied\n");
            goto done;
        }
        if (!strcmp(param1, "ptr"))
        {
            MoveResizeMoveStart(ewin, 0, Mode.nogroup);
        }
        else if (!strcmp(param1, "kbd"))
        {
            MoveResizeMoveStart(ewin, 1, Mode.nogroup);
        }
        else if (!strcmp(param1, "?"))
        {
            IpcPrintf("window location: %d %d\n", EoGetX(ewin), EoGetY(ewin));
        }
        else if (!strcmp(param1, "??"))
        {
            IpcPrintf("client location: %d %d\n",
                      ewin->client.x, ewin->client.y);
        }
        else
        {
            a = EoGetX(ewin);
            b = EoGetY(ewin);
            sscanf(param1, "%i", &a);
            sscanf(param2, "%i", &b);
            EwinOpMove(ewin, OPSRC_USER, a, b);
        }
        break;

    case EWIN_OP_SIZE:
        if (!param1[0])
            goto done;

        if (!strcmp(param1, "ptr"))
        {
            MoveResizeResizeStart(ewin, 0, MODE_RESIZE);
        }
        else if (!strcmp(param1, "ptr-h"))
        {
            MoveResizeResizeStart(ewin, 0, MODE_RESIZE_H);
        }
        else if (!strcmp(param1, "ptr-v"))
        {
            MoveResizeResizeStart(ewin, 0, MODE_RESIZE_V);
        }
        else if (!strcmp(param1, "kbd"))
        {
            MoveResizeResizeStart(ewin, 1, MODE_RESIZE);
        }
        else if (!strcmp(param1, "?"))
        {
            IpcPrintf("window size: %d %d\n", ewin->client.w, ewin->client.h);
        }
        else if (!strcmp(param1, "??"))
        {
            IpcPrintf("frame size: %d %d\n", EoGetW(ewin), EoGetH(ewin));
        }
        else
        {
            a = ewin->client.w;
            b = ewin->client.h;
            sscanf(param1, "%i", &a);
            sscanf(param2, "%i", &b);
            EwinOpResize(ewin, OPSRC_USER, a, b);
        }
        break;

    case EWIN_OP_MOVE_REL:
        if (!param1[0])
            goto done;
        a = b = 0;
        sscanf(prm, "%i %i", &a, &b);
        a += EoGetX(ewin);
        b += EoGetY(ewin);
        EwinOpMove(ewin, OPSRC_USER, a, b);
        break;

    case EWIN_OP_SIZE_REL:
        if (!param1[0])
            goto done;
        a = b = 0;
        sscanf(prm, "%i %i", &a, &b);
        a += ewin->client.w;
        b += ewin->client.h;
        EwinOpResize(ewin, OPSRC_USER, a, b);
        break;

    case EWIN_OP_MAX_WIDTH:
        MaxSizeHV(ewin, param1, 1, 0, 0);
        break;

    case EWIN_OP_MAX_HEIGHT:
        MaxSizeHV(ewin, param1, 0, 1, 0);
        break;

    case EWIN_OP_MAX_SIZE:
        MaxSizeHV(ewin, param1, 1, 1, 0);
        break;

    case EWIN_OP_FULLSCREEN:
        on = ewin->state.fullscreen;
        if (SetEwinBoolean(wop->name, &on, param1, 1))
            EwinOpFullscreen(ewin, OPSRC_USER, on);
        break;

    case EWIN_OP_ZOOM:
        on = ewin->state.zoomed;
        if (SetEwinBoolean(wop->name, &on, param1, 1))
            Zoom(ewin, on);
        break;

    case EWIN_OP_LAYER:
        if (!strcmp(param1, "?"))
        {
            IpcPrintf("window layer: %d\n", EoGetLayer(ewin));
            goto done;
        }
        val = atoi(param1);
        EwinOpSetLayer(ewin, OPSRC_USER, val);
        break;

    case EWIN_OP_RAISE:
        EwinOpRaise(ewin, OPSRC_USER);
        break;

    case EWIN_OP_LOWER:
        EwinOpLower(ewin, OPSRC_USER);
        break;

    case EWIN_OP_OPACITY:
        a = OpacityToPercent(ewin->props.opacity);
        if (!strcmp(param1, "?"))
        {
            IpcPrintf("opacity: %u\n", a);
            goto done;
        }
        b = a;
        sscanf(param1, "%i", &b);
        if ((param1[0] == '+') || (param1[0] == '-'))
        {
            if (a == 0)
                a = OpacityToPercent(EoGetOpacity(ewin));
            b += a;
            if (b < 10)
                b = 10;
            else if (b > 100)
                b = 0;
        }
        a = (b < 0) ? 1 : (b > 100) ? 100 : b;
        EwinOpSetOpacity(ewin, OPSRC_USER, a);
        if (a && ewin->state.active)
        {
            EoChangeOpacity(ewin, OpacityFromPercent(a));
            TIMER_DEL(op_timer);
            if (ewin->props.focused_opacity)
                TIMER_ADD(op_timer, 700, OpacityTimeout, ewin);
        }
        if (ewin->state.in_action)
            CoordsShowOpacity(ewin);
        break;

    case EWIN_OP_FOCUSED_OPACITY:
        a = OpacityToPercent(ewin->props.focused_opacity);
        if (!strcmp(param1, "?"))
        {
            IpcPrintf("focused_opacity: %u\n", a);
            goto done;
        }
        b = a;
        sscanf(param1, "%i", &b);
        if ((param1[0] == '+') || (param1[0] == '-'))
        {
            if (a == 0)
                a = OpacityToPercent(EoGetOpacity(ewin));
            b += a;
            if (b < 10)
                b = 10;
            else if (b > 100)
                b = 0;
        }
        a = (b < 0) ? 0 : (b > 100) ? 100 : b;
        EwinOpSetFocusedOpacity(ewin, OPSRC_USER, a);
        if (ewin->state.in_action)
            CoordsShowOpacity(ewin);
        break;

    case EWIN_OP_SNAP:
        SnapshotEwinParse(ewin, prm);
        break;

    case EWIN_OP_SKIP_LISTS:
        on = ewin->props.skip_ext_task;
        if (SetEwinBoolean(wop->name, &on, param1, 1))
            EwinOpSkipLists(ewin, OPSRC_USER, on);
        break;

    case EWIN_OP_IGNORE_ARRANGE:
        on = ewin->props.ignorearrange;
        SetEwinBoolean(wop->name, &on, param1, 1);
        ewin->props.ignorearrange = on;
        goto ewin_update_snap_flags;

    case EWIN_OP_NEVER_USE_AREA:
        on = ewin->props.never_use_area;
        SetEwinBoolean(wop->name, &on, param1, 1);
        ewin->props.never_use_area = on;
        goto ewin_update_snap_flags;

    case EWIN_OP_FOCUS_CLICK:
        on = ewin->props.focusclick;
        SetEwinBoolean(wop->name, &on, param1, 1);
        ewin->props.focusclick = on;
        goto ewin_update_snap_flags;

    case EWIN_OP_AUTOSHADE:
        on = ewin->props.autoshade;
        SetEwinBoolean(wop->name, &on, param1, 1);
        ewin->props.autoshade = on;
        goto ewin_update_snap_flags;

    case EWIN_OP_NO_BUTTON_GRABS:
        on = ewin->props.no_button_grabs;
        if (SetEwinBoolean(wop->name, &on, param1, 1))
        {
            ewin->props.no_button_grabs = on;
            if (ewin->props.no_button_grabs)
                UnGrabButtonGrabs(EoGetWin(ewin));
            else
                GrabButtonGrabs(EoGetWin(ewin));
        }
        goto ewin_update_snap_flags;

    case EWIN_OP_PASS_POINTER:
        on = ewin->props.pass_pointer;
        SetEwinBoolean(wop->name, &on, param1, 1);
        ewin->props.pass_pointer = on;
        EWindowPassPointer(EoGetWin(ewin), on);
        goto ewin_update_snap_flags;

    case EWIN_OP_INH_APP_FOCUS:
        on = EwinInhGetApp(ewin, focus);
        SetEwinBoolean(wop->name, &on, param1, 1);
        EwinInhSetApp(ewin, focus, on);
        goto ewin_update_snap_flags;

    case EWIN_OP_INH_APP_MOVE:
        on = EwinInhGetApp(ewin, move);
        SetEwinBoolean(wop->name, &on, param1, 1);
        EwinInhSetApp(ewin, move, on);
        goto ewin_update_snap_flags;

    case EWIN_OP_INH_APP_SIZE:
        on = EwinInhGetApp(ewin, size);
        SetEwinBoolean(wop->name, &on, param1, 1);
        EwinInhSetApp(ewin, size, on);
        goto ewin_update_snap_flags;

    case EWIN_OP_INH_USER_CLOSE:
        on = EwinInhGetUser(ewin, close);
        SetEwinBoolean(wop->name, &on, param1, 1);
        EwinInhSetUser(ewin, close, on);
        goto ewin_update_state_hints;

    case EWIN_OP_INH_USER_MOVE:
        on = EwinInhGetUser(ewin, move);
        SetEwinBoolean(wop->name, &on, param1, 1);
        EwinInhSetUser(ewin, move, on);
        goto ewin_update_state_hints;

    case EWIN_OP_INH_USER_SIZE:
        on = EwinInhGetUser(ewin, size);
        SetEwinBoolean(wop->name, &on, param1, 1);
        EwinInhSetUser(ewin, size, on);
        goto ewin_update_state_hints;

    case EWIN_OP_INH_WM_FOCUS:
        on = EwinInhGetWM(ewin, focus);
        SetEwinBoolean(wop->name, &on, param1, 1);
        EwinInhSetWM(ewin, focus, on);
        goto ewin_update_state;

#if USE_COMPOSITE
    case EWIN_OP_FADE:
        on = EoGetFade(ewin);
        if (SetEwinBoolean(wop->name, &on, param1, 1))
            EoSetFade(ewin, on);
        break;

    case EWIN_OP_SHADOW:
        on = EoGetShadow(ewin);
        if (SetEwinBoolean(wop->name, &on, param1, 1))
            EoChangeShadow(ewin, on);
        break;

    case EWIN_OP_NO_REDIRECT:
        on = EoGetNoRedirect(ewin);
        if (SetEwinBoolean(wop->name, &on, param1, 1))
            EoSetNoRedirect(ewin, on);
        break;
#endif
      ewin_update_snap_flags:
        SnapshotEwinUpdate(ewin, SNAP_USE_FLAGS);
        break;

      ewin_update_state:
        EwinStateUpdate(ewin);
        break;

      ewin_update_state_hints:
        EwinStateUpdate(ewin);
        HintsSetWindowState(ewin);
        break;
    }

  done:
    return;
}

static void
IPC_WinOps(const char *params)
{
    char            match[128];
    char            operation[128];
    const char     *p;
    EWin          **lst;
    int             i, num, flags;
    const WinOp    *wop;

    if (!params)
    {
        IpcPrintf("Error: no window specified\n");
        return;
    }

    match[0] = operation[0] = '\0';
    num = 0;
    sscanf(params, "%127s %127s %n", match, operation, &num);
    p = params + num;

    if (!operation[0])
    {
        IpcPrintf("Error: no operation specified\n");
        return;
    }

    wop = EwinOpFind(operation);
    if (!wop)
    {
        IpcPrintf("Error: unknown operation\n");
        return;
    }

    lst = EwinsFindByExpr(match, &num, &flags);
    if (!lst)
    {
        IpcPrintf("No windows matching %s\n", match);
        return;
    }

    if (flags)
        Mode.nogroup = 1;

    for (i = 0; i < num; i++)
        IpcWinop(wop, lst[i], p);

    Mode.nogroup = 0;

    Efree(lst);
}

static void
IPC_Remember(const char *params)
{
    int             window, l;
    EWin           *ewin;

    if (!params)
    {
        IpcPrintf("Error: no parameters\n");
        goto done;
    }

    l = 0;
    window = 0;
    sscanf(params, "%x %n", &window, &l);
    if (l <= 0)
        return;

    ewin = EwinFindByClient(window);
    if (!ewin)
    {
        IpcPrintf("Error: Window not found: %#x\n", window);
        goto done;
    }

    SnapshotEwinParse(ewin, params + l);

  done:
    return;
}

static void
IPC_ForceSave(const char *params __UNUSED__)
{
    autosave();
}

static void
IPC_Exec(const char *params)
{
    if (params)
        EspawnApplication(params, EXEC_SET_LANG | EXEC_SET_STARTUP_ID);
    else
        IpcPrintf("exec what?\n");
}

static void
IPC_Restart(const char *params __UNUSED__)
{
    SessionExit(EEXIT_RESTART, NULL);
}

static void
IPC_Exit(const char *params)
{
    char            param1[1024];
    const char     *p2;
    int             l;

    param1[0] = 0;
    l = 0;
    if (params)
        sscanf(params, "%1000s %n", param1, &l);
    p2 = (l > 0) ? params + l : NULL;

    if (!param1[0])
        SessionExit(EEXIT_EXIT, NULL);
    else if (!strcmp(param1, "restart"))
        SessionExit(EEXIT_RESTART, NULL);
    else if (!strcmp(param1, "theme"))
        SessionExit(EEXIT_THEME, p2);
    else if (!strcmp(param1, "exec"))
        SessionExit(EEXIT_EXEC, p2);
    else if (!strcmp(param1, "logout"))
        SessionLogout(ESESSION_LOGOUT);
    else if (!strcmp(param1, "shutdown"))
        SessionLogout(ESESSION_SHUTDOWN);
}

#if ENABLE_DIALOGS
static void
IPC_About(const char *params __UNUSED__)
{
    About();
}
#endif

static void
IPC_Version(const char *params __UNUSED__)
{
    IpcPrintf("%s %s\n", e_wm_name, e_wm_version);
}

static void
IPC_Debug(const char *params)
{
    char            param[1024];
    int             l;
    const char     *p;

    if (!params)
        return;

    p = params;
    l = 0;
    sscanf(p, "%1000s %n", param, &l);
    p += l;

    if (!strncmp(param, "event", 2))
    {
        EDebugInit(p);
    }
    else if (!strncmp(param, "grab", 2))
    {
        l = 0;
        sscanf(p, "%1000s %n", param, &l);
        p += l;

        if (!strcmp(param, "?"))
        {
            IpcPrintf("Pointer grab on=%d win=%#x\n",
                      Mode.grabs.pointer_grab_active,
                      Mode.grabs.pointer_grab_window);
        }
        else if (!strncmp(param, "allow", 2))
        {
            l = 0;
            sscanf(p, "%d", &l);
            XAllowEvents(disp, l, CurrentTime);
            IpcPrintf("XAllowEvents\n");
        }
        else if (!strncmp(param, "unset", 2))
        {
            GrabPointerRelease();
            IpcPrintf("Ungrab\n");
        }
    }
    else if (!strncmp(param, "sync", 2))
    {
        l = 0;
        sscanf(p, "%1000s %n", param, &l);
        if (!strncmp(param, "on", 2))
        {
            XSynchronize(disp, True);
            IpcPrintf("Sync on\n");
        }
        else if (!strncmp(param, "off", 2))
        {
            XSynchronize(disp, False);
            IpcPrintf("Sync off\n");
        }
    }
    else if (!strncmp(param, "cache", 2))
    {
        ECacheInfo      ci;

        EImageGetCacheInfo(&ci);
        IpcPrintf("Cache         %10s %10s\n"
                  "Image  bytes: %10d %10d\n"
                  "Ximage bytes: %10d %10d\n"
                  "Ximage count: %10d %10d\n",
                  "Max", "Used",
                  ci.img.max_mem, ci.img.used_mem,
                  ci.xim.max_mem, ci.xim.used_mem,
                  ci.xim.max_cnt, ci.xim.used_cnt);
    }
#if 0
    else if (!strncmp(param, "dump", 2))
    {
        l = 0;
        sscanf(p, "%i", &l);
        EDrawableDumpImage(l, "dump");
    }
#endif
}

static void
IPC_Set(const char *params)
{
    ConfigurationSet(params);
}

static void
IPC_Show(const char *params)
{
    ConfigurationShow(params);
}

static void
EwinShowInfo(const EWin *ewin)
{
    int             bl, br, bt, bb;

    EwinBorderGetSize(ewin, &bl, &br, &bt, &bb);

    IpcPrintf("WM_NAME                 %s\n"
              "_NET_WM_NAME            %s\n"
              "WM_ICON_NAME            %s\n"
              "WM_CLASS name.class     %s.%s\n"
              "WM_WINDOW_ROLE          %s\n"
              "WM_COMMAND              %s\n"
              "WM_CLIENT_MACHINE       %s\n"
              "Client window           %#10x   x,y %4i,%4i   wxh %4ix%4i\n"
              "Container window        %#10x\n"
              "Frame window            %#10x   x,y %4i,%4i   wxh %4ix%4i\n"
#if USE_COMPOSITE
              "Named pixmap            %#10x\n"
#endif
              "Border                  %s   lrtb %i,%i,%i,%i\n"
              "Icon window, pixmap, mask %#10x, %#10x, %#10x\n"
              "Is group leader  %i  Window group leader %#x   Client leader %#10x\n"
              "Has transients   %i  Transient type  %i  Transient for %#10x\n"
              "No resize H/V    %i/%i       Shaped      %i\n"
              "Base, min, max, inc w/h %ix%i, %ix%i, %ix%i %ix%i\n"
              "Aspect min, max         %5.5f, %5.5f\n"
              "Struts                  lrtb %i,%i,%i,%i\n"
              "MWM border %i resizeh %i title %i menu %i minimize %i maximize %i\n"
              "NeedsInput   %i   TakeFocus    %i   FocusNever   %i   FocusClick   %i\n"
              "NeverUseArea %i   FixedPos     %i   FixedSize    %i\n"
              "Desktop      %i   Layer        %i(%i)\n"
              "Iconified    %i   Sticky       %i   Shaded       %i   Docked       %i\n"
              "State        %i   Shown        %i   Visibility   %i   Active       %i\n"
              "Member of groups        %i\n"
#if USE_COMPOSITE
              "Opacity    %3i(%x)  Focused Opacity     %3i\n"
              "Shadow       %i   Fade         %i   NoRedirect   %i\n"
#else
              "Opacity    %3i\n"
#endif
              ,
              SS(EwinGetIcccmName(ewin)),
              SS(ewin->ewmh.wm_name),
              SS(ewin->icccm.wm_icon_name),
              SS(EwinGetIcccmCName(ewin)), SS(EwinGetIcccmClass(ewin)),
              SS(ewin->icccm.wm_role),
              SS(ewin->icccm.wm_command),
              SS(ewin->icccm.wm_machine),
              EwinGetClientXwin(ewin),
              ewin->client.x, ewin->client.y, ewin->client.w, ewin->client.h,
              EwinGetContainerXwin(ewin),
              EoGetXwin(ewin),
              EoGetX(ewin), EoGetY(ewin), EoGetW(ewin), EoGetH(ewin),
#if USE_COMPOSITE
              EoGetPixmap(ewin),
#endif
              EwinBorderGetName(ewin), bl, br, bt, bb,
              ewin->icccm.icon_win,
              ewin->icccm.icon_pmap, ewin->icccm.icon_mask,
              EwinIsWindowGroupLeader(ewin), EwinGetWindowGroup(ewin),
              ewin->icccm.client_leader, EwinGetTransientCount(ewin),
              EwinIsTransient(ewin), EwinGetTransientFor(ewin),
              ewin->props.no_resize_h, ewin->props.no_resize_v,
              ewin->state.shaped, ewin->icccm.base_w, ewin->icccm.base_h,
              ewin->icccm.width_min, ewin->icccm.height_min,
              ewin->icccm.width_max, ewin->icccm.height_max,
              ewin->icccm.w_inc, ewin->icccm.h_inc,
              (double)ewin->icccm.aspect_min, (double)ewin->icccm.aspect_max,
              ewin->strut.left, ewin->strut.right,
              ewin->strut.top, ewin->strut.bottom,
              ewin->mwm.decor_border, ewin->mwm.decor_resizeh,
              ewin->mwm.decor_title, ewin->mwm.decor_menu,
              ewin->mwm.decor_minimize, ewin->mwm.decor_maximize,
              ewin->icccm.need_input, ewin->icccm.take_focus,
              EwinInhGetWM(ewin, focus), ewin->props.focusclick,
              ewin->props.never_use_area, EwinInhGetUser(ewin, move),
              EwinInhGetUser(ewin, size), EoGetDeskNum(ewin),
              EoGetLayer(ewin), ewin->o.ilayer,
              ewin->state.iconified, EoIsSticky(ewin), ewin->state.shaded,
              ewin->state.docked, ewin->state.state, EoIsShown(ewin),
              ewin->state.visibility, ewin->state.active, ewin->num_groups,
              OpacityToPercent(ewin->props.opacity)
#if USE_COMPOSITE
              , EoGetOpacity(ewin),
              OpacityToPercent(ewin->props.focused_opacity), EoGetShadow(ewin),
              EoGetFade(ewin), EoGetNoRedirect(ewin)
#endif
        );
}

static void
IPC_EwinInfo(const char *params)
{
    char            match[FILEPATH_LEN_MAX];
    EWin          **lst;
    int             i, num;

    if (!params)
    {
        IpcPrintf("Error: no window specified\n");
        return;
    }

    sscanf(params, "%1000s", match);

    lst = EwinsFindByExpr(match, &num, NULL);
    if (!lst)
    {
        IpcPrintf("No windows matching %s\n", match);
        return;
    }

    for (i = 0; i < num; i++)
    {
        EwinShowInfo(lst[i]);
        if (i != num - 1)
            IpcPrintf("\n");
    }

    Efree(lst);
}

static void
IPC_ObjInfo(const char *params __UNUSED__)
{
    int             i, num;
    EObj           *const *lst, *eo;

    lst = EobjListStackGet(&num);

    IpcPrintf
        ("Num    Window De T V Shape  Dsk S  F   L     Pos       Size    C R Name\n");
    for (i = 0; i < num; i++)
    {
        eo = lst[i];
        IpcPrintf
            (" %2d %#9x %2d %d %d %2d/%2d  %3d %d  %d %3d %5d,%5d %4dx%4d %d %d %s\n",
             i, EobjGetXwin(eo), WinGetDepth(EobjGetWin(eo)), eo->type,
             eo->shown, eo->shaped, EShapeCheck(EobjGetWin(eo)), eo->desk->num,
             eo->sticky, eo->floating, eo->ilayer,
             EobjGetX(eo), EobjGetY(eo), EobjGetW(eo), EobjGetH(eo),
#if USE_COMPOSITE
             (eo->cmhook) ? 1 : 0, !eo->noredir
#else
             0, 0
#endif
             , EobjGetName(eo));
    }
}

static void
IPC_Reparent(const char *params)
{
    char            param1[FILEPATH_LEN_MAX];
    char            param2[FILEPATH_LEN_MAX];
    EWin           *ewin, *enew;

    if (!params)
        return;

    sscanf(params, "%100s %100s", param1, param2);

    ewin = EwinFindByExpr(param1);
    enew = EwinFindByExpr(param2);
    if (!ewin || !enew)
        IpcPrintf("No matching client or target EWin found\n");
    else
        EwinReparent(ewin, EwinGetClientWin(enew));
}

static void
IPC_Warp(const char *params)
{
    int             x, y;

    if (!params)
        return;

    x = y = 0;
    if (!strcmp(params, "?"))
    {
        EQueryPointer(NULL, &x, &y, NULL, NULL);
        IpcPrintf("Pointer location: %d %d\n", x, y);
    }
    else if (!strncmp(params, "abs", 3))
    {
        sscanf(params, "%*s %i %i", &x, &y);
        EWarpPointer(VROOT, x, y);
    }
    else if (!strncmp(params, "rel", 3))
    {
        sscanf(params, "%*s %i %i", &x, &y);
        EWarpPointer(NULL, x, y);
    }
    else if (!strncmp(params, "scr", 3))
    {
        x = (Dpy.screen + 1) % ScreenCount(disp);
        sscanf(params, "%*s %i", &x);
        FocusScreen(x);
    }
    else
    {
        sscanf(params, "%i %i", &x, &y);
        EWarpPointer(NULL, x, y);
    }
}

#if ENABLE_IPC_INSERT_KEYS
struct _keyset {
    const char     *sym;
    int             state;
    const char     *ch;
};

static const struct _keyset ks[] = {
    { "a", 0, "a" },
    { "b", 0, "b" },
    { "c", 0, "c" },
    { "d", 0, "d" },
    { "e", 0, "e" },
    { "f", 0, "f" },
    { "g", 0, "g" },
    { "h", 0, "h" },
    { "i", 0, "i" },
    { "j", 0, "j" },
    { "k", 0, "k" },
    { "l", 0, "l" },
    { "m", 0, "m" },
    { "n", 0, "n" },
    { "o", 0, "o" },
    { "p", 0, "p" },
    { "q", 0, "q" },
    { "r", 0, "r" },
    { "s", 0, "s" },
    { "t", 0, "t" },
    { "u", 0, "u" },
    { "v", 0, "v" },
    { "w", 0, "w" },
    { "x", 0, "x" },
    { "y", 0, "y" },
    { "z", 0, "z" },
    { "a", ShiftMask, "A" },
    { "b", ShiftMask, "B" },
    { "c", ShiftMask, "C" },
    { "d", ShiftMask, "D" },
    { "e", ShiftMask, "E" },
    { "f", ShiftMask, "F" },
    { "g", ShiftMask, "G" },
    { "h", ShiftMask, "H" },
    { "i", ShiftMask, "I" },
    { "j", ShiftMask, "J" },
    { "k", ShiftMask, "K" },
    { "l", ShiftMask, "L" },
    { "m", ShiftMask, "M" },
    { "n", ShiftMask, "N" },
    { "o", ShiftMask, "O" },
    { "p", ShiftMask, "P" },
    { "q", ShiftMask, "Q" },
    { "r", ShiftMask, "R" },
    { "s", ShiftMask, "S" },
    { "t", ShiftMask, "T" },
    { "u", ShiftMask, "U" },
    { "v", ShiftMask, "V" },
    { "w", ShiftMask, "W" },
    { "x", ShiftMask, "X" },
    { "y", ShiftMask, "Y" },
    { "z", ShiftMask, "Z" },
    { "grave", 0, "`" },
    { "1", 0, "1" },
    { "2", 0, "2" },
    { "3", 0, "3" },
    { "4", 0, "4" },
    { "5", 0, "5" },
    { "6", 0, "6" },
    { "7", 0, "7" },
    { "8", 0, "8" },
    { "9", 0, "9" },
    { "0", 0, "0" },
    { "minus", 0, "-" },
    { "equal", 0, "=" },
    { "bracketleft", 0, "[" },
    { "bracketright", 0, "]" },
    { "backslash", 0, "\\\\" },
    { "semicolon", 0, "\\s" },
    { "apostrophe", 0, "\\a" },
    { "comma", 0, "," },
    { "period", 0, "." },
    { "slash", 0, "/" },
    { "grave", ShiftMask, "~" },
    { "1", ShiftMask, "!" },
    { "2", ShiftMask, "@" },
    { "3", ShiftMask, "#" },
    { "4", ShiftMask, "$" },
    { "5", ShiftMask, "%" },
    { "6", ShiftMask, "^" },
    { "7", ShiftMask, "&" },
    { "8", ShiftMask, "*" },
    { "9", ShiftMask, "(" },
    { "0", ShiftMask, ")" },
    { "minus", ShiftMask, "_" },
    { "equal", ShiftMask, "+" },
    { "bracketleft", ShiftMask, "{" },
    { "bracketright", ShiftMask, "}" },
    { "backslash", ShiftMask, "|" },
    { "semicolon", ShiftMask, ":" },
    { "apostrophe", ShiftMask, "\\q" },
    { "comma", ShiftMask, "<" },
    { "period", ShiftMask, ">" },
    { "slash", ShiftMask, "?" },
    { "space", ShiftMask, " " },
    { "Return", ShiftMask, "\\n" },
    { "Tab", ShiftMask, "\\t" }
};

static void
IPC_InsertKeys(const char *params, Client *c __UNUSED__)
{
    EX_Window       win = 0;
    int             i, rev;
    const char     *s;
    XKeyEvent       ev;

    if (!params)
        return;

    s = params;
    XGetInputFocus(disp, &win, &rev);
    if (win == NoXID)
        return;

    SoundPlay(SOUND_INSERT_KEYS);
    ev.window = win;
    for (i = 0; i < (int)strlen(s); i++)
    {
        unsigned int    j;

        ev.x = Mode.events.cx;
        ev.y = Mode.events.cy;
        ev.x_root = Mode.events.cx;
        ev.y_root = Mode.events.cy;
        for (j = 0; j < E_ARRAY_SIZE(ks); j++)
        {
            if (strncmp(ks[j].ch, &(s[i]), strlen(ks[j].ch)))
                continue;

            i += strlen(ks[j].ch) - 1;
            ev.keycode = EKeynameToKeycode(ks[j].sym);
            ev.state = ks[j].state;
            ev.type = KeyPress;
            EXSendEvent(win, 0, (XEvent *) & ev);
            ev.type = KeyRelease;
            EXSendEvent(win, 0, (XEvent *) & ev);
            break;
        }
    }
}
#endif                          /* ENABLE_IPC_INSERT_KEYS */

/*
 * Compatibility stuff - DO NOT USE
 */
static int      IpcExec(const char *params);

static int
IPC_Compat(const char *params)
{
    int             ok = 0;
    char            param1[128], buf[FILEPATH_LEN_MAX];
    const char     *p;
    int             len;

    if (!params)
        goto done;

    len = 0;
    param1[0] = '\0';
    sscanf(params, "%127s %n", param1, &len);
    p = params + len;

    ok = 1;
    if (!strcmp(param1, "goto_desktop"))
    {
        if (*p == '?')
            IpcPrintf("Current Desktop: %d\n", DesksGetCurrentNum());
    }
    else if (!strcmp(param1, "num_desks"))
    {
        if (*p == '?')
            IpcPrintf("Number of Desks: %d\n", DesksGetNumber());
    }
    else if (!strcmp(param1, "use_bg"))
    {
        snprintf(buf, sizeof(buf), "bg use %s", p);
        IpcExec(buf);           /* Beware - recursive */
    }
#if !USE_COMPOSITE
    else if (!strcmp(param1, "cm"))
    {
        DialogOK(_("Message"), _("e16 was built without %s support"),
                 _("composite"));
    }
#endif
#if !ENABLE_SOUND
    else if (!strcmp(param1, "sound"))
    {
        DialogOK(_("Message"), _("e16 was built without %s support"),
                 _("sound"));
    }
#endif
    else
    {
        ok = 0;
    }

  done:
    return ok;
}

/* the IPC Array */

/* the format of an IPC member of the IPC array is as follows:
 * {
 *    NameOfMyFunction,
 *    "command_name",
 *    "quick-help explanation",
 *    "extended help data"
 *    "may go on for several lines, be sure\n"
 *    "to add line feeds when you need them and to \"quote\"\n"
 *    "properly"
 * }
 *
 * when you add a function into this array, make sure you also add it into
 * the declarations above and also put the function in this file.  PLEASE
 * if you add a new function in, add help to it also.  since my end goal
 * is going to be to have this whole IPC usable by an end-user or to your
 * scripter, it should be easy to learn to use without having to crack
 * open the source code.
 * --Mandrake
 */
static void     IPC_Help(const char *params);

/**INDENT-OFF**/
static const IpcItem IPCArray[] = {
   {
    IPC_Help,
    "help", "?",
    "Gives you this help screen",
    "Additional parameters will retrieve help on many topics - "
    "\"help <command>\"." "\n" "use \"help all\" for a list of commands.\n"},
   {
    IPC_Version,
    "version", "ver",
    "Displays the current version of Enlightenment running",
    NULL},
   {
    IPC_Nop,
    "nop", NULL,
    "IPC No-operation - returns nop",
    NULL},
#if ENABLE_DIALOGS
   {
    IPC_About, "about", NULL, "Show E info", NULL},
   {
    IPC_Cfg, "configure", "cfg", "Configuration dialogs", NULL},
#endif
   {
    IPC_Exec,
    "exec", NULL,
    "Execute program",
    "  exec <command>       Execute command\n"},
   {
    IPC_Restart,
    "restart", NULL,
    "Restart Enlightenment",
    NULL},
   {
    IPC_Exit,
    "exit", "q",
    "Exit Enlightenment",
    "  exit                 Exit immediately\n"
    "  exit logout          Show logout dialog\n"
    "  exit restart         Restart\n"
    "  exit theme <theme>   Restart with new theme\n"
    "  exit exec <program>  Exit and start program\n"},
   {
    IPC_ForceSave,
    "save_config", "s",
    "Force Enlightenment to save settings now",
    NULL},
   {
    IPC_WinOps,
    "win_op", "wop",
    "Change a property of one or more window(s)",
    "Use \"win_op <windowid> <property> <value>\" to change the property of a window\n"
    "You can use the \"window_list\" command to retrieve a list of available windows\n"
    "You can use ? after most of these commands to receive the current\n"
    "status of that flag\n"
    "Available win_op commands are:\n"
    "  win_op <windowid> border <BORDERNAME>\n"
    "  win_op <windowid> title <title>\n"
    "  win_op <windowid> <close/kill>\n"
    "  win_op <windowid> focus/alone\n"
    "  win_op <windowid> <iconify/shade/stick> [on|off]\n"
    "  win_op <windowid> pin <on/off/tgl> desk area_x area_y\n"
    "                    pin ?\n"
    "                    pin clear\n"
#if USE_COMPOSITE
    "  win_op <windowid> <fade/shadow>\n"
#endif
    "  win_op <windowid> desk <desktochangeto/next/prev>\n"
    "  win_op <windowid> area <x> <y>\n"
    "  win_op <windowid> <move/size> <x> <y> or <kbd/ptr>\n"
    "          (you can use ? and ?? to retreive client and frame locations)\n"
    "  win_op <windowid> <mr/sr> <x> <y>   (incremental move/size)\n"
    "  win_op <windowid> toggle_<width/height/size> <absolute/conservative/available/xinerama>\n"
    "  win_op <windowid> toggle_size <half_[N|S|E|W]>\n"
    "  win_op <windowid> <fullscreen/zoom> [on|off]\n"
    "  win_op <windowid> layer <0-100,4=normal>\n"
    "  win_op <windowid> <raise/lower>\n"
    "  win_op <windowid> opacity [+|-]<1-100(100=opaque)>   (+/-: incremental change)\n"
#if USE_COMPOSITE
    "  win_op <windowid> focused_opacity [+|-]<0-100(0=follow opacity, 100=opaque)>\n"
#endif
    "  win_op <windowid> snap <what>\n"
    "         <what>: all, none, auto, border, command, desktop, dialog, group, icon,\n"
    "                 layer, location, opacity, shade, shadow, size, sticky\n"
    "  win_op <windowid> <focusclick/never_use_area/no_button_grabs/skiplists> [on|off]\n"
    "  win_op <windowid> <no_app_focus/move/size> [on|off]\n"
    "  win_op <windowid> <no_user_close/move/size> [on|off]\n"
    "  win_op <windowid> <no_wm_focus> [on|off]\n"
    "  win_op <windowid> pass_ptr [on|off]\n"
    "<windowid> may be one of\n"
    "  \"*\" or \"current\" : the current window (group operation)\n"
    "  \"=\"              : the current window (non-group operation)\n"
    "  <xid>            : the window XID\n"
    "  <string>         : the first window with name containing <string>\n"
    "  <pattern>        : the window(s) with name matching <pattern>\n"
    "                     <pattern> is a string containing at least one *\n"
   },
   {
    IPC_WinList,
    "window_list", "wl",
    "Get a list of managed application windows",
    "The window list can be shown in a number of different formats:\n"
    "  window_list       \"windowid : title\"\n"
    "  window_list all   \"windowid : x y w x h :: desk : area_x area_y : title\"\n"
    "  window_list ext   \"windowid : title :: desk : area_x area_y : x y wxh\"\n"
    "  window_list prop  \"windowid : x y w x h :: desk : name class Title\"\n"},
   {
    IPC_MoveResize,
    "movres", "mr",
    "Show/set Window move/resize/geometry info modes",
    "  movres move   <?/opaque/lined/box/shaded/semi-solid/translucent>\n"
    "  movres resize <?/opaque/lined/box/shaded/semi-solid>\n"
    "  movres info   <?/never/center/corner>\n"},
#if ENABLE_DIALOGS
   {
    IPC_DialogOK,
    "dialog_ok", "dok",
    "Pop up a dialog box with an OK button",
    "Use \"dialog_ok <message>\" to pop up a dialog box\n"},
#endif /* ENABLE_DIALOGS */
   {
    IPC_Border, "border", NULL, "List available borders", NULL},
   {
    IPC_Screen, "screen", NULL, "Return screen information",
    "  screen list         List screens\n"
    "  screen size         Show current screen size\n"
    "  screen split nx ny  Simulate xinerama by subdividing screen\n"},
   {
    SnapshotsIpcFunc,
    "list_remember", "rl",
    "Retrieve a list of remembered windows and their attributes",
    SnapshotsIpcText},
   {
    IPC_Debug,
    "debug", NULL,
    "Set debug options",
    "  debug events <EvNo>:<EvNo>...\n"},
   {
    IPC_Set, "set", NULL, "Set configuration parameter", NULL},
   {
    IPC_Show, "show", "sh", "Show configuration parameter(s)", NULL},
   {
    IPC_EwinInfo, "win_info", "wi", "Show client window info", NULL},
   {
    IPC_ObjInfo, "obj_info", "oi", "Show window object info", NULL},
   {
    IPC_Reparent,
    "reparent", "rep",
    "Reparent window",
    "  reparent <windowid> <new parent>\n"},
   {
    IPC_Remember,
    "remember", NULL,
    "Remembers parameters for client windows (obsolete)",
    "  remember <windowid> <parameter>...\n"
    "For compatibility with epplets only. In stead use\n"
    "  wop <windowid> snap <parameter>...\n"},
   {
    IPC_Warp,
    "warp", NULL,
    "Warp/query pointer",
    "  warp ?               Get pointer position\n"
    "  warp abs <x> <y>     Set pointer position\n"
    "  warp rel <x> <y>     Move pointer relative to current position\n"
    "  warp scr [<i>]       Move pointer to other screen (default next)\n"
    "  warp <x> <y>         Same as \"warp rel\"\n"},
#if ENABLE_IPC_INSERT_KEYS
   {
    IPC_InsertKeys,
    "keys", NULL,
    "Send key events to focused window",
    "  keys <string>\n"},
#endif
};
/**INDENT-ON**/

static int      ipc_item_count = 0;
static const IpcItem **ipc_item_list = NULL;

static const IpcItem **
IPC_GetList(int *pnum)
{
    int             i, num;
    const IpcItem **lst;

    if (ipc_item_list)
    {
        /* Must be re-generated if modules are ever added/removed */
        *pnum = ipc_item_count;
        return ipc_item_list;
    }

    num = E_ARRAY_SIZE(IPCArray);
    lst = EMALLOC(const IpcItem *, num);

    for (i = 0; i < num; i++)
        lst[i] = &IPCArray[i];

    ModulesGetIpcItems(&lst, &num);

    ipc_item_count = num;
    ipc_item_list = lst;
    *pnum = num;
    return lst;
}

/* The IPC Handler */
/* this is the function that actually loops through the IPC array
 * and finds the command that you were trying to run, and then executes it.
 * you shouldn't have to touch this function
 * - Mandrake
 */
static int
IpcExec(const char *params)
{
    int             i, num, ok;
    char            cmd[128];
    const char     *prm;
    const IpcItem **lst, *ipc;

    if (EDebug(EDBUG_TYPE_IPC))
        Eprintf("%s: '%s'\n", __func__, params);

    cmd[0] = '\0';
    num = 0;
    if (params)
        sscanf(params, "%100s %n", cmd, &num);
    prm = (num > 0 && params[num]) ? params + num : NULL;

    ok = 1;

    if (cmd[0] == '\0')
        goto done;              /* Accept empty input */

    lst = IPC_GetList(&num);

    for (i = 0; i < num; i++)
    {
        ipc = lst[i];
        if (!(ipc->nick && !strcmp(cmd, ipc->nick)) && strcmp(cmd, ipc->name))
            continue;

        ipc->func(prm);

        goto done;
    }

    /* Commmand not found - check the legacy ones */
    ok = IPC_Compat(params);

    if (!ok)
        IpcPrintf("Unknown command: '%s'\n", params);

  done:
    return ok;
}

int
IpcExecReply(const char *params, IpcReplyFunc *reply, void *data)
{
    int             ok;

    IpcPrintInit();

    ok = IpcExec(params);

    if (EDebug(EDBUG_TYPE_IPC))
        Eprintf("%s: '%s'\n", __func__, IpcPrintGetBuffer());
    reply(data, IpcPrintGetBuffer());

    IpcPrintDone();

    return ok;
}

int
EFunc(EWin *ewin, const char *params)
{
    int             ok;

    SetContextEwin(ewin);
    ok = IpcExec(params);
    SetContextEwin(NULL);

    return ok;
}

static int
doEFuncDeferred(void *data)
{
    void          **prm = (void **)data;
    EWin           *ewin;

    ewin = (EWin *) prm[0];
    if (ewin && !EwinFindByPtr(ewin))
        goto done;

    EFunc(ewin, (const char *)prm[1]);

  done:
    Efree(prm[1]);
    Efree(data);

    return 0;
}

void
EFuncDefer(EWin *ewin, const char *cmd)
{
    void          **prm;

    prm = EMALLOC(void *, 2);

    if (!prm)
        return;
    prm[0] = ewin;
    prm[1] = Estrdup(cmd);

    TIMER_ADD_NP(0, doEFuncDeferred, prm);
}

static int
ipccmp(const void *p1, const void *p2)
{
    return strcmp((*(const IpcItem **)p1)->name, (*(const IpcItem **)p2)->name);
}

static void
IPC_Help(const char *params)
{
    int             i, j, k, num, nr;
    const IpcItem **lst, *ipc;
    const char     *nick;

    lst = IPC_GetList(&num);

    IpcPrintf(_("Enlightenment IPC Commands Help\n"));

    if (!params)
    {
        IpcPrintf(_("Use \"help all\" for descriptions of each command\n"
                    "Use \"help <command>\" for an individual description\n\n"));
        IpcPrintf(_("Commands currently available:\n"));

        qsort(lst, num, sizeof(IpcItem *), ipccmp);

        nr = (num + 2) / 3;
        for (i = 0; i < nr; i++)
        {
            for (j = 0; j < 3; j++)
            {
                k = i + j * nr;
                if (k >= num)
                    break;
                ipc = lst[k];
                nick = (ipc->nick) ? ipc->nick : "";
                IpcPrintf("  %-14s %-10s", ipc->name, nick);
            }
            IpcPrintf("\n");
        }
    }
    else if (!strcmp(params, "all"))
    {
        IpcPrintf(_
                  ("Use \"help full\" for full descriptions of each command\n"));
        IpcPrintf(_("Use \"help <command>\" for an individual description\n"));
        IpcPrintf(_("Commands currently available:\n"));
        IpcPrintf(_("         <command>     : <description>\n"));

        for (i = 0; i < num; i++)
        {
            ipc = lst[i];
            nick = (ipc->nick) ? ipc->nick : "";
            IpcPrintf("%18s %4s: %s\n", ipc->name, nick, ipc->help_text);
        }
    }
    else if (!strcmp(params, "full"))
    {
        IpcPrintf(_("Commands currently available:\n"));
        IpcPrintf(_("         <command>     : <description>\n"));

        for (i = 0; i < num; i++)
        {
            IpcPrintf("----------------------------------------\n");
            ipc = lst[i];
            nick = (ipc->nick) ? ipc->nick : "";
            IpcPrintf("%18s %4s: %s\n", ipc->name, nick, ipc->help_text);
            if (ipc->extended_help_text)
                IpcPrintf("%s", ipc->extended_help_text);
        }
    }
    else
    {
        for (i = 0; i < num; i++)
        {
            ipc = lst[i];
            if (strcmp(params, ipc->name) &&
                (!ipc->nick || strcmp(params, ipc->nick)))
                continue;

            nick = (ipc->nick) ? ipc->nick : "";
            IpcPrintf("----------------------------------------\n");
            IpcPrintf("%18s %4s: %s\n", ipc->name, nick, ipc->help_text);
            IpcPrintf("----------------------------------------\n");
            if (ipc->extended_help_text)
                IpcPrintf("%s", ipc->extended_help_text);
        }
    }
}
