/*
 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
 * Copyright (C) 2004-2022 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 "cursors.h"
#include "desktops.h"           /* FIXME - Should not be here */
#include "dialog.h"
#include "emodule.h"
#include "ewins.h"
#include "focus.h"
#include "grabs.h"
#include "hints.h"
#include "icons.h"
#include "settings.h"
#include "timers.h"
#include "xwin.h"

#define EwinListFocusRaise(ewin) EobjListFocusRaise(EoObj(ewin))

static const char *const focus_why[] = {
    "NOP", "INIT", "SET", "NONE", "ENTER", "LEAVE", "EWIN_NEW", "EWIN_UNMAP",
    "DESK_ENTER", "DESK_LEAVE", "NEXT", "PREV", "CLICK",
};

static char     focus_inhibit = 1;
static char     focus_is_set = 0;
static char     click_pending_update_grabs = 0;
static int      focus_pending_why = 0;
static EWin    *focus_pending_ewin = NULL;
static EWin    *focus_pending_new = NULL;
static EWin    *focus_pending_raise = NULL;
static Timer   *focus_timer_autoraise = NULL;
static int      focus_request = 0;

void
FocusEnable(int on)
{
    if (on)
    {
        if (focus_inhibit > 0)
            focus_inhibit--;
    }
    else
    {
        focus_inhibit++;
    }

    if (EDebug(EDBUG_TYPE_FOCUS))
        Eprintf("%s: inhibit=%d\n", __func__, focus_inhibit);
}

/*
 * Return !0 if it is OK to focus ewin.
 */
static int
FocusEwinValid(EWin *ewin, int want_on_screen, int click, int want_visible)
{
    int             ok;

    if (!ewin)
        return 0;

    if (ewin->state.inhibit_focus)
        ok = 0;

    else if (!EoIsMapped(ewin) || !EoIsShown(ewin) ||
             ewin->state.state != EWIN_STATE_MAPPED)
        ok = 0;

    else if (ewin->state.sliding)
        ok = 0;

    else if (ewin->props.focusclick && !click)
        ok = 0;

    else if (want_visible && ewin->state.visibility == VisibilityFullyObscured)
        ok = 0;

    else if (want_on_screen && !EwinIsOnScreen(ewin))
        ok = 0;

    else
        ok = 1;

#if 0
    Eprintf
        ("%s: %#x %s: st=%d sh=%d inh=%d/%d ons=%d(%d) vis=%d(%d) cl=%d(%d): Ok=%d\n",
         __func__, EwinGetClientXwin(ewin), EwinGetTitle(ewin),
         ewin->state.state, EoIsShown(ewin),
         ewin->state.inhibit_focus, ewin->state.sliding,
         EwinIsOnScreen(ewin), want_on_screen,
         ewin->state.visibility, want_visible, ok, click,
         ewin->props.focusclick);
#endif
    return ok;
}

/*
 * Return !0 if new ewin should be focused.
 */
static int
FocusEwinValidNew(EWin *ewin)
{
    int             ok = 0;

    if (Conf.focus.all_new_windows_get_focus)
        ok = 1;

    else if (Mode.place.doing_manual == ewin)
        ok = 1;

    else if (Conf.focus.new_windows_get_focus_if_group_focused &&
             Mode.focuswin &&
             EwinGetWindowGroup(ewin) == EwinGetWindowGroup(Mode.focuswin))
        ok = 1;

    else if (EwinIsTransient(ewin))
    {
        if (Conf.focus.new_transients_get_focus)
        {
            ok = 1;
        }
        else if (Conf.focus.new_transients_get_focus_if_group_focused &&
                 Mode.focuswin)
        {
            if ((EwinGetTransientFor(ewin) ==
                 EwinGetClientXwin(Mode.focuswin)) ||
                (EwinGetWindowGroup(ewin) == EwinGetWindowGroup(Mode.focuswin)))
                ok = 1;
        }
    }

#if 0
    Eprintf("%s: %#x %s: Ok=%d\n", __func__,
            EwinGetClientXwin(ewin), EwinGetTitle(ewin), ok);
#endif

    return ok;
}

/*
 * Return the ewin to focus after entering area or losing focused window.
 */
static EWin    *
FocusEwinSelect(void)
{
    EWin           *const *lst, *ewin;
    int             num, i;

    switch (Conf.focus.mode)
    {
    default:
    case MODE_FOCUS_POINTER:
        ewin = GetEwinPointerInClient();
        if (ewin && !FocusEwinValid(ewin, 1, 0, 0))
            ewin = NULL;
        break;

    case MODE_FOCUS_SLOPPY:
        ewin = GetEwinPointerInClient();
        if (ewin && FocusEwinValid(ewin, 1, 0, 0))
            break;
        goto do_select;

    case MODE_FOCUS_CLICK:
        goto do_select;

      do_select:
        ewin = NULL;
        lst = EwinListFocusGet(&num);
        for (i = 0; i < num; i++)
        {
            if (!FocusEwinValid(lst[i], 1, 0, 0) ||
                lst[i]->props.skip_focuslist)
                continue;
            ewin = lst[i];
            break;
        }
        break;
    }

    return ewin;
}

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

    if (Conf.focus.mode == MODE_FOCUS_CLICK)
        goto done;

    if (EwinFindByPtr(ewin))    /* May be gone */
        EwinRaise(ewin);

  done:
    focus_timer_autoraise = NULL;
    return 0;
}

static void
FocusRaisePending(void)
{
    EWin           *ewin = focus_pending_raise;
    unsigned int    mask;

    /* The focusing cycle ends when no more modifiers are depressed */
    mask = 0;
    EQueryPointer(NULL, NULL, NULL, NULL, &mask);
    if ((mask & Mode.masks.mod_key_mask) != 0)
        return;

    if (EwinFindByPtr(ewin))    /* May be gone */
        EwinListFocusRaise(ewin);

    GrabKeyboardRelease();

    focus_pending_raise = NULL;
}

/*
 * dir > 0: Focus previously focused window
 * else   : Focus least recently focused window
 */
static void
FocusCycleEwin(int dir)
{
    EWin           *const *lst;
    EWin           *ewin;
    int             i, j, num;

    lst = EwinListFocusGet(&num);
    if (num <= 1)
        return;

    dir = (dir > 0) ? 1 : -1;

    for (j = 0; j < num; j++)
    {
        if (lst[j] == Mode.focuswin)
            break;
    }
    for (i = 1; i < num; i++)
    {
        ewin = lst[(j + i * dir + num) % num];
        if (!FocusEwinValid(ewin, 1, 0, 0) || ewin->props.skip_focuslist)
            continue;
        FocusToEWin(ewin, FOCUS_PREV);
        break;
    }
}

static void
ClickGrabsSet(EWin *ewin)
{
    int             set = 0;
    unsigned int    raise_button = AnyButton;

    if (Conf.focus.clickraises == 2)
        raise_button = Button1;

    if ((Conf.focus.clickraises && !EwinListStackIsRaised(ewin)) ||
        (!ewin->state.active && !ewin->state.inhibit_focus))
        set = 1;

    if (set)
    {
        if (ewin->state.click_grab_isset &&
            ewin->state.click_grab_button != raise_button)
        {
            GrabButtonRelease(ewin->state.click_grab_button, AnyModifier,
                              EwinGetClientConWin(ewin));
            ewin->state.click_grab_isset = 0;
        }
        if (!ewin->state.click_grab_isset)
        {
            GrabButtonSet(raise_button, AnyModifier, EwinGetClientConWin(ewin),
                          ButtonPressMask, ECSR_PGRAB, 1);
            if (EDebug(EDBUG_TYPE_GRABS))
                Eprintf("%s: %#x set %s\n", __func__,
                        EwinGetClientXwin(ewin), EwinGetTitle(ewin));
            ewin->state.click_grab_isset = 1;
            ewin->state.click_grab_button = raise_button;
        }
    }
    else
    {
        if (ewin->state.click_grab_isset)
        {
            GrabButtonRelease(ewin->state.click_grab_button, AnyModifier,
                              EwinGetClientConWin(ewin));
            if (EDebug(EDBUG_TYPE_GRABS))
                Eprintf("%s: %#x unset %s\n", __func__,
                        EwinGetClientXwin(ewin), EwinGetTitle(ewin));
            ewin->state.click_grab_isset = 0;
        }
    }
}

static void
FocusEwinSetActive(EWin *ewin, int active)
{
    if (ewin->state.active == (unsigned)active)
        return;

    ewin->state.active = active;
    EwinBorderUpdateState(ewin);
    EwinUpdateOpacity(ewin);

    if (active && ewin->state.attention)
        ewin->state.attention = 0;

    HintsSetWindowState(ewin);
}

static void
doClickGrabsUpdate(void)
{
    EWin           *const *lst, *ewin;
    int             i, num;

    lst = EwinListGetAll(&num);
    for (i = 0; i < num; i++)
    {
        ewin = lst[i];
        ClickGrabsSet(ewin);
    }
    click_pending_update_grabs = 0;
}

void
ClickGrabsUpdate(void)
{
    click_pending_update_grabs = 1;
}

static void
doFocusToEwin(EWin *ewin, int why)
{
    int             do_raise = 0, do_warp = 0;

    if (focus_inhibit)
        return;

    if (EDebug(EDBUG_TYPE_FOCUS))
        Eprintf("%s: %#x %s why=%s\n", __func__,
                (ewin) ? EwinGetClientXwin(ewin) : 0,
                (ewin) ? EwinGetTitle(ewin) : "None", focus_why[why]);

    switch (why)
    {
    case FOCUS_NEXT:
    case FOCUS_PREV:
        if (Conf.focus.raise_on_next)
            do_raise = 1;
        if (Conf.focus.warp_on_next)
            do_warp = 1;
        /* Fall thru */
    default:
    case FOCUS_SET:
    case FOCUS_ENTER:
    case FOCUS_LEAVE:          /* Unused */
    case FOCUS_CLICK:
        if (ewin && ewin == Mode.focuswin)
            return;
        if (!ewin)              /* Unfocus */
            break;
        if (!FocusEwinValid(ewin, 1, why == FOCUS_CLICK, 0))
            return;
        break;

    case FOCUS_INIT:
    case FOCUS_DESK_ENTER:
        ewin = FocusEwinSelect();
        break;

    case FOCUS_DESK_LEAVE:
        focus_is_set = 0;
        /* FALLTHROUGH */
    case FOCUS_NONE:
        ewin = NULL;
        if (ewin == Mode.focuswin)
            return;
        break;

    case FOCUS_EWIN_UNMAP:
        if (Mode.focuswin)
            return;
        ewin = FocusEwinSelect();
        if (ewin == Mode.focuswin)
            ewin = NULL;
        break;

    case FOCUS_EWIN_NEW:
        if (!ewin)
            return;

        if (ewin->props.focus_when_mapped)
            goto check_focus_new;

        if (FocusEwinValidNew(ewin))
        {
            if (EwinIsTransient(ewin))
                DeskGotoByEwin(ewin, 0);
            goto check_focus_new;
        }

        if (Conf.focus.mode != MODE_FOCUS_CLICK && ewin == Mode.mouse_over_ewin)
            goto check_focus_new;

        return;

      check_focus_new:
        if (!FocusEwinValid(ewin, 1, 0, 0))
            return;
        break;
    }

    if (ewin == Mode.focuswin && focus_is_set)
        return;

    /* Check if ewin is a valid focus window target */

    if (!ewin)
        goto done;

    /* NB! ewin != NULL */

    if (why != FOCUS_CLICK && ewin->props.focusclick)
        return;

    if (Conf.autoraise.enable)
    {
        TIMER_DEL(focus_timer_autoraise);

        if (Conf.focus.mode != MODE_FOCUS_CLICK)
            TIMER_ADD(focus_timer_autoraise, Conf.autoraise.delay,
                      AutoraiseTimeout, ewin);
    }

    if (do_raise)
        EwinRaise(ewin);

    if (Conf.focus.warp_always)
        do_warp = 1;
    if (do_warp)
        EwinWarpTo(ewin, 0);

    switch (why)
    {
    case FOCUS_PREV:
    case FOCUS_NEXT:
        GrabKeyboardSet(VROOT); /* Causes idler to be called on KeyRelease */
        focus_pending_raise = ewin;
        break;
    case FOCUS_DESK_ENTER:
        if (Conf.focus.mode == MODE_FOCUS_CLICK)
            break;
        /* FALLTHROUGH */
    default:
    case FOCUS_INIT:
        EwinListFocusRaise(ewin);
        break;
    }

    SoundPlay(SOUND_FOCUS_SET);
  done:

    ClickGrabsUpdate();

    /* Unset old focus window (if any) highlighting */
    if (Mode.focuswin)
        FocusEwinSetActive(Mode.focuswin, 0);
    ICCCM_Cmap(ewin);

    /* Quit if pointer is not on our screen */

    if (!Mode.events.on_screen)
    {
        Mode.focuswin = NULL;
        return;
    }

    /* Set new focus window (if any) highlighting */
    Mode.focuswin = ewin;
    if (Mode.focuswin)
        FocusEwinSetActive(Mode.focuswin, 1);

    if (why == FOCUS_DESK_LEAVE)
        return;

    ICCCM_Focus(ewin);
    focus_is_set = 1;
}

void
FocusToEWin(EWin *ewin, int why)
{
    if (EDebug(EDBUG_TYPE_FOCUS))
        Eprintf("%s(%d) %#x %s why=%s\n", __func__, focus_inhibit,
                (ewin) ? EwinGetClientXwin(ewin) : 0,
                (ewin) ? EwinGetTitle(ewin) : "None", focus_why[why]);

    switch (why)
    {
    case FOCUS_EWIN_NEW:
        if (!FocusEwinValidNew(ewin))
            break;
        if (!FocusEwinValid(ewin, 0, 0, 0))
            break;
        focus_pending_new = ewin;
        focus_pending_why = why;
        focus_pending_ewin = ewin;
        focus_request = (int)NextRequest(disp) - 1;
        break;

    default:
        if (ewin && !FocusEwinValid(ewin, 0, why == FOCUS_CLICK, 0))
            break;
        focus_pending_why = why;
        focus_pending_ewin = ewin;
        focus_request = (int)NextRequest(disp) - 1;
        break;

    case FOCUS_EWIN_UNMAP:
        focus_pending_why = why;
        focus_pending_ewin = NULL;
        if (ewin == Mode.focuswin)
        {
            Mode.focuswin = NULL;
            focus_is_set = 0;
            if (!EoIsGone(ewin))
                FocusEwinSetActive(ewin, 0);
        }
        if (ewin == focus_pending_new)
            focus_pending_new = NULL;
        break;
    }
}

static void
FocusSet(void)
{
    if (focus_pending_new && Conf.focus.all_new_windows_get_focus)
        doFocusToEwin(focus_pending_new, FOCUS_EWIN_NEW);
    else
        doFocusToEwin(focus_pending_ewin, focus_pending_why);
    focus_pending_why = 0;
    focus_pending_ewin = focus_pending_new = NULL;
}

void
FocusNewDeskBegin(void)
{
    /* Freeze keyboard */
    GrabKeyboardFreeze(VROOT);

    focus_pending_new = NULL;
    doFocusToEwin(NULL, FOCUS_DESK_LEAVE);
    FocusEnable(0);
}

void
FocusNewDesk(void)
{
    FocusEnable(1);
    FocusToEWin(NULL, FOCUS_DESK_ENTER);

    /* Unfreeze keyboard */
    GrabKeyboardRelease();
}

static void
_FocusScreenSendEvent(EX_Window xwin, int x, int y, EX_Time t, int enter)
{
    XEvent          xe;

    xe.type = (enter) ? EnterNotify : LeaveNotify;
    xe.xcrossing.window = xwin;
    xe.xcrossing.root = xwin;
    xe.xcrossing.subwindow = 0;
    xe.xcrossing.time = t;
    xe.xcrossing.x = xe.xcrossing.x_root = x;
    xe.xcrossing.y = xe.xcrossing.y_root = y;
    xe.xcrossing.mode = NotifyNormal;
    xe.xcrossing.detail = NotifyNonlinear;
    xe.xcrossing.same_screen = (enter) ? True : False;
    xe.xcrossing.focus = (enter) ? False : True;
    xe.xcrossing.state = 0;

    XSendEvent(disp, xwin, False, EnterWindowMask | LeaveWindowMask, &xe);
}

void
FocusScreen(int scr)
{
    EX_Window       xwin;
    EX_Time         t;
    int             x, y;

    if (scr < 0 || scr >= ScreenCount(disp))
        return;

    /* IIRC warping to a different screen once did cause
     * LeaveNotify's on the current root window. This does not
     * happen in xorg 1.5.3 (and probably other versions).
     * So, send LeaveNotify to current root and EnterNotify
     * to new root. */

    t = EGetTimestamp();

    /* Report LeaveNotify on current root window */
    xwin = WinGetXwin(VROOT);
    EXQueryPointer(xwin, &x, &y, NULL, NULL);
    _FocusScreenSendEvent(xwin, x, y, t, 0);

    /* Do warp and report EnterNotify on new root window */
    xwin = RootWindow(disp, scr);
    x = DisplayWidth(disp, scr) / 2;
    y = DisplayHeight(disp, scr) / 2;
    EXWarpPointer(xwin, x, y);
    _FocusScreenSendEvent(xwin, x, y, t, 1);
}

static void
FocusInit(void)
{
    /* Start focusing windows */
    FocusEnable(1);

    FocusToEWin(NULL, FOCUS_INIT);
    FocusSet();

    /* Enable window placement features */
    Mode.place.enable_features++;
}

static void
FocusExit(void)
{
}

/*
 * Focus event handlers
 */

void
FocusHandleEnter(EWin *ewin, XEvent *ev)
{
    Mode.mouse_over_ewin = ewin;

#if 0                           /* FIXME - Remove? */
    if (ev->xcrossing.mode == NotifyUngrab &&
        ev->xcrossing.detail == NotifyNonlinearVirtual)
    {
        if (EDebug(1))
            Eprintf("%s: Previously ignored: focused: %s, enter: %s\n",
                    __func__, EoGetNameSafe(Mode.focuswin),
                    EoGetNameSafe(ewin));
    }
#endif

    if ((int)ev->xcrossing.serial - focus_request < 0)
    {
        /* This event was caused by a request older than the latest
         * focus assignment request - ignore */
        if (EDebug(EDBUG_TYPE_FOCUS))
            Eprintf("%s: Ignore serial < %#x\n", __func__, focus_request);
        return;
    }

    if (!ewin)
    {
        /* Entering root may mean entering this screen */
        FocusToEWin(NULL, FOCUS_DESK_ENTER);
        return;
    }

    switch (Conf.focus.mode)
    {
    default:
    case MODE_FOCUS_CLICK:
        break;
    case MODE_FOCUS_SLOPPY:
        if (FocusEwinValid(ewin, 1, 0, 0))
            FocusToEWin(ewin, FOCUS_ENTER);
        break;
    case MODE_FOCUS_POINTER:
        if (FocusEwinValid(ewin, 1, 0, 0))
            FocusToEWin(ewin, FOCUS_ENTER);
        else
            FocusToEWin(NULL, FOCUS_NONE);
        break;
    }
}

void
FocusHandleLeave(EWin *ewin, XEvent *ev)
{
    if ((int)ev->xcrossing.serial - focus_request < 0)
    {
        /* This event was caused by a request older than the latest
         * focus assignment request - ignore */
        if (EDebug(EDBUG_TYPE_FOCUS))
            Eprintf("%s: Ignore serial < %#x\n", __func__, focus_request);
        return;
    }

    /* Leaving root may mean entering other screen */
    if (!ewin)
    {
        if (ev->xcrossing.mode == NotifyNormal &&
            ev->xcrossing.detail != NotifyInferior)
            FocusToEWin(NULL, FOCUS_DESK_LEAVE);
    }
}

void
FocusHandleChange(EWin *ewin __UNUSED__, XEvent *ev __UNUSED__)
{
#if 0                           /* Debug */
    if (ewin == Mode.focuswin && ev->type == FocusOut)
        Eprintf("%s: ??? Lost focus: %s\n", __func__, EwinGetTitle(ewin));
#endif
}

void
FocusHandleClick(EWin *ewin, Win win)
{
    if (Conf.focus.clickraises)
        EwinRaise(ewin);

    FocusToEWin(ewin, FOCUS_CLICK);

    /* Allow click to pass thorugh */
    if (EDebug(EDBUG_TYPE_GRABS))
        Eprintf("%s: %#x %#x\n", __func__, WinGetXwin(win),
                EwinGetContainerXwin(ewin));
    if (win == EwinGetClientConWin(ewin))
    {
        ESync(ESYNC_FOCUS);
        GrabPointerThaw();
        ESync(ESYNC_FOCUS);
    }
}

#if ENABLE_DIALOGS
/*      
 * Configuration dialog
 */

typedef struct {
    struct {
        int             mode;
        int             clickalways;
        char            new_focus;
        char            new_focus_if_group;
        char            popup_focus;
        char            popup_focus_if_group;
        char            raise_focus;
        char            warp_focus;
        char            warp_always;
    } focus;

    struct {
        char            enable;
        int             time;
    } autoraise;

    struct {
        char            enable;
        char            warp_after_focus;
        char            raise_after_focus;
        char            showsticky;
        char            showshaded;
        char            showiconified;
        char            showalldesks;
        char            warpfocused;
        char            show_shape;
        int             icon_mode;
    } focuslist;
} FocusDlgData;

static void
_DlgApplyFocus(Dialog *d, int val __UNUSED__, void *data __UNUSED__)
{
    FocusDlgData   *dd = DLG_DATA_GET(d, FocusDlgData);

    Conf.focus.mode = dd->focus.mode;
    Conf.focus.clickraises = dd->focus.clickalways;
    Conf.focus.all_new_windows_get_focus = dd->focus.new_focus;
    Conf.focus.new_windows_get_focus_if_group_focused =
        dd->focus.new_focus_if_group;
    Conf.focus.new_transients_get_focus = dd->focus.popup_focus;
    Conf.focus.new_transients_get_focus_if_group_focused =
        dd->focus.popup_focus_if_group;
    Conf.focus.raise_on_next = dd->focus.raise_focus;
    Conf.focus.warp_on_next = dd->focus.warp_focus;
    Conf.focus.warp_always = dd->focus.warp_always;

    Conf.autoraise.enable = dd->autoraise.enable;
    Conf.autoraise.delay = 10 * dd->autoraise.time;

    Conf.warplist.enable = dd->focuslist.enable;
    Conf.warplist.warp_on_select = dd->focuslist.warp_after_focus;
    Conf.warplist.raise_on_select = dd->focuslist.raise_after_focus;
    Conf.warplist.showsticky = dd->focuslist.showsticky;
    Conf.warplist.showshaded = dd->focuslist.showshaded;
    Conf.warplist.showiconified = dd->focuslist.showiconified;
    Conf.warplist.showalldesks = dd->focuslist.showalldesks;
    Conf.warplist.warpfocused = dd->focuslist.warpfocused;
    Conf.warplist.show_shape = dd->focuslist.show_shape;
    Conf.warplist.icon_mode = dd->focuslist.icon_mode;

    ClickGrabsUpdate();

    autosave();
}

static void
_DlgFillFocus(Dialog *d, DItem *table, void *data __UNUSED__)
{
    FocusDlgData   *dd = DLG_DATA_GET(d, FocusDlgData);
    DItem          *di, *radio;

    dd->focus.mode = Conf.focus.mode;
    dd->focus.clickalways = Conf.focus.clickraises;
    dd->focus.new_focus = Conf.focus.all_new_windows_get_focus;
    dd->focus.new_focus_if_group =
        Conf.focus.new_windows_get_focus_if_group_focused;
    dd->focus.popup_focus = Conf.focus.new_transients_get_focus;
    dd->focus.popup_focus_if_group =
        Conf.focus.new_transients_get_focus_if_group_focused;
    dd->focus.raise_focus = Conf.focus.raise_on_next;
    dd->focus.warp_focus = Conf.focus.warp_on_next;
    dd->focus.warp_always = Conf.focus.warp_always;

    dd->autoraise.enable = Conf.autoraise.enable;
    dd->autoraise.time = Conf.autoraise.delay / 10;

    dd->focuslist.enable = Conf.warplist.enable;
    dd->focuslist.raise_after_focus = Conf.warplist.raise_on_select;
    dd->focuslist.warp_after_focus = Conf.warplist.warp_on_select;
    dd->focuslist.showsticky = Conf.warplist.showsticky;
    dd->focuslist.showshaded = Conf.warplist.showshaded;
    dd->focuslist.showiconified = Conf.warplist.showiconified;
    dd->focuslist.showalldesks = Conf.warplist.showalldesks;
    dd->focuslist.warpfocused = Conf.warplist.warpfocused;
    dd->focuslist.show_shape = Conf.warplist.show_shape;
    dd->focuslist.icon_mode = Conf.warplist.icon_mode;

    DialogItemTableSetOptions(table, 2, 0, 0, 0);

    radio = di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Focus follows pointer"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 0);

    di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Focus follows pointer sloppily"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 1);

    di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Focus follows mouse clicks"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 2);
    DialogItemRadioButtonGroupSetValPtr(radio, &dd->focus.mode);

    di = DialogAddItem(table, DITEM_SEPARATOR);
    DialogItemSetColSpan(di, 2);

    radio = di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Clicking in a window does not raise it"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 0);

    di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Clicking in a window always raises it"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 1);

    di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Only primary mouse button can raise window"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 2);
    DialogItemRadioButtonGroupSetValPtr(radio, &dd->focus.clickalways);

    di = DialogAddItem(table, DITEM_SEPARATOR);
    DialogItemSetColSpan(di, 2);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("All new windows first get the focus"));
    DialogItemCheckButtonSetPtr(di, &dd->focus.new_focus);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di,
                      _
                      ("New windows get the focus if their window group is focused"));
    DialogItemCheckButtonSetPtr(di, &dd->focus.new_focus_if_group);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Only new dialog windows get the focus"));
    DialogItemCheckButtonSetPtr(di, &dd->focus.popup_focus);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di,
                      _
                      ("Only new dialogs whose owner is focused get the focus"));
    DialogItemCheckButtonSetPtr(di, &dd->focus.popup_focus_if_group);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Raise windows while switching focus"));
    DialogItemCheckButtonSetPtr(di, &dd->focus.raise_focus);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di,
                      _("Send mouse pointer to window while switching focus"));
    DialogItemCheckButtonSetPtr(di, &dd->focus.warp_focus);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di,
                      _("Always send mouse pointer to window on focus switch"));
    DialogItemCheckButtonSetPtr(di, &dd->focus.warp_always);

    di = DialogAddItem(table, DITEM_SEPARATOR);
    DialogItemSetColSpan(di, 2);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Raise windows automatically"));
    DialogItemCheckButtonSetPtr(di, &dd->autoraise.enable);

    di = DialogAddItem(table, DITEM_TEXT);
    DialogItemSetText(di, _("Autoraise delay:"));

    di = DialogAddItem(table, DITEM_SLIDER);
    DialogItemSliderSetBounds(di, 0, 300);
    DialogItemSliderSetUnits(di, 10);
    DialogItemSliderSetJump(di, 25);
    DialogItemSliderSetValPtr(di, &dd->autoraise.time);

    di = DialogAddItem(table, DITEM_SEPARATOR);
    DialogItemSetColSpan(di, 2);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Display and use focus list"));
    DialogItemCheckButtonSetPtr(di, &dd->focuslist.enable);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Include sticky windows in focus list"));
    DialogItemCheckButtonSetPtr(di, &dd->focuslist.showsticky);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Include shaded windows in focus list"));
    DialogItemCheckButtonSetPtr(di, &dd->focuslist.showshaded);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Include iconified windows in focus list"));
    DialogItemCheckButtonSetPtr(di, &dd->focuslist.showiconified);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Include windows on other desks in focus list"));
    DialogItemCheckButtonSetPtr(di, &dd->focuslist.showalldesks);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Focus windows while switching"));
    DialogItemCheckButtonSetPtr(di, &dd->focuslist.warpfocused);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Outline windows while switching"));
    DialogItemCheckButtonSetPtr(di, &dd->focuslist.show_shape);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Raise windows after focus switch"));
    DialogItemCheckButtonSetPtr(di, &dd->focuslist.raise_after_focus);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Send mouse pointer to window after focus switch"));
    DialogItemCheckButtonSetPtr(di, &dd->focuslist.warp_after_focus);

    di = DialogAddItem(table, DITEM_SEPARATOR);
    DialogItemSetColSpan(di, 2);

    di = DialogAddItem(table, DITEM_TEXT);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di,
                      _
                      ("Focuslist image display policy (if one operation fails, try the next):"));

    radio = di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("First E icon, then App icon"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, EWIN_ICON_MODE_IMG_APP);

    di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("First App icon, then E icon"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, EWIN_ICON_MODE_APP_IMG);

    di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("None"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, EWIN_ICON_MODE_NONE);
    DialogItemRadioButtonGroupSetValPtr(radio, &dd->focuslist.icon_mode);
}

const DialogDef DlgFocus = {
    "CONFIGURE_FOCUS",
    N_("Focus"), N_("Focus Settings"),
    sizeof(FocusDlgData),
    SOUND_SETTINGS_FOCUS,
    "pix/focus.png",
    N_("Enlightenment Focus\n" "Settings Dialog"),
    _DlgFillFocus,
    DLG_OAC, _DlgApplyFocus, NULL
};
#endif                          /* ENABLE_DIALOGS */

/*
 * Focus Module
 */

static int
FocusInitTimeout(void *data __UNUSED__)
{
    FocusInit();
    return 0;
}

static void
_FocusIdler(void *data __UNUSED__)
{
    if (focus_inhibit)
        return;
    if (focus_pending_why)
        FocusSet();
    if (click_pending_update_grabs)
        doClickGrabsUpdate();
    if (focus_pending_raise)
        FocusRaisePending();
}

static void
FocusSighan(int sig, void *prm __UNUSED__)
{
    switch (sig)
    {
    case ESIGNAL_START:
        /* Delay focusing a bit to allow things to settle down */
        IdlerAdd(_FocusIdler, NULL);
        TIMER_ADD_NP(500, FocusInitTimeout, NULL);
        break;

    case ESIGNAL_EXIT:
        FocusExit();
        break;
    }
}

static void
FocusIpc(const char *params)
{
    const char     *p;
    char            cmd[128], prm[4096];
    int             len;

    cmd[0] = prm[0] = '\0';
    p = params;
    if (p)
    {
        len = 0;
        sscanf(p, "%100s %4000s %n", cmd, prm, &len);
        p += len;
    }

    if (!p || cmd[0] == '?')
    {
        EWin           *ewin;

        ewin = GetFocusEwin();
        if (ewin)
            IpcPrintf("Focused: %#x\n", EwinGetClientXwin(ewin));
        else
            IpcPrintf("Focused: none\n");
    }
    else if (!strncmp(cmd, "mode", 2))
    {
        int             mode = Conf.focus.mode;

        if (!strcmp(prm, "click"))
        {
            mode = MODE_FOCUS_CLICK;
            Mode.grabs.pointer_grab_active = 1;
        }
        else if (!strcmp(prm, "clicknograb"))
        {
            mode = MODE_FOCUS_CLICK;
            Mode.grabs.pointer_grab_active = 0;
        }
        else if (!strcmp(prm, "pointer"))
        {
            mode = MODE_FOCUS_POINTER;
        }
        else if (!strcmp(prm, "sloppy"))
        {
            mode = MODE_FOCUS_SLOPPY;
        }
        else if (!strcmp(prm, "?"))
        {
            if (Conf.focus.mode == MODE_FOCUS_CLICK)
            {
                if (Mode.grabs.pointer_grab_active)
                    p = "click";
                else
                    p = "clicknograb";
            }
            else if (Conf.focus.mode == MODE_FOCUS_SLOPPY)
                p = "sloppy";
            else if (Conf.focus.mode == MODE_FOCUS_POINTER)
                p = "pointer";
            else
                p = "unknown";
            IpcPrintf("Focus Mode: %s\n", p);
        }
        else
        {
            IpcPrintf("Error: unknown focus type\n");
        }
        if (Conf.focus.mode != mode)
        {
            Conf.focus.mode = mode;
            ClickGrabsUpdate();
            autosave();
        }
    }
    else if (!strncmp(cmd, "next", 2))
    {
        /* Focus previously focused window */
        if (Conf.warplist.enable)
            WarpFocus(1);
        else
            FocusCycleEwin(1);
    }
    else if (!strncmp(cmd, "prev", 2))
    {
        /* Focus least recently focused window */
        if (Conf.warplist.enable)
            WarpFocus(-1);
        else
            FocusCycleEwin(-1);
    }
}

static const IpcItem FocusIpcArray[] = {
    {
     FocusIpc,
     "focus", "sf",
     "Focus functions",
     "  focus ?              Show focused window id\n"
     "  focus mode MODE      Set focus mode. MODEs:\n"
     "    click        The traditional click-to-focus mode\n"
     "    clicknograb  A similar focus mode, but without the grabbing of the click\n"
     "                 (you cannot click anywhere in a window to focus it)\n"
     "    pointer      The focus will follow the mouse pointer\n"
     "    sloppy       The focus follows the mouse, but when over the desktop\n"
     "                 background the last window does not lose the focus\n"
     "    ?            Show currrent focus mode\n"
     "  focus next           Focus previously focused window\n"
     "  focus prev           Focus least recently focused window\n" }
    ,
};

static const CfgItem FocusCfgItems[] = {
    CFG_ITEM_INT(Conf.focus, mode, MODE_FOCUS_SLOPPY),
    CFG_ITEM_INT(Conf.focus, clickraises, 1),
    CFG_ITEM_BOOL(Conf.focus, transientsfollowleader, 1),
    CFG_ITEM_BOOL(Conf.focus, switchfortransientmap, 1),
    CFG_ITEM_BOOL(Conf.focus, all_new_windows_get_focus, 0),
    CFG_ITEM_BOOL(Conf.focus, new_windows_get_focus_if_group_focused, 1),
    CFG_ITEM_BOOL(Conf.focus, new_transients_get_focus, 0),
    CFG_ITEM_BOOL(Conf.focus, new_transients_get_focus_if_group_focused, 1),
    CFG_ITEM_BOOL(Conf.focus, raise_on_next, 1),
    CFG_ITEM_BOOL(Conf.focus, warp_on_next, 0),
    CFG_ITEM_BOOL(Conf.focus, warp_always, 0),

    CFG_ITEM_BOOL(Conf, autoraise.enable, 0),
    CFG_ITEM_INT(Conf, autoraise.delay, 500),
};

/*
 * Module descriptor
 */
extern const EModule ModFocus;

const EModule   ModFocus = {
    "focus", NULL,
    FocusSighan,
    MOD_ITEMS(FocusIpcArray),
    MOD_ITEMS(FocusCfgItems)
};
