/*
 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
 * Copyright (C) 2004-2025 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 "animation.h"
#include "borders.h"
#include "desktops.h"
#include "emodule.h"
#include "eobj.h"
#include "events.h"
#include "ewins.h"
#include "ewin-ops.h"
#include "focus.h"
#include "groups.h"
#include "hints.h"
#include "screen.h"
#include "snaps.h"
#include "xwin.h"

static const WinOp winops[] = {
    { "border", 2, 1, 1, EWIN_OP_BORDER },
    { "title", 2, 1, 1, EWIN_OP_TITLE },

    { "focusclick", 0, 1, 1, EWIN_OP_FOCUS_CLICK },     /* Place before "focus" */

    { "close", 2, 1, 0, EWIN_OP_CLOSE },
    { "kill", 0, 1, 0, EWIN_OP_KILL },
    { "iconify", 2, 1, 1, EWIN_OP_ICONIFY },
    { "alone", 0, 1, 0, EWIN_OP_ALONE },
    { "opacity", 2, 1, 1, EWIN_OP_OPACITY },
    { "focused_opacity", 0, 1, 1, EWIN_OP_FOCUSED_OPACITY },
    { "shadow", 0, 1, 1, EWIN_OP_SHADOW },      /* Place before "shade" */
    { "shade", 2, 1, 1, EWIN_OP_SHADE },
    { "stick", 2, 1, 1, EWIN_OP_STICK },
    { "pin", 0, 1, 1, EWIN_OP_PIN },
    { "focus", 2, 1, 0, EWIN_OP_FOCUS },

    { "desk", 2, 1, 1, EWIN_OP_DESK },
    { "area", 2, 1, 1, EWIN_OP_AREA },
    { "move", 2, 1, 1, EWIN_OP_MOVE },
    { "size", 2, 1, 1, EWIN_OP_SIZE },
    { "sz", 2, 1, 1, EWIN_OP_SIZE },
    { "move_relative", 0, 1, 0, EWIN_OP_MOVE_REL },
    { "mr", 2, 1, 0, EWIN_OP_MOVE_REL },
    { "resize_relative", 0, 1, 0, EWIN_OP_SIZE_REL },
    { "sr", 2, 1, 0, EWIN_OP_SIZE_REL },

    { "toggle_width", 0, 1, 0, EWIN_OP_MAX_WIDTH },
    { "tw", 2, 1, 0, EWIN_OP_MAX_WIDTH },
    { "toggle_height", 0, 1, 0, EWIN_OP_MAX_HEIGHT },
    { "th", 0, 1, 0, EWIN_OP_MAX_HEIGHT },
    { "toggle_size", 0, 1, 0, EWIN_OP_MAX_SIZE },
    { "ts", 2, 1, 0, EWIN_OP_MAX_SIZE },
    { "fullscreen", 2, 1, 1, EWIN_OP_FULLSCREEN },
    { "zoom", 2, 1, 0, EWIN_OP_ZOOM },

    { "layer", 2, 1, 1, EWIN_OP_LAYER },
    { "raise", 2, 1, 0, EWIN_OP_RAISE },
    { "lower", 2, 1, 0, EWIN_OP_LOWER },

    { "snap", 0, 1, 0, EWIN_OP_SNAP },

    { "ignore_arrange", 0, 1, 1, EWIN_OP_IGNORE_ARRANGE },
    { "never_use_area", 0, 1, 1, EWIN_OP_NEVER_USE_AREA },
    { "no_button_grabs", 0, 1, 1, EWIN_OP_NO_BUTTON_GRABS },
    { "skiplists", 4, 1, 1, EWIN_OP_SKIP_LISTS },
    { "autoshade", 0, 1, 1, EWIN_OP_AUTOSHADE },
    { "pass_ptr", 0, 1, 1, EWIN_OP_PASS_POINTER },

    { "no_app_focus", 0, 1, 1, EWIN_OP_INH_APP_FOCUS },
    { "no_app_move", 0, 1, 1, EWIN_OP_INH_APP_MOVE },
    { "no_app_size", 0, 1, 1, EWIN_OP_INH_APP_SIZE },
    { "no_user_close", 0, 1, 1, EWIN_OP_INH_USER_CLOSE },
    { "no_user_move", 0, 1, 1, EWIN_OP_INH_USER_MOVE },
    { "no_user_size", 0, 1, 1, EWIN_OP_INH_USER_SIZE },
    { "no_wm_focus", 0, 1, 1, EWIN_OP_INH_WM_FOCUS },

    { "fade", 0, 1, 1, EWIN_OP_FADE },
    { "no_redir", 4, 1, 1, EWIN_OP_NO_REDIRECT },
    { "no_argb", 0, 1, 1, EWIN_OP_NO_ARGB },

    { NULL, 0, 0, 0, EWIN_OP_INVALID }  /* Terminator */
};

const WinOp    *
EwinOpFind(const char *op)
{
    const WinOp    *wop;

    wop = winops;
    for (; wop->name; wop++)
    {
        if (wop->len)
        {
            if (!strncmp(op, wop->name, wop->len))
                return wop;
        }
        else
        {
            if (!strcmp(op, wop->name))
                return wop;
        }
    }

    return NULL;
}

static void
EwinDetermineArea(EWin *ewin)
{
    Desk           *dsk;
    int             ax, ay;

    dsk = EoGetDesk(ewin);
    ewin->vx = dsk->current_area_x * EoGetW(dsk) + EoGetX(ewin);
    ewin->vy = dsk->current_area_y * EoGetH(dsk) + EoGetY(ewin);

    ax = (ewin->vx + EoGetW(ewin) / 2) / EoGetW(dsk);
    ay = (ewin->vy + EoGetH(ewin) / 2) / EoGetH(dsk);
    DesksFixArea(&ax, &ay);

    if (ax != ewin->area_x || ay != ewin->area_y)
    {
        ewin->area_x = ax;
        ewin->area_y = ay;
        HintsSetWindowArea(ewin);
    }
}

#define MRF_DESK    (1 << 0)    /* 0x01 */
#define MRF_MOVE    (1 << 1)    /* 0x02 */
#define MRF_RESIZE  (1 << 2)    /* 0x04 */
#define MRF_RAISE   (1 << 3)    /* 0x08 */
#define MRF_FLOAT   (1 << 4)    /* 0x10 */
#define MRF_UNFLOAT (1 << 5)    /* 0x20 */

static void
doEwinMoveResize(EWin *ewin, Desk *dsk, int x, int y, int w, int h, int flags)
{
    static int      call_depth = 0;
    int             dx, dy, sw, sh, xo, yo;
    char            move, resize, reparent, raise, floating, configure;
    EWin          **lst;
    int             i, num;
    Desk           *pdesk;
    const EImageBorder *pad;

    if (ewin->state.zoomed)
        return;

    if (call_depth > 256)
        return;
    call_depth++;

    if (EDebug(EDBUG_TYPE_MOVERESIZE))
        Eprintf("%s(%d,%d) %#x f=%x d=%d %d+%d %d*%d %s\n", __func__,
                call_depth, Mode.mode, EwinGetClientXwin(ewin), flags,
                (dsk) ? (int)dsk->num : -1, x, y, w, h, EwinGetTitle(ewin));

    pdesk = (ewin->o.stacked >= 0) ? EoGetDesk(ewin) : NULL;
    reparent = move = resize = raise = 0;
    floating = EoIsFloating(ewin);

    pad = BorderGetSize(ewin->border);

    if (flags & MRF_FLOAT)
    {
        if (floating == 0)
        {
            dsk = (!pdesk) ? EoGetDesk(ewin) : pdesk;
            floating = 1;
        }
        else if (floating == 1)
        {
            dsk = DeskGet(0);
            floating = 2;
        }
        flags |= MRF_RAISE;
    }
    else if (flags & MRF_UNFLOAT)
    {
        floating = 0;
        flags |= MRF_RAISE;
    }
    else if (EoIsSticky(ewin) && !floating)
    {
        dsk = DesksGetCurrent();
    }
    else if (!dsk)
    {
        dsk = EoGetDesk(ewin);
    }

    if (dsk != pdesk)
    {
        reparent = 1;
        flags |= MRF_DESK;
    }

    if (Mode.mode == MODE_NONE && !(flags & MRF_NOCHECK_ONSCREEN))
    {
        /* Don't throw windows offscreen */
        sw = WinGetW(VROOT);
        sh = WinGetH(VROOT);
        if (EoIsSticky(ewin))
        {
            xo = yo = 0;
        }
        else
        {
            int             ax, ay;

            DeskGetArea(dsk, &ax, &ay);
            xo = -ax * sw;
            yo = -ay * sh;
            sw *= Conf.desks.areas_nx;
            sh *= Conf.desks.areas_ny;
        }

        /* Keep at least 8 pixels (or window dimension) on-screen */
        dx = 8;
        if (dx > EoGetW(ewin))
            dx = EoGetW(ewin);
        dy = 8;
        if (dy > EoGetH(ewin))
            dy = EoGetH(ewin);

        if (x < xo - EoGetW(ewin) + dx)
            x = xo - EoGetW(ewin) + dx;
        else if (x > xo + sw - dx)
            x = xo + sw - dx;
        if (y < yo - EoGetH(ewin) + dy)
            y = yo - EoGetH(ewin) + dy;
        else if (y > yo + sh - dy)
            y = yo + sh - dy;
    }

    if (flags & MRF_RAISE)
        raise = 1;

    if (!(flags & MRF_MOVE))
    {
        x = EoGetX(ewin);
        y = EoGetY(ewin);
    }

    if (!(flags & MRF_RESIZE))
    {
        w = EoGetW(ewin);
        h = EoGetH(ewin);
    }
    else
    {
        if (w > 32000)
            w = 32000;
        if (h > 32000)
            h = 32000;
        if (ewin->ops && ewin->ops->Layout)
        {
            ewin->ops->Layout(ewin, &x, &y, &w, &h);
        }
        else
        {
            ICCCM_SizeMatch(ewin, w, h, &w, &h);
        }
        if (w <= 0)
            w = 1;
        if (h <= 0)
            h = 1;

        if ((w != ewin->client.w) || (h != ewin->client.h))
            resize = 2;
        ewin->client.w = w;
        ewin->client.h = h;

        if (ewin->state.shaded)
        {
            w = EoGetW(ewin);
            h = EoGetH(ewin);
            if (resize)
                EwinBorderMinShadeSize(ewin, &w, &h);
        }
        else
        {
            w = ewin->client.w + pad->left + pad->right;
            h = ewin->client.h + pad->top + pad->bottom;
        }
    }

    /* Clamp window position or weirdness may happen.
     * Not sure where the exact limit is. */
    if (x < -32000)
        x = -32000;
    else if (x > 32000 - w)
        x = 32000 - w;
    if (y < -32000)
        y = -32000;
    else if (y > 32000 - h)
        y = 32000 - h;

    dx = x - EoGetX(ewin);
    dy = y - EoGetY(ewin);
    if ((dx != 0) || (dy != 0))
        move = 1;
    ewin->client.x = x + pad->left;
    ewin->client.y = y + pad->top;

#if 0
    Eprintf("repa=%d float=%d raise=%d move=%d resz=%d\n",
            reparent, floating, raise, move, resize);
#endif
    if (EoIsShown(ewin) && (move || reparent))
        ModulesSignal(ESIGNAL_EWIN_CHANGE, ewin);

    if (reparent)
    {
        EoReparent(ewin, EoObj(dsk), x, y);
        if (flags & MRF_RESIZE)
            EoResize(ewin, w, h);
    }
    else
    {
        EoMoveResize(ewin, x, y, w, h);
    }

    configure = 0;
    if (Mode.mode == MODE_NONE || resize || Conf.movres.update_while_moving)
    {
        configure = 1;
#if USE_XSYNC
        if (Conf.movres.enable_sync_request)
            EwinSyncRequestSend(ewin);
#endif
    }

    if (flags & MRF_RESIZE)
    {
        if (!ewin->state.shaded)
            EMoveResizeWindow(EwinGetClientConWin(ewin), pad->left, pad->top,
                              ewin->client.w, ewin->client.h);
#if USE_CONTAINER_WIN
        EMoveResizeWindow(EwinGetClientWin(ewin), 0, 0, ewin->client.w,
                          ewin->client.h);
#endif
        EwinBorderCalcSizes(ewin, 0);
        if (resize && ewin->state.shaped)
            ewin->update.shape = 1;
    }

    EwinPropagateShapes(ewin);

    /* Clear maximized state on move or resize */
    if ((move || resize) && !(flags & MRF_KEEP_MAXIMIZED))
    {
        if (ewin->state.maximized_horz || ewin->state.maximized_vert)
        {
            ewin->state.maximized_horz = 0;
            ewin->state.maximized_vert = 0;
            HintsSetWindowState(ewin);
        }
    }

    if (raise)
    {
        EoSetFloating(ewin, floating);
        EwinRaise(ewin);
    }

    if (Mode.mode == MODE_NONE || Conf.movres.update_while_moving)
        ICCCM_Configure(ewin);

    if (configure)
    {
#if USE_XSYNC
        if (Conf.movres.enable_sync_request)
            EwinSyncRequestWait(ewin);
#endif
    }

    if (flags & (MRF_DESK | MRF_MOVE | MRF_FLOAT | MRF_UNFLOAT))
    {
        lst = EwinListTransients(ewin, &num, 0);
        for (i = 0; i < num; i++)
            doEwinMoveResize(lst[i], dsk, EoGetX(lst[i]) + dx,
                             EoGetY(lst[i]) + dy, 0, 0,
                             flags & (MRF_DESK | MRF_MOVE |
                                      MRF_FLOAT | MRF_UNFLOAT |
                                      MRF_NOCHECK_ONSCREEN |
                                      MRF_KEEP_MAXIMIZED));
        Efree(lst);
    }

    EwinDetermineArea(ewin);
    if (Mode.op_source == OPSRC_USER)
        EwinSetPlacementGravity(ewin, x, y);

    if ((flags & (MRF_DESK | MRF_MOVE | MRF_RESIZE)) &&
        ewin->ops && ewin->ops->MoveResize)
        ewin->ops->MoveResize(ewin, resize);

    if (Mode.mode == MODE_NONE)
    {
        SnapshotEwinUpdate(ewin, SNAP_USE_POS | SNAP_USE_SIZE);

        if (EoIsShown(ewin))
            ModulesSignal(ESIGNAL_EWIN_CHANGE, ewin);
    }

    if (dsk != pdesk)
    {
        HintsSetWindowDesktop(ewin);
        SnapshotEwinUpdate(ewin, SNAP_USE_DESK);
    }

    call_depth--;
}

void
EwinMove(EWin *ewin, int x, int y, int flags)
{
    doEwinMoveResize(ewin, NULL, x, y, 0, 0,
                     MRF_MOVE | (flags & MRF_NOCHECK_ONSCREEN));
}

void
EwinResize(EWin *ewin, int w, int h, int flags)
{
    doEwinMoveResize(ewin, NULL, 0, 0, w, h,
                     MRF_RESIZE | (flags & MRF_KEEP_MAXIMIZED));
}

void
EwinMoveResize(EWin *ewin, int x, int y, int w, int h, int flags)
{
    doEwinMoveResize(ewin, NULL, x, y, w, h,
                     MRF_MOVE | MRF_RESIZE |
                     (flags & (MRF_NOCHECK_ONSCREEN | MRF_KEEP_MAXIMIZED)));
}

void
EwinMoveResizeWithGravity(EWin *ewin, int x, int y, int w, int h, int grav)
{
    EwinGetPosition(ewin, x, y, grav, &x, &y);
    doEwinMoveResize(ewin, NULL, x, y, w, h, MRF_MOVE | MRF_RESIZE);
}

void
EwinMoveToDesktop(EWin *ewin, Desk *dsk)
{
    doEwinMoveResize(ewin, dsk, 0, 0, 0, 0, MRF_DESK);
}

void
EwinMoveToDesktopAt(EWin *ewin, Desk *dsk, int x, int y)
{
    doEwinMoveResize(ewin, dsk, x, y, 0, 0, MRF_DESK | MRF_MOVE);
}

void
EwinMoveToDesktopAtNocheck(EWin *ewin, Desk *dsk, int x, int y)
{
    doEwinMoveResize(ewin, dsk, x, y, 0, 0,
                     MRF_DESK | MRF_MOVE | MRF_NOCHECK_ONSCREEN);
}

void
EwinOpMove(EWin *ewin, int source, int x, int y)
{
    Mode.op_source = source;
    EwinMove(ewin, x, y, 0);
    Mode.op_source = OPSRC_NA;
}

void
EwinOpResize(EWin *ewin, int source, int w, int h)
{
    Mode.op_source = source;
    EwinResize(ewin, w, h, 0);
    Mode.op_source = OPSRC_NA;
}

void
EwinOpMoveResize(EWin *ewin, int source, int x, int y, int w, int h)
{
    Mode.op_source = source;
    EwinMoveResize(ewin, x, y, w, h, 0);
    Mode.op_source = OPSRC_NA;
}

void
EwinOpMoveToDesktopAt(EWin *ewin, int source, Desk *dsk, int x, int y)
{
    Mode.op_source = source;
    EwinMoveToDesktopAt(ewin, dsk, x, y);
    Mode.op_source = OPSRC_NA;
}

void
EwinOpFloatAt(EWin *ewin, int source, int x, int y)
{
    Mode.op_source = source;
    doEwinMoveResize(ewin, EoGetDesk(ewin), x, y, 0, 0, MRF_MOVE | MRF_FLOAT);
    Mode.op_source = OPSRC_NA;
}

void
EwinOpUnfloatAt(EWin *ewin, int source, Desk *dsk, int x, int y)
{
    Mode.op_source = source;
    doEwinMoveResize(ewin, dsk, x, y, 0, 0, MRF_MOVE | MRF_UNFLOAT);
    Mode.op_source = OPSRC_NA;
}

void
EwinIconify(EWin *ewin)
{
    static int      call_depth = 0;
    EWin          **lst, *e;
    int             i, num;

    if (!ewin)
        return;

    if (ewin->state.inhibit_iconify)
        return;

    if (ewin->state.state != EWIN_STATE_MAPPED)
        return;

    if (call_depth > 256)
        return;
    call_depth++;

    Zoom(ewin, 0);

    /* Save position at which the window was iconified */
    EwinRememberPositionSet(ewin);

    if (!EwinIsTransient(ewin))
        ModulesSignal(ESIGNAL_EWIN_ICONIFY, ewin);

    ewin->state.iconified = 1;
    EwinHide(ewin);

    ICCCM_Iconify(ewin);

    lst = EwinListTransients(ewin, &num, 0);
    for (i = 0; i < num; i++)
    {
        e = lst[i];
        if (e->state.iconified)
            continue;

        EwinIconify(e);
    }
    Efree(lst);

    EwinStateUpdate(ewin);
    HintsSetWindowState(ewin);

    call_depth--;
}

void
EwinAlone(EWin *ewin)
{
    EWin           *const *lst, *item;
    int             i, num;

    lst = EwinListGetForDesk(&num, EoGetDesk(ewin));

    for (i = 0; i < num; i++)
    {
        item = lst[i];

        if (item == ewin || EwinIsTransient(item) ||
            item->state.iconified || item->state.donthide ||
            item->area_x != ewin->area_x || item->area_y != ewin->area_y)
            continue;
        EwinIconify(item);
    }
}

static void
GetOnScreenPos(int x, int y, int w, int h, int *px, int *py)
{
    int             dx, dy;

    if (x + w > 4 && x <= WinGetW(VROOT) - 4 &&
        y + h > 4 && y <= WinGetH(VROOT) - 4)
        goto done;

    dx = w / 2;
    dy = h / 2;
    x = (x + dx) % WinGetW(VROOT);
    if (x < 0)
        x += WinGetW(VROOT);
    x -= dx;
    y = (y + dy) % WinGetH(VROOT);
    if (y < 0)
        y += WinGetH(VROOT);
    y -= dy;

  done:
    *px = x;
    *py = y;
}

static void
EwinDeIconify1(EWin *ewin, int dx, int dy)
{
    static int      call_depth = 0;
    EWin          **lst, *e;
    int             i, num;
    int             x, y;

    if (call_depth > 256)
        return;
    call_depth++;

    if (ewin->state.state != EWIN_STATE_ICONIC || !ewin->state.iconified)
        return;

    EwinRememberPositionGet(ewin, DesksGetCurrent(), &x, &y);

    EwinMoveToDesktopAt(ewin, DesksGetCurrent(), x + dx, y + dy);

    if (!EwinIsTransient(ewin))
        ModulesSignal(ESIGNAL_EWIN_DEICONIFY, ewin);

    ewin->state.iconified = 0;
    ewin->state.showingdesk = 0;

    EwinRaise(ewin);
    EwinShow(ewin);
    ICCCM_DeIconify(ewin);

    lst = EwinListTransients(ewin, &num, 0);
    for (i = 0; i < num; i++)
    {
        e = lst[i];
        if (!e->state.iconified)
            continue;

        EwinDeIconify1(e, dx, dy);
    }
    Efree(lst);

    EwinStateUpdate(ewin);
    HintsSetWindowState(ewin);

    call_depth--;
}

void
EwinDeIconify(EWin *ewin)
{
    int             x, y, ox, oy, dx, dy;

    EwinRememberPositionGet(ewin, DesksGetCurrent(), &x, &y);
    ox = x;
    oy = y;

    /* If we iconified an offscreen window, get it back on screen */
    if (!ewin->state.showingdesk)
        GetOnScreenPos(x, y, EoGetW(ewin), EoGetH(ewin), &x, &y);

    dx = x - ox;
    dy = y - oy;

    EwinDeIconify1(ewin, dx, dy);
}

static void
EwinUnStick(EWin *ewin)
{
    if (!EoIsSticky(ewin))
        return;

    SoundPlay(SOUND_WINDOW_UNSTICK);

    EoSetSticky(ewin, 0);
    EwinMoveToDesktopAt(ewin, DesksGetCurrent(), EoGetX(ewin), EoGetY(ewin));
    EwinBorderUpdateState(ewin);
    EwinStateUpdate(ewin);
    HintsSetWindowState(ewin);
    HintsSetWindowDesktop(ewin);
    SnapshotEwinUpdate(ewin, SNAP_USE_STICKY);
}

static void
EwinStick(EWin *ewin)
{
    int             x, y, dx, dy;

    if (EoIsSticky(ewin))
        return;

    SoundPlay(SOUND_WINDOW_STICK);

    /* Avoid "losing" windows made sticky while not in the current viewport */
    dx = EoGetW(ewin) / 2;
    dy = EoGetH(ewin) / 2;
    x = (EoGetX(ewin) + dx) % WinGetW(VROOT);
    if (x < 0)
        x += WinGetW(VROOT);
    x -= dx;
    y = (EoGetY(ewin) + dy) % WinGetH(VROOT);
    if (y < 0)
        y += WinGetH(VROOT);
    y -= dy;

    EoSetSticky(ewin, 1);
    EwinMoveToDesktopAt(ewin, DesksGetCurrent(), x, y);
    EwinBorderUpdateState(ewin);
    EwinStateUpdate(ewin);
    HintsSetWindowState(ewin);
    HintsSetWindowDesktop(ewin);
    SnapshotEwinUpdate(ewin, SNAP_USE_STICKY);
}

#define SHADE_LEFT  0           /* __LEFT */
#define SHADE_RIGHT 1           /* __RIGHT */
#define SHADE_UP    2           /* __UP/__TOP */
#define SHADE_DOWN  3           /* __DOWN/__BOTTOM */

void
EwinInstantShade(EWin *ewin, int force)
{
    int             x, y, w, h;
    int             minw, minh;

    if (ewin->state.inhibit_shade)
        return;
    if (ewin->state.shaded && !force)
        return;

    x = EoGetX(ewin);
    y = EoGetY(ewin);
    w = EoGetW(ewin);
    h = EoGetH(ewin);

    EwinBorderMinShadeSize(ewin, &minw, &minh);

    switch (BorderGetShadedir(ewin->border))
    {
    default:
    case SHADE_LEFT:
        w = minw;
        break;
    case SHADE_RIGHT:
        if (!force)
            x = x + w - minw;
        w = minw;
        break;
    case SHADE_UP:
        h = minh;
        break;
    case SHADE_DOWN:
        if (!force)
            y = y + h - minh;
        h = minh;
        break;
    }

    ewin->state.shaded = 2;
    EoMoveResize(ewin, x, y, w, h);
#if USE_CONTAINER_WIN
    EMoveResizeWindow(ewin->win_container, -30, -30, 1, 1);
#else
    EMoveResizeWindow(EwinGetClientWin(ewin), 100, 100,
                      ewin->client.w, ewin->client.h);
#endif
    EwinMoveResize(ewin, x, y, ewin->client.w, ewin->client.h,
                   MRF_KEEP_MAXIMIZED);
}

void
EwinInstantUnShade(EWin *ewin)
{
    XSetWindowAttributes att;
    int             x, y, w, h;
    const EImageBorder *pad;

    if (ewin->state.zoomed)
        return;
    if (!ewin->state.shaded)
        return;

    x = EoGetX(ewin);
    y = EoGetY(ewin);

    pad = BorderGetSize(ewin->border);

    switch (BorderGetShadedir(ewin->border))
    {
    default:
        break;
    case SHADE_RIGHT:
        w = ewin->client.w + pad->left + pad->right;
        x = x + EoGetW(ewin) - w;
        break;
    case SHADE_DOWN:
        h = ewin->client.h + pad->top + pad->bottom;
        y = y + EoGetH(ewin) - h;
        break;
    }

    /* Reset gravity */
    att.win_gravity = NorthWestGravity;
    EChangeWindowAttributes(EwinGetClientWin(ewin), CWWinGravity, &att);
    /* Force move after gravity change */
    EWindowSetGeometry(EwinGetClientWin(ewin), -1, -1,
                       ewin->client.w, ewin->client.h, 0);

    ewin->state.shaded = 0;
    EwinMoveResize(ewin, x, y, ewin->client.w, ewin->client.h,
                   MRF_KEEP_MAXIMIZED);
}

#if USE_CONTAINER_WIN
#define _EWIN_ADJUST_SHAPE(ewin, xo, yo) \
  do { \
    EShapeSetShape(ewin->win_container, xo, yo, EwinGetClientWin(ewin)); \
    ewin->update.shape = 1; \
  } while (0)
#else
#define _EWIN_ADJUST_SHAPE(ewin, xo, yo) \
  do { \
    EShapeUpdate(EwinGetClientWin(ewin)); \
    ewin->update.shape = 1; \
  } while (0)
#endif

typedef struct {
    int             x, y, w, h;
} _xywh;

typedef struct {
    EWin           *ewin;
    char            doshade;
    _xywh           start;
    _xywh           final;
    int             a, b, c;
} _ewin_shade_data;

static void
_EwinShadeStart(_ewin_shade_data *esd)
{
    EWin           *ewin = esd->ewin;
    int             minw, minh;
    XSetWindowAttributes att;
    const EImageBorder *pad;

    esd->start.x = EoGetX(ewin);
    esd->start.y = EoGetY(ewin);
    esd->start.w = EoGetW(ewin);
    esd->start.h = EoGetH(ewin);
    esd->final = esd->start;
    esd->doshade = 1;

    ewin->state.shading = 1;

    EwinBorderMinShadeSize(ewin, &minw, &minh);

    pad = BorderGetSize(ewin->border);

    switch (BorderGetShadedir(ewin->border))
    {
    default:
    case SHADE_LEFT:
        att.win_gravity = EastGravity;
        esd->a = esd->start.w;
        esd->b = pad->left + pad->right;
        esd->final.w = minw;
        break;
    case SHADE_RIGHT:
        att.win_gravity = WestGravity;
        esd->a = esd->start.w;
        esd->b = pad->left + pad->right;
        esd->c = esd->start.x + esd->start.w;
        esd->final.x = esd->c - minw;
        esd->final.w = minw;
        break;
    case SHADE_UP:
        att.win_gravity = SouthGravity;
        esd->a = esd->start.h;
        esd->b = pad->top + pad->bottom;
        esd->final.h = minh;
        break;
    case SHADE_DOWN:
        att.win_gravity = SouthGravity;
        esd->a = esd->start.h;
        esd->b = pad->top + pad->bottom;
        esd->c = esd->start.y + esd->start.h;
        esd->final.y = esd->c - minh;
        esd->final.h = minh;
        break;
    }

    EChangeWindowAttributes(EwinGetClientWin(ewin), CWWinGravity, &att);
}

static void
_EwinShadeEnd(_ewin_shade_data *esd)
{
    EWin           *ewin = esd->ewin;

    EoMoveResize(ewin, esd->final.x, esd->final.y, esd->final.w, esd->final.h);
    ewin->state.shaded = 2;
#if USE_CONTAINER_WIN
    EMoveResizeWindow(ewin->win_container, -30, -30, 1, 1);
#else
    EMoveResizeWindow(EwinGetClientWin(ewin), 100, 100,
                      ewin->client.w, ewin->client.h);
    ewin->update.shape = 1;
#endif
    if (ewin->state.shaped)
        _EWIN_ADJUST_SHAPE(ewin, 0, 0);

    EwinMoveResize(ewin, EoGetX(ewin), EoGetY(ewin),
                   ewin->client.w, ewin->client.h, MRF_KEEP_MAXIMIZED);

    ewin->state.shading = 0;

    EwinStateUpdate(ewin);
    HintsSetWindowState(ewin);
    SnapshotEwinUpdate(ewin, SNAP_USE_SHADED);
}

static int      _EwinShadeRun(EObj * eobj, int remaining, void *state);

void
EwinShade(EWin *ewin)
{
    Animator       *an;
    _ewin_shade_data esd;
    int             duration;

    if (ewin->state.inhibit_shade)
        return;
    if (ewin->state.shaded || ewin->state.shading)
        return;

    esd.ewin = ewin;
    _EwinShadeStart(&esd);
    if ((Conf.shading.animate) || (ewin->type == EWIN_TYPE_MENU))
    {
        if (Conf.shading.speed < SPEED_MIN)
            Conf.shading.speed = SPEED_MIN;
        duration = 1000000 / Conf.shading.speed;
        an = AnimatorAdd(&ewin->o, ANIM_SHADE, _EwinShadeRun, duration, 0,
                         sizeof(esd), &esd);
        AnimatorSetSound(an, SOUND_SHADE, SOUND_NONE);
    }
    else
        _EwinShadeEnd(&esd);
}

static void
_EwinUnshadeStart(_ewin_shade_data *esd)
{
    EWin           *ewin = esd->ewin;
    int cow         __UNUSED__, coh __UNUSED__, clx, cly;
    XSetWindowAttributes att;
    const EImageBorder *pad;

    esd->start.x = EoGetX(ewin);
    esd->start.y = EoGetY(ewin);
    esd->start.w = EoGetW(ewin);
    esd->start.h = EoGetH(ewin);
    esd->final = esd->start;
    esd->doshade = 0;

    ewin->state.shading = 1;
    ewin->state.shaded = 0;

    cow = ewin->client.w;
    coh = ewin->client.h;

    clx = cly = 0;

    pad = BorderGetSize(ewin->border);

    switch (BorderGetShadedir(ewin->border))
    {
    default:
    case SHADE_LEFT:
        att.win_gravity = NorthEastGravity;
        esd->a = pad->left + pad->right;
        esd->b = ewin->client.w + esd->a;
        esd->c = 0;             /* Not used */
        esd->final.w = esd->b;
        cow = 1;
        clx = -ewin->client.w;
        break;
    case SHADE_RIGHT:
        att.win_gravity = NorthWestGravity;
        esd->a = pad->left + pad->right;
        esd->b = ewin->client.w + esd->a;
        esd->c = esd->start.x + esd->start.w;   /* NB! w != a is possible */
        esd->final.x = esd->c - esd->b;
        esd->final.w = esd->b;
        cow = 1;
        break;
    case SHADE_UP:
        att.win_gravity = SouthEastGravity;
        esd->a = pad->top + pad->bottom;
        esd->b = ewin->client.h + esd->a;
        esd->c = 0;             /* Not used */
        esd->final.h = esd->b;
        coh = 1;
        cly = 1 - ewin->client.h;
        break;
    case SHADE_DOWN:
        att.win_gravity = SouthEastGravity;
        esd->a = pad->top + pad->bottom;
        esd->b = ewin->client.h + esd->a;
        esd->c = esd->start.y + esd->start.h;   /* NB! h != a is possible */
        esd->final.y = esd->c - esd->b;
        esd->final.h = esd->b;
        coh = 1;
        cly = 1 - ewin->client.h;
        break;
    }

    EChangeWindowAttributes(EwinGetClientWin(ewin), CWWinGravity, &att);
    EWindowSync(EwinGetClientWin(ewin));        /* Gravity - recache */
#if USE_CONTAINER_WIN
    EMoveResizeWindow(ewin->win_container, pad->left, pad->top, cow, coh);
    EMoveResizeWindow(EwinGetClientWin(ewin), clx, cly,
                      ewin->client.w, ewin->client.h);
#else
    EMoveResizeWindow(EwinGetClientWin(ewin), pad->left + clx, pad->top + cly,
                      ewin->client.w, ewin->client.h);
#endif
}

static void
_EwinUnshadeEnd(_ewin_shade_data *esd)
{
    EWin           *ewin = esd->ewin;
    XSetWindowAttributes att;
#if USE_CONTAINER_WIN
    const EImageBorder *pad = BorderGetSize(ewin->border);
#endif

    EoMoveResize(ewin, esd->final.x, esd->final.y, esd->final.w, esd->final.h);

    /* Reset gravity */
    att.win_gravity = NorthWestGravity;
    EChangeWindowAttributes(EwinGetClientWin(ewin), CWWinGravity, &att);

#if USE_CONTAINER_WIN
    EMoveResizeWindow(EwinGetClientWin(ewin), 0, 0,
                      ewin->client.w, ewin->client.h);
    EMoveResizeWindow(ewin->win_container, pad->left, pad->top,
                      ewin->client.w, ewin->client.h);
#else
    EWindowSync(EwinGetClientWin(ewin));        /* Gravity - recache */
#endif

    if (ewin->state.shaped)
        _EWIN_ADJUST_SHAPE(ewin, 0, 0);

    EwinMoveResize(ewin, EoGetX(ewin), EoGetY(ewin),
                   ewin->client.w, ewin->client.h, MRF_KEEP_MAXIMIZED);

    ewin->state.shading = 0;

    EwinStateUpdate(ewin);
    HintsSetWindowState(ewin);
    SnapshotEwinUpdate(ewin, SNAP_USE_SHADED);
}

static int
_EwinShadeRun(EObj *eobj, int remaining, void *state)
{
    _ewin_shade_data *esd = (_ewin_shade_data *) state;
    EWin           *ewin = (EWin *) eobj;
    int             k, x, y, w, h, ww, hh;
    int cow         __UNUSED__, coh __UNUSED__, shx, shy;
    const EImageBorder *pad;

    k = 1024 - remaining;

    x = esd->start.x;
    y = esd->start.y;
    w = esd->start.w;
    h = esd->start.h;

    cow = ewin->client.w;
    coh = ewin->client.h;

    shx = shy = 0;

    pad = BorderGetSize(ewin->border);

    switch (BorderGetShadedir(ewin->border))
    {
    default:
    case SHADE_LEFT:
        w = ((esd->a * (1024 - k)) + (esd->b * k)) >> 10;
        if (w <= 0)
            w = 1;
        ww = w - (pad->left + pad->right);
        if (ww <= 0)
            ww = 1;
        cow = ww;
        shx = ww - ewin->client.w;
        break;
    case SHADE_RIGHT:
        w = ((esd->a * (1024 - k)) + (esd->b * k)) >> 10;
        if (w <= 0)
            w = 1;
        x = esd->c - w;
        ww = w - (pad->left + pad->right);
        if (ww <= 0)
            ww = 1;
        cow = ww;
        break;
    case SHADE_UP:
        h = ((esd->a * (1024 - k)) + (esd->b * k)) >> 10;
        if (h <= 0)
            h = 1;
        hh = h - (pad->top + pad->bottom);
        if (hh <= 0)
            hh = 1;
        coh = hh;
        shy = hh - ewin->client.h;
        break;
    case SHADE_DOWN:
        h = ((esd->a * (1024 - k)) + (esd->b * k)) >> 10;
        if (h <= 0)
            h = 1;
        y = esd->c - h;
        hh = h - (pad->top + pad->bottom);
        if (hh <= 0)
            hh = 1;
        coh = hh;
        shy = hh - ewin->client.h;
        break;
    }
#if USE_CONTAINER_WIN
    EMoveResizeWindow(ewin->win_container, pad->left, pad->top, cow, coh);
#else
    EMoveResizeWindow(EwinGetClientWin(ewin), pad->left + shx, pad->top + shy,
                      ewin->client.w, ewin->client.h);
#endif
    if (ewin->state.shaped)
        _EWIN_ADJUST_SHAPE(ewin, shx, shy);
    EoMoveResize(ewin, x, y, w, h);
    EwinBorderCalcSizes(ewin, 1);

    if (remaining == 0)
    {
        if (esd->doshade)
            _EwinShadeEnd(esd);
        else
            _EwinUnshadeEnd(esd);
    }

    return 0;
}

void
EwinUnShade(EWin *ewin)
{
    Animator       *an;
    _ewin_shade_data esd;
    int             duration;

    if (ewin->state.zoomed)
        return;
    if (!ewin->state.shaded || ewin->state.shading || ewin->state.iconified)
        return;

    esd.ewin = ewin;
    _EwinUnshadeStart(&esd);
    if ((Conf.shading.animate) || (ewin->type == EWIN_TYPE_MENU))
    {
        if (Conf.shading.speed < SPEED_MIN)
            Conf.shading.speed = SPEED_MIN;
        duration = 1000000 / Conf.shading.speed;
        an = AnimatorAdd(&ewin->o, ANIM_SHADE, _EwinShadeRun, duration, 0,
                         sizeof(esd), &esd);
        AnimatorSetSound(an, SOUND_UNSHADE, SOUND_NONE);
    }
    else
        _EwinUnshadeEnd(&esd);
}

void
EwinOpFullscreen(EWin *ewin, int source __UNUSED__, int on)
{
    int             x, y, w, h, ww, hh;
    Area            area;
    EWin          **lst;
    int             i, num;
    const Border   *b;

    if (ewin->state.fullscreen == (unsigned)on)
        return;
    if (ewin->state.zoomed)
        return;

    if (on)
    {
        if (on == 1)
        {
            if (ewin->state.inhibit_fullscreeen)
                return;
            ewin->save_fs.x = EoGetX(ewin);
            ewin->save_fs.y = EoGetY(ewin);
            ewin->save_fs.w = ewin->client.w;
            ewin->save_fs.h = ewin->client.h;
            ewin->save_fs.layer = EoGetLayer(ewin);
        }
        if (ewin->state.placed)
        {
            x = EoGetX(ewin);
            y = EoGetY(ewin);
        }
        else
        {
            EventsUpdateXY(&x, &y);
        }
        ScreenGetAvailableArea(x, y, &area,
                               Conf.place.ignore_struts_fullscreen);
        x = area.x;
        y = area.y;
        w = area.w;
        h = area.h;

        ewin->state.fullscreen = 1;

        /* Fixup if available space doesn't match ICCCM size constraints */
        ICCCM_SizeMatch(ewin, w, h, &ww, &hh);
        if (w == ww && h == hh)
        {
            b = BorderFind("BORDERLESS");
        }
        else
        {
            b = BorderCreateFiller(ww, hh, w, h);
            w = ww;
            h = hh;
        }
        EwinBorderSetTo(ewin, b);

        if (Conf.place.raise_fullscreen)
        {
            EoSetLayer(ewin, 8);
            lst = EwinListTransients(ewin, &num, 0);
            for (i = 0; i < num; i++)
            {
                lst[i]->save_fs.layer = EoGetLayer(lst[i]);
                EoSetLayer(lst[i], lst[i]->save_fs.layer +
                           EoGetLayer(ewin) - ewin->save_fs.layer);
            }
            Efree(lst);
        }

        EwinRaise(ewin);
        EwinMoveResize(ewin, x, y, w, h, MRF_KEEP_MAXIMIZED);
        EwinStateUpdate(ewin);
    }
    else
    {
        x = ewin->save_fs.x;
        y = ewin->save_fs.y;
        w = ewin->save_fs.w;
        h = ewin->save_fs.h;
        GetOnScreenPos(x, y, w, h, &x, &y);
        b = ewin->normal_border;
        EwinBorderSetTo(ewin, b);

        if (Conf.place.raise_fullscreen)
        {
            lst = EwinListTransients(ewin, &num, 0);
            for (i = 0; i < num; i++)
                EoSetLayer(lst[i], lst[i]->save_fs.layer);
            Efree(lst);
        }
        EoSetLayer(ewin, ewin->save_fs.layer);

        ewin->state.fullscreen = 0;
        EwinStateUpdate(ewin);
        EwinRaise(ewin);
        EwinMoveResize(ewin, x, y, w, h, MRF_KEEP_MAXIMIZED);

        /* Keep focus if focused */
        if (ewin->state.active)
            FocusToEWin(ewin, FOCUS_SET);
    }

    HintsSetWindowState(ewin);
}

void
EwinsShowDesktop(int on)
{
    EWin           *const *lst, *ewin;
    int             i, num;

    lst = EwinListGetForDesk(&num, DesksGetCurrent());

    if (on)
    {
        for (i = 0; i < num; i++)
        {
            ewin = lst[i];

            if (EwinIsTransient(ewin) ||
                ewin->state.iconified || ewin->state.donthide)
                continue;

            ewin->state.showingdesk = 1;
            EwinIconify(ewin);
        }
    }
    else
    {
        for (i = num - 1; i >= 0; i--)
        {
            ewin = lst[i];

            if (!ewin->state.showingdesk)
                continue;

            EwinDeIconify(ewin);
        }
    }
    Mode.showing_desktop = on;
    EWMH_SetShowingDesktop(on);
}

void
EwinMoveToArea(EWin *ewin, int ax, int ay)
{
    DesksFixArea(&ax, &ay);
    EwinMove(ewin, EoGetX(ewin) + (WinGetW(VROOT) * (ax - ewin->area_x)),
             EoGetY(ewin) + (WinGetH(VROOT) * (ay - ewin->area_y)), 0);
}

void
EwinOpActivate(EWin *ewin, int source, int raise)
{
    int             unshade;

    if (source == OPSRC_APP && EwinInhGetApp(ewin, focus))
        return;

    unshade = ewin->state.shaded /* && !ewin->state.iconified */ ;

    if (!ewin->state.sliding && !ewin->state.iconified)
    {
        /* If somehow lost outside desktop, move it to center */
        if (!EwinIsOnDesktop(ewin))
            ArrangeEwinCentered(ewin);
        DeskGotoByEwin(ewin, 0);
    }
    if (raise)
        EwinOpRaise(ewin, source);
    if (ewin->state.iconified)
        EwinOpIconify(ewin, source, 0);
    if (unshade)
        EwinOpShade(ewin, source, 0);
    FocusToEWin(ewin, FOCUS_SET);
}

void
EwinOpClose(EWin *ewin, int source __UNUSED__)
{
    EWin          **gwins;
    int             num, i;

    if (!ewin)
        return;

    gwins = ListWinGroupMembersForEwin(ewin, GROUP_ACTION_KILL,
                                       Mode.nogroup, &num);
    for (i = 0; i < num; i++)
    {
        ICCCM_Delete(gwins[i]);
        SoundPlay(SOUND_WINDOW_CLOSE);
    }
    Efree(gwins);
}

void
EwinOpKill(EWin *ewin, int source __UNUSED__)
{
    SoundPlay(SOUND_WINDOW_CLOSE);
    EwinKill(ewin);
}

void
EwinOpRaise(EWin *ewin, int source __UNUSED__)
{
    EWin          **gwins;
    int             i, num, changed;

    gwins = ListWinGroupMembersForEwin(ewin, GROUP_ACTION_STACKING,
                                       Mode.nogroup, &num);
    for (i = changed = 0; i < num; i++)
        changed += EwinRaise(gwins[i]);
    Efree(gwins);
    if (changed)
        SoundPlay(SOUND_RAISE);
}

void
EwinOpLower(EWin *ewin, int source __UNUSED__)
{
    EWin          **gwins;
    int             i, num, changed;

    gwins = ListWinGroupMembersForEwin(ewin, GROUP_ACTION_STACKING,
                                       Mode.nogroup, &num);
    for (i = changed = 0; i < num; i++)
        changed += EwinLower(gwins[i]);
    Efree(gwins);
    if (changed)
        SoundPlay(SOUND_LOWER);
}

void
EwinOpStick(EWin *ewin, int source __UNUSED__, int on)
{
    EWin          **gwins;
    int             i, num;

    gwins = ListWinGroupMembersForEwin(ewin, GROUP_ACTION_STICK,
                                       Mode.nogroup, &num);
    for (i = 0; i < num; i++)
    {
        if (on)
            EwinStick(gwins[i]);
        else
            EwinUnStick(gwins[i]);
    }
    Efree(gwins);
}

void
EwinOpSkipLists(EWin *ewin, int source __UNUSED__, int skip)
{
    ewin->props.skip_ext_task = skip;
    ewin->props.skip_winlist = skip;
    ewin->props.skip_focuslist = skip;

    EwinStateUpdate(ewin);
    HintsSetWindowState(ewin);
    SnapshotEwinUpdate(ewin, SNAP_USE_SKIP_LISTS);
}

#if 0                           /* Unused */
void
EwinOpSkipTask(EWin *ewin, int skip)
{
    ewin->props.skip_ext_task = skip;

    EwinStateUpdate(ewin);
    HintsSetWindowState(ewin);
    SnapshotEwinUpdate(ewin, SNAP_USE_SKIP_LISTS);
}

void
EwinOpSkipFocus(EWin *ewin, int skip)
{
    ewin->props.skip_focuslist = skip;
    EwinStateUpdate(ewin);
    SnapshotEwinUpdate(ewin, SNAP_USE_SKIP_LISTS);
}

void
EwinOpSkipWinlist(EWin *ewin, int skip)
{
    ewin->props.skip_winlist = skip;
    EwinStateUpdate(ewin);
    SnapshotEwinUpdate(ewin, SNAP_USE_SKIP_LISTS);
}

void
EwinOpNeverFocus(EWin *ewin, int on)
{
    ewin->props.never_focus = on;
    EwinStateUpdate(ewin);
    SnapshotEwinUpdate(ewin, SNAP_USE_FOCUS_NEVER);
}
#endif

void
EwinOpIconify(EWin *ewin, int source __UNUSED__, int on)
{
    EWin          **gwins;
    int             i, num;

    gwins = ListWinGroupMembersForEwin(ewin, GROUP_ACTION_ICONIFY,
                                       Mode.nogroup, &num);
    for (i = 0; i < num; i++)
    {
        if (on)
            EwinIconify(gwins[i]);
        else
            EwinDeIconify(gwins[i]);
    }
    Efree(gwins);
}

void
EwinOpShade(EWin *ewin, int source __UNUSED__, int on)
{
    EWin          **gwins;
    int             i, num;

    gwins = ListWinGroupMembersForEwin(ewin, GROUP_ACTION_SHADE,
                                       Mode.nogroup, &num);
    for (i = 0; i < num; i++)
    {
        if (on)
            EwinShade(gwins[i]);
        else
            EwinUnShade(gwins[i]);
    }
    Efree(gwins);
}

void
EwinOpSetLayer(EWin *ewin, int source __UNUSED__, int layer)
{
    if (EoGetLayer(ewin) > layer)
    {
        SoundPlay(SOUND_WINDOW_CHANGE_LAYER_DOWN);
    }
    else if (EoGetLayer(ewin) < layer)
    {
        SoundPlay(SOUND_WINDOW_CHANGE_LAYER_UP);
    }
    EoSetLayer(ewin, layer);
    EwinRaise(ewin);
    EwinStateUpdate(ewin);
    HintsSetWindowState(ewin);
    SnapshotEwinUpdate(ewin, SNAP_USE_LAYER);
}

void
EwinOpSetTitle(EWin *ewin, const char *title)
{
    /* Note! The window border title is updated via the PropertyChange
     * notification(s) caused by the property changes done below.
     * PropertyChange notifications for internal windows are ignored so
     * the window title for internal windows will not be updated that way.
     * Therefore, let's just not change the properties either. */
    if (EwinIsInternal(ewin))
        return;

    HintsSetWindowName(EwinGetClientWin(ewin), title);
}

void
EwinOpSetBorder(EWin *ewin, int source __UNUSED__, const char *name)
{
    EWin          **gwins;
    int             i, num;
    char            has_shaded;
    Border         *b;
    char            shadechange = 0;

    b = BorderFind(name);
    if (!b)
        return;

    has_shaded = 0;
    gwins = ListWinGroupMembersForEwin(ewin, GROUP_ACTION_SET_WINDOW_BORDER,
                                       Mode.nogroup, &num);
    for (i = 0; i < num; i++)
    {
        if (gwins[i]->state.shaded)
        {
            has_shaded = 1;
            break;
        }
    }
    if (has_shaded && !BorderCanShade(b))
        return;

    for (i = 0; i < num; i++)
    {
        if (b != gwins[i]->border)
        {
            SoundPlay(SOUND_WINDOW_BORDER_CHANGE);
            shadechange = 0;
            if (gwins[i]->state.shaded)
            {
                shadechange = 1;
                EwinInstantUnShade(gwins[i]);
            }
            EwinBorderChange(gwins[i], b, 1);
            if (shadechange)
                EwinInstantShade(gwins[i], 0);
        }
        SnapshotEwinUpdate(gwins[i], SNAP_USE_BORDER);
    }
    Efree(gwins);
}

void
EwinOpSetOpacity(EWin *ewin, int source __UNUSED__, int opacity)
{
    unsigned int    op;

    op = OpacityFromPercent(opacity);
    ewin->props.opacity = op;
    ewin->ewmh.opacity_update = 1;      /* Set opacity on client window */
    HintsSetWindowOpacity(ewin);
    EwinUpdateOpacity(ewin);
    SnapshotEwinUpdate(ewin, SNAP_USE_OPACITY);
}

void
EwinOpSetFocusedOpacity(EWin *ewin, int source __UNUSED__, int opacity)
{
    unsigned int    op;

    op = OpacityFromPercent(opacity);
    ewin->props.focused_opacity = op;
    EwinUpdateOpacity(ewin);
    SnapshotEwinUpdate(ewin, SNAP_USE_OPACITY);
}

void
EwinOpMoveToDesk(EWin *ewin, int source __UNUSED__, Desk *dsk, int inc)
{
    dsk = DeskGetRelative(dsk, inc);

    EoSetSticky(ewin, 0);
    EwinMoveToDesktop(ewin, dsk);
    EwinRaise(ewin);
    EwinBorderUpdateState(ewin);
    EwinStateUpdate(ewin);
    HintsSetWindowState(ewin);
    HintsSetWindowDesktop(ewin);
    SnapshotEwinUpdate(ewin, SNAP_USE_STICKY);
}

#if 0                           /* Unused */
void
EwinMoveToLinearArea(EWin *ewin, int a)
{
    int             ax, ay;

    AreaLinearToXY(a, &ax, &ay);
    EwinMoveToArea(ewin, ax, ay);
}

void
EwinMoveLinearAreaBy(EWin *ewin, int a)
{
    EwinMoveToLinearArea(ewin, AreaXYToLinear(ewin->area_x, ewin->area_y) + a);
}

void
EwinOpMoveToArea(EWin *ewin, int x, int y)
{
    EwinMoveToArea(ewin, x, y);
    SnapshotEwinUpdate(ewin, SNAP_USE_POS);
}
#endif

#if 0                           /* Not used? */
static int
doMoveWinToLinearArea(EWin *ewin, const char *params)
{
    int             da;

    if (params)
    {
        sscanf(params, "%i", &da);
        EwinMoveToLinearArea(ewin, da);
    }
    SnapshotEwinUpdate(ewin, SNAP_USE_POS);
    return 0;
}

static int
doMoveWinByLinearArea(EWin *ewin, const char *params)
{
    EWin           *ewin;
    int             da;

    if (params)
    {
        sscanf(params, "%i", &da);
        EwinMoveLinearAreaBy(ewin, da);
    }
    SnapshotEwinUpdate(ewin, SNAP_USE_POS);
    return 0;
}
#endif

#if 0                           /* FIXME - Unused? */
static int
doScrollWindows(EWin *edummy __UNUSED__, const char *params)
{
    int             x, y, num, i;
    EWin           *const *lst;

    if (!params)
        return 0;

    x = 0;
    y = 0;
    sscanf(params, "%i %i", &x, &y);

    lst = EwinListGetAll(&num);
    for (i = 0; i < num; i++)
    {
        if ((lst[i]->desktop == DesksGetCurrent()) && (!lst[i]->sticky) &&
            (!lst[i]->floating))
            EwinMove(lst[i], lst[i]->x + x, lst[i]->y + y);
    }
    return 0;
}
#endif
