/********************************************************************
 KWin - the KDE window manager
 This file is part of the KDE project.

Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/

// This file contains things relevant to handling incoming events.

#include "client.h"

#include <kstartupinfo.h>
#include <kglobal.h>
#include <X11/extensions/shape.h>

#ifdef KWIN_BUILD_ACTIVITIES
#include "activities.h"
#endif
#include "cursor.h"
#include <QX11Info>
#include "rules.h"
#include "group.h"
#include "netinfo.h"
#include "screens.h"
#include "workspace.h"
#include "xcbutils.h"

namespace KWin
{

/**
 * Manages the clients. This means handling the very first maprequest:
 * reparenting, initial geometry, initial state, placement, etc.
 * Returns false if KWin is not going to manage this window.
 */
bool Client::manage(xcb_window_t w, bool isMapped)
{
    StackingUpdatesBlocker stacking_blocker(workspace());

    grabXServer();

    XWindowAttributes attr;
    if (!XGetWindowAttributes(display(), w, &attr)) {
        ungrabXServer();
        return false;
    }

    // From this place on, manage() must not return false
    block_geometry_updates = 1;
    pending_geometry_update = PendingGeometryForced; // Force update when finishing with geometry changes

    embedClient(w, attr);

    vis = attr.visual;
    bit_depth = attr.depth;

    // SELI TODO: Order all these things in some sane manner

    bool init_minimize = false;
    XWMHints* hints = XGetWMHints(display(), w);
    if (hints && (hints->flags & StateHint) && hints->initial_state == IconicState)
        init_minimize = true;
    if (hints)
        XFree(hints);
    if (isMapped)
        init_minimize = false; // If it's already mapped, ignore hint

    unsigned long properties[2];
    properties[WinInfo::PROTOCOLS] =
        NET::WMDesktop |
        NET::WMState |
        NET::WMWindowType |
        NET::WMStrut |
        NET::WMName |
        NET::WMIconGeometry |
        NET::WMIcon |
        NET::WMPid |
        NET::WMIconName |
        0;
    properties[WinInfo::PROTOCOLS2] =
        NET::WM2UserTime |
        NET::WM2StartupId |
        NET::WM2ExtendedStrut |
        NET::WM2Opacity |
        NET::WM2FullscreenMonitors |
        NET::WM2FrameOverlap |
        0;

    info = new WinInfo(this, display(), m_client, rootWindow(), properties, 2);

    m_colormap = attr.colormap;

    getResourceClass();
    getWindowRole();
    getWmClientLeader();
    getWmClientMachine();
    getSyncCounter();
    // First only read the caption text, so that setupWindowRules() can use it for matching,
    // and only then really set the caption using setCaption(), which checks for duplicates etc.
    // and also relies on rules already existing
    cap_normal = readName();
    setupWindowRules(false);
    setCaption(cap_normal, true);

    if (Xcb::Extensions::self()->isShapeAvailable())
        XShapeSelectInput(display(), window(), ShapeNotifyMask);
    detectShape(window());
    detectNoBorder();
    fetchIconicName();
    getWMHints(); // Needs to be done before readTransient() because of reading the group
    modal = (info->state() & NET::Modal) != 0;   // Needs to be valid before handling groups
    readTransient();
    getIcons();
    getWindowProtocols();
    getWmNormalHints(); // Get xSizeHint
    getMotifHints();
    getWmOpaqueRegion();

    // TODO: Try to obey all state information from info->state()

    original_skip_taskbar = skip_taskbar = (info->state() & NET::SkipTaskbar) != 0;
    skip_pager = (info->state() & NET::SkipPager) != 0;
    updateFirstInTabBox();

    setupCompositing();

    KStartupInfoId asn_id;
    KStartupInfoData asn_data;
    bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data);

    // Make sure that the input window is created before we update the stacking order
    updateInputWindow();

    workspace()->updateClientLayer(this);

    SessionInfo* session = workspace()->takeSessionInfo(this);
    if (session) {
        init_minimize = session->minimized;
        noborder = session->noBorder;
    }

    setShortcut(rules()->checkShortcut(session ? session->shortcut : QString(), true));

    init_minimize = rules()->checkMinimize(init_minimize, !isMapped);
    noborder = rules()->checkNoBorder(noborder, !isMapped);

    checkActivities();

    // Initial desktop placement
    if (session) {
        desk = session->desktop;
        if (session->onAllDesktops)
            desk = NET::OnAllDesktops;
        setOnActivities(session->activities);
    } else {
        // If this window is transient, ensure that it is opened on the
        // same window as its parent.  this is necessary when an application
        // starts up on a different desktop than is currently displayed
        if (isTransient()) {
            ClientList mainclients = mainClients();
            bool on_current = false;
            bool on_all = false;
            Client* maincl = NULL;
            // This is slightly duplicated from Placement::placeOnMainWindow()
            for (ClientList::ConstIterator it = mainclients.constBegin();
                    it != mainclients.constEnd();
                    ++it) {
                if (mainclients.count() > 1 && (*it)->isSpecialWindow())
                    continue; // Don't consider toolbars etc when placing
                maincl = *it;
                if ((*it)->isOnCurrentDesktop())
                    on_current = true;
                if ((*it)->isOnAllDesktops())
                    on_all = true;
            }
            if (on_all)
                desk = NET::OnAllDesktops;
            else if (on_current)
                desk = VirtualDesktopManager::self()->current();
            else if (maincl != NULL)
                desk = maincl->desktop();

            if (maincl)
                setOnActivities(maincl->activities());
        }
        if (info->desktop())
            desk = info->desktop(); // Window had the initial desktop property, force it
        if (desktop() == 0 && asn_valid && asn_data.desktop() != 0)
            desk = asn_data.desktop();
#ifdef KWIN_BUILD_ACTIVITIES
        if (!isMapped && !noborder && isNormalWindow() && !activitiesDefined) {
            //a new, regular window, when we're not recovering from a crash,
            //and it hasn't got an activity. let's try giving it the current one.
            //TODO: decide whether to keep this before the 4.6 release
            //TODO: if we are keeping it (at least as an option), replace noborder checking
            //with a public API for setting windows to be on all activities.
            //something like KWindowSystem::setOnAllActivities or
            //KActivityConsumer::setOnAllActivities
            setOnActivity(Activities::self()->current(), true);
        }
#endif
    }

    if (desk == 0)   // Assume window wants to be visible on the current desktop
        desk = isDesktop() ? static_cast<int>(NET::OnAllDesktops) : VirtualDesktopManager::self()->current();
    desk = rules()->checkDesktop(desk, !isMapped);
    if (desk != NET::OnAllDesktops)   // Do range check
        desk = qBound(1, desk, static_cast<int>(VirtualDesktopManager::self()->count()));
    info->setDesktop(desk);
    workspace()->updateOnAllDesktopsOfTransients(this);   // SELI TODO
    //onAllDesktopsChange(); // Decoration doesn't exist here yet

    QString activitiesList;
    activitiesList = rules()->checkActivity(activitiesList, !isMapped);
    if (!activitiesList.isEmpty())
        setOnActivities(activitiesList.split(','));

    QRect geom(attr.x, attr.y, attr.width, attr.height);
    bool placementDone = false;

    if (session)
        geom = session->geometry;

    QRect area;
    bool partial_keep_in_area = isMapped || session;
    if (isMapped || session)
        area = workspace()->clientArea(FullArea, geom.center(), desktop());
    else {
        int screen = asn_data.xinerama() == -1 ? screens()->current() : asn_data.xinerama();
        screen = rules()->checkScreen(screen, !isMapped);
        area = workspace()->clientArea(PlacementArea, screens()->geometry(screen).center(), desktop());
    }

    if (int type = checkFullScreenHack(geom)) {
        fullscreen_mode = FullScreenHack;
        if (rules()->checkStrictGeometry(false)) {
            geom = type == 2 // 1 = It's xinerama-aware fullscreen hack, 2 = It's full area
                   ? workspace()->clientArea(FullArea, geom.center(), desktop())
                   : workspace()->clientArea(ScreenArea, geom.center(), desktop());
        } else
            geom = workspace()->clientArea(FullScreenArea, geom.center(), desktop());
        placementDone = true;
    }

    if (isDesktop())
        // KWin doesn't manage desktop windows
        placementDone = true;

    bool usePosition = false;
    if (isMapped || session || placementDone)
        placementDone = true; // Use geometry
    else if (isTransient() && !isUtility() && !isDialog() && !isSplash())
        usePosition = true;
    else if (isTransient() && !hasNETSupport())
        usePosition = true;
    else if (isDialog() && hasNETSupport()) {
        // If the dialog is actually non-NETWM transient window, don't try to apply placement to it,
        // it breaks with too many things (xmms, display)
        if (mainClients().count() >= 1) {
#if 1
            // #78082 - Ok, it seems there are after all some cases when an application has a good
            // reason to specify a position for its dialog. Too bad other WMs have never bothered
            // with placement for dialogs, so apps always specify positions for their dialogs,
            // including such silly positions like always centered on the screen or under mouse.
            // Using ignoring requested position in window-specific settings helps, and now
            // there's also _NET_WM_FULL_PLACEMENT.
            usePosition = true;
#else
            ; // Force using placement policy
#endif
        } else
            usePosition = true;
    } else if (isSplash())
        ; // Force using placement policy
    else
        usePosition = true;
    if (!rules()->checkIgnoreGeometry(!usePosition, true)) {
        if (((xSizeHint.flags & PPosition)) ||
                (xSizeHint.flags & USPosition)) {
            placementDone = true;
            // Disobey xinerama placement option for now (#70943)
            area = workspace()->clientArea(PlacementArea, geom.center(), desktop());
        }
    }
    //if ( true ) // Size is always obeyed for now, only with constraints applied
    //    if (( xSizeHint.flags & USSize ) || ( xSizeHint.flags & PSize ))
    //        {
    //        // Keep in mind that we now actually have a size :-)
    //        }

    if (xSizeHint.flags & PMaxSize)
        geom.setSize(geom.size().boundedTo(
                         rules()->checkMaxSize(QSize(xSizeHint.max_width, xSizeHint.max_height))));
    if (xSizeHint.flags & PMinSize)
        geom.setSize(geom.size().expandedTo(
                         rules()->checkMinSize(QSize(xSizeHint.min_width, xSizeHint.min_height))));

    if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom()))
        placementDone = false; // Weird, do not trust.

    if (placementDone)
        move(geom.x(), geom.y());   // Before gravitating

    // Create client group if the window will have a decoration
    bool dontKeepInArea = false;
    setTabGroup(NULL);
    if (!noBorder()) {
        const bool autogrouping = rules()->checkAutogrouping(options->isAutogroupSimilarWindows());
        const bool autogroupInFg = rules()->checkAutogroupInForeground(options->isAutogroupInForeground());
        // Automatically add to previous groups on session restore
        if (session && session->tabGroupClient && !workspace()->hasClient(session->tabGroupClient))
            session->tabGroupClient = NULL;
        if (session && session->tabGroupClient && session->tabGroupClient != this) {
            tabBehind(session->tabGroupClient, autogroupInFg);
        } else if (isMapped && autogrouping) {
            // If the window is already mapped (Restarted KWin) add any windows that already have the
            // same geometry to the same client group. (May incorrectly handle maximized windows)
            foreach (Client *other, workspace()->clientList()) {
                if (other->maximizeMode() != MaximizeFull &&
                    geom == QRect(other->pos(), other->clientSize()) &&
                    desk == other->desktop() && activities() == other->activities()) {

                    tabBehind(other, autogroupInFg);
                    break;

                }
            }
        }
        if (!(tab_group || isMapped || session)) {
            // Attempt to automatically group similar windows
            Client* similar = findAutogroupCandidate();
            if (similar && !similar->noBorder()) {
                if (autogroupInFg) {
                    similar->setDesktop(desk); // can happen when grouping by id. ...
                    similar->setMinimized(false); // ... or anyway - still group, but "here" and visible
                }
                if (!similar->isMinimized()) { // do not attempt to tab in background of a hidden group
                    geom = QRect(similar->pos() + similar->clientPos(), similar->clientSize());
                    updateDecoration(false);
                    if (tabBehind(similar, autogroupInFg)) {
                        // Don't move entire group
                        geom = QRect(similar->pos() + similar->clientPos(), similar->clientSize());
                        placementDone = true;
                        dontKeepInArea = true;
                    }
                }
            }
        }
    }

    updateDecoration(false);   // Also gravitates
    // TODO: Is CentralGravity right here, when resizing is done after gravitating?
    plainResize(rules()->checkSize(sizeForClientSize(geom.size()), !isMapped));

    QPoint forced_pos = rules()->checkPosition(invalidPoint, !isMapped);
    if (forced_pos != invalidPoint) {
        move(forced_pos);
        placementDone = true;
        // Don't keep inside workarea if the window has specially configured position
        partial_keep_in_area = true;
        area = workspace()->clientArea(FullArea, geom.center(), desktop());
    }
    if (!placementDone) {
        // Placement needs to be after setting size
        Placement::self()->place(this, area);
        dontKeepInArea = true;
        placementDone = true;
    }

    // bugs #285967, #286146, #183694
    // geometry() now includes the requested size and the decoration and is at the correct screen/position (hopefully)
    // Maximization for oversized windows must happen NOW.
    // If we effectively pass keepInArea(), the window will resizeWithChecks() - i.e. constrained
    // to the combo of all screen MINUS all struts on the edges
    // If only one screen struts, this will affect screens as a side-effect, the window is artificailly shrinked
    // below the screen size and as result no more maximized what breaks KMainWindow's stupid width+1, height+1 hack
    // TODO: get KMainWindow a correct state storage what will allow to store the restore size as well.

    if (!session) { // has a better handling of this
        geom_restore = geometry(); // Remember restore geometry
        if (isMaximizable() && (width() >= area.width() || height() >= area.height())) {
            // Window is too large for the screen, maximize in the
            // directions necessary
            const QSize ss = workspace()->clientArea(ScreenArea, area.center(), desktop()).size();
            const QSize fs = workspace()->clientArea(FullArea, geom.center(), desktop()).size();
            const QSize cs = clientSize();
            int pseudo_max = Client::MaximizeRestore;
            if (width() >= area.width())
                pseudo_max |=  Client::MaximizeHorizontal;
            if (height() >= area.height())
                pseudo_max |=  Client::MaximizeVertical;

            // heuristics:
            // if decorated client is smaller than the entire screen, the user might want to move it around (multiscreen)
            // in this case, if the decorated client is bigger than the screen (+1), we don't take this as an
            // attempt for maximization, but just constrain the size (the window simply wants to be bigger)
            // NOTICE
            // i intended a second check on cs < area.size() ("the managed client ("minus border") is smaller
            // than the workspace") but gtk / gimp seems to store it's size including the decoration,
            // thus a former maximized window wil become non-maximized
            if (width() < fs.width() && (cs.width() > ss.width()+1))
                pseudo_max &= ~Client::MaximizeHorizontal;
            if (height() < fs.height() && (cs.height() > ss.height()+1))
                pseudo_max &= ~Client::MaximizeVertical;

            if (pseudo_max != Client::MaximizeRestore) {
                maximize((MaximizeMode)pseudo_max);
                // from now on, care about maxmode, since the maximization call will override mode for fix aspects
                dontKeepInArea |= (max_mode == Client::MaximizeFull);
                geom_restore = QRect(); // Use placement when unmaximizing ...
                if (!(max_mode & Client::MaximizeVertical)) {
                    geom_restore.setY(y());   // ...but only for horizontal direction
                    geom_restore.setHeight(height());
                }
                if (!(max_mode & Client::MaximizeHorizontal)) {
                    geom_restore.setX(x());   // ...but only for vertical direction
                    geom_restore.setWidth(width());
                }
            }
        }
    }

    if ((!isSpecialWindow() || isToolbar()) && isMovable() && !dontKeepInArea)
        keepInArea(area, partial_keep_in_area);

    updateShape();

    // CT: Extra check for stupid jdk 1.3.1. But should make sense in general
    // if client has initial state set to Iconic and is transient with a parent
    // window that is not Iconic, set init_state to Normal
    if (init_minimize && isTransient()) {
        ClientList mainclients = mainClients();
        for (ClientList::ConstIterator it = mainclients.constBegin();
                it != mainclients.constEnd();
                ++it)
            if ((*it)->isShown(true))
                init_minimize = false; // SELI TODO: Even e.g. for NET::Utility?
    }
    // If a dialog is shown for minimized window, minimize it too
    if (!init_minimize && isTransient() && mainClients().count() > 0) {
        bool visible_parent = false;
        // Use allMainClients(), to include also main clients of group transients
        // that have been optimized out in Client::checkGroupTransients()
        ClientList mainclients = allMainClients();
        for (ClientList::ConstIterator it = mainclients.constBegin();
                it != mainclients.constEnd();
                ++it)
            if ((*it)->isShown(true))
                visible_parent = true;
        if (!visible_parent) {
            init_minimize = true;
            demandAttention();
        }
    }

    if (init_minimize)
        minimize(true);   // No animation

    // Other settings from the previous session
    if (session) {
        // Session restored windows are not considered to be new windows WRT rules,
        // I.e. obey only forcing rules
        setKeepAbove(session->keepAbove);
        setKeepBelow(session->keepBelow);
        setSkipTaskbar(session->skipTaskbar, true);
        setSkipPager(session->skipPager);
        setSkipSwitcher(session->skipSwitcher);
        setShade(session->shaded ? ShadeNormal : ShadeNone);
        setOpacity(session->opacity);
        geom_restore = session->restore;
        if (session->maximized != MaximizeRestore) {
            maximize(MaximizeMode(session->maximized));
        }
        if (session->fullscreen == FullScreenHack)
            ; // Nothing, this should be already set again above
        else if (session->fullscreen != FullScreenNone) {
            setFullScreen(true, false);
            geom_fs_restore = session->fsrestore;
        }
    } else {
        // Window may want to be maximized
        // done after checking that the window isn't larger than the workarea, so that
        // the restore geometry from the checks above takes precedence, and window
        // isn't restored larger than the workarea
        MaximizeMode maxmode = static_cast<MaximizeMode>(
                                   ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) |
                                   ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0));
        MaximizeMode forced_maxmode = rules()->checkMaximize(maxmode, !isMapped);

        // Either hints were set to maximize, or is forced to maximize,
        // or is forced to non-maximize and hints were set to maximize
        if (forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore)
            maximize(forced_maxmode);

        // Read other initial states
        setShade(rules()->checkShade(info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped));
        setKeepAbove(rules()->checkKeepAbove(info->state() & NET::KeepAbove, !isMapped));
        setKeepBelow(rules()->checkKeepBelow(info->state() & NET::KeepBelow, !isMapped));
        setSkipTaskbar(rules()->checkSkipTaskbar(info->state() & NET::SkipTaskbar, !isMapped), true);
        setSkipPager(rules()->checkSkipPager(info->state() & NET::SkipPager, !isMapped));
        setSkipSwitcher(rules()->checkSkipSwitcher(false, !isMapped));
        if (info->state() & NET::DemandsAttention)
            demandAttention();
        if (info->state() & NET::Modal)
            setModal(true);
        if (fullscreen_mode != FullScreenHack && isFullScreenable())
            setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped), false);
    }

    updateAllowedActions(true);

    // Set initial user time directly
    m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : NULL, asn_valid ? &asn_data : NULL, session);
    group()->updateUserTime(m_userTime);   // And do what Client::updateUserTime() does

    // This should avoid flicker, because real restacking is done
    // only after manage() finishes because of blocking, but the window is shown sooner
    XLowerWindow(display(), frameId());
    if (session && session->stackingOrder != -1) {
        sm_stacking_order = session->stackingOrder;
        workspace()->restoreSessionStackingOrder(this);
    }

    if (compositing())
        // Sending ConfigureNotify is done when setting mapping state below,
        // Getting the first sync response means window is ready for compositing
        sendSyncRequest();
    else
        ready_for_painting = true; // set to true in case compositing is turned on later. bug #160393

    if (isShown(true)) {
        bool allow;
        if (session)
            allow = session->active &&
                    (!workspace()->wasUserInteraction() || workspace()->activeClient() == NULL ||
                     workspace()->activeClient()->isDesktop());
        else
            allow = workspace()->allowClientActivation(this, userTime(), false);

        if (!(isMapped || session)) {
            if (workspace()->sessionSaving()) {
                /*
                 * If we get a new window during session saving, we assume it's some 'save file?' dialog
                 * which the user really needs to see (to know why logout's stalled).
                 *
                 * Given the current session management protocol, I can't see a nicer way of doing this.
                 * Someday I'd like to see a protocol that tells the windowmanager who's doing SessionInteract.
                 */
                needsSessionInteract = true;
                //show the parent too
                ClientList mainclients = mainClients();
                for (ClientList::ConstIterator it = mainclients.constBegin();
                        it != mainclients.constEnd(); ++it) {
                    (*it)->setSessionInteract(true);
                }
            } else if (allow) {
                // also force if activation is allowed
                if (!isOnCurrentDesktop() && options->focusPolicyIsReasonable()) {
                    VirtualDesktopManager::self()->setCurrent(desktop());
                }
                /*if (!isOnCurrentActivity()) {
                    workspace()->setCurrentActivity( activities().first() );
                } FIXME no such method*/
            }
        }

        resetShowingDesktop(options->isShowDesktopIsMinimizeAll());

        if (isOnCurrentDesktop() && !isMapped && !allow && (!session || session->stackingOrder < 0))
            workspace()->restackClientUnderActive(this);

        updateVisibility();

        if (!isMapped) {
            if (allow && isOnCurrentDesktop()) {
                if (!isSpecialWindow())
                    if (options->focusPolicyIsReasonable() && wantsTabFocus())
                        workspace()->requestFocus(this);
            } else if (!session && !isSpecialWindow())
                demandAttention();
        }
    } else
        updateVisibility();
    assert(mapping_state != Withdrawn);
    m_managed = true;
    blockGeometryUpdates(false);

    if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) {
        // No known user time, set something old
        m_userTime = xTime() - 1000000;
        if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U)   // Let's be paranoid
            m_userTime = xTime() - 1000000 + 10;
    }

    //sendSyntheticConfigureNotify(); // Done when setting mapping state

    delete session;

    ungrabXServer();

    client_rules.discardTemporary();
    applyWindowRules(); // Just in case
    RuleBook::self()->discardUsed(this, false);   // Remove ApplyNow rules
    updateWindowRules(Rules::All); // Was blocked while !isManaged()

    updateCompositeBlocking(true);

    // TODO: there's a small problem here - isManaged() depends on the mapping state,
    // but this client is not yet in Workspace's client list at this point, will
    // be only done in addClient()
    emit clientManaging(this);
    return true;
}

// Called only from manage()
void Client::embedClient(xcb_window_t w, const XWindowAttributes& attr)
{
    assert(m_client == XCB_WINDOW_NONE);
    assert(frameId() == XCB_WINDOW_NONE);
    assert(m_wrapper == XCB_WINDOW_NONE);
    m_client = w;

    const xcb_visualid_t visualid = XVisualIDFromVisual(attr.visual);
    const uint32_t zero_value = 0;

    xcb_connection_t *conn = connection();

    // We don't want the window to be destroyed when we quit
    xcb_change_save_set(conn, XCB_SET_MODE_INSERT, m_client);

    xcb_change_window_attributes(conn, m_client, XCB_CW_EVENT_MASK, &zero_value);
    xcb_unmap_window(conn, m_client);
    xcb_configure_window(conn, m_client, XCB_CONFIG_WINDOW_BORDER_WIDTH, &zero_value);

    // Note: These values must match the order in the xcb_cw_t enum
    const uint32_t cw_values[] = {
        0,                                // back_pixmap
        0,                                // border_pixel
        static_cast<uint32_t>(attr.colormap),                    // colormap
        Cursor::x11Cursor(Qt::ArrowCursor)
    };

    const uint32_t cw_mask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL |
                             XCB_CW_COLORMAP | XCB_CW_CURSOR;

    const uint32_t common_event_mask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE |
                                       XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
                                       XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
                                       XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION |
                                       XCB_EVENT_MASK_KEYMAP_STATE |
                                       XCB_EVENT_MASK_FOCUS_CHANGE |
                                       XCB_EVENT_MASK_EXPOSURE |
                                       XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;

    const uint32_t frame_event_mask   = common_event_mask | XCB_EVENT_MASK_PROPERTY_CHANGE;
    const uint32_t wrapper_event_mask = common_event_mask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;

    const uint32_t client_event_mask = XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE |
                                       XCB_EVENT_MASK_COLOR_MAP_CHANGE |
                                       XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
                                       XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE;

    // Create the frame window
    xcb_window_t frame = xcb_generate_id(conn);
    xcb_create_window(conn, attr.depth, frame, rootWindow(), 0, 0, 1, 1, 0,
                      XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values);

    setWindowHandles(m_client, frame);

    // Create the wrapper window
    xcb_window_t wrapperId = xcb_generate_id(conn);
    xcb_create_window(conn, attr.depth, wrapperId, frame, 0, 0, 1, 1, 0,
                      XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values);
    m_wrapper.reset(wrapperId);

    xcb_reparent_window(conn, m_client, m_wrapper, 0, 0);

    // We could specify the event masks when we create the windows, but the original
    // Xlib code didn't.  Let's preserve that behavior here for now so we don't end up
    // receiving any unexpected events from the wrapper creation or the reparenting.
    xcb_change_window_attributes(conn, frame,   XCB_CW_EVENT_MASK, &frame_event_mask);
    xcb_change_window_attributes(conn, m_wrapper, XCB_CW_EVENT_MASK, &wrapper_event_mask);
    xcb_change_window_attributes(conn, m_client,  XCB_CW_EVENT_MASK, &client_event_mask);

    updateMouseGrab();
}

// To accept "mainwindow#1" to "mainwindow#2"
static QByteArray truncatedWindowRole(QByteArray a)
{
    int i = a.indexOf('#');
    if (i == -1)
        return a;
    QByteArray b(a);
    b.truncate(i);
    return b;
}

Client* Client::findAutogroupCandidate() const
{
    // Attempt to find a similar window to the input. If we find multiple possibilities that are in
    // different groups then ignore all of them. This function is for automatic window grouping.
    Client *found = NULL;

    // See if the window has a group ID to match with
    QString wGId = rules()->checkAutogroupById(QString());
    if (!wGId.isEmpty()) {
        foreach (Client *c, workspace()->clientList()) {
            if (activities() != c->activities())
                continue; // don't cross activities
            if (wGId == c->rules()->checkAutogroupById(QString())) {
                if (found && found->tabGroup() != c->tabGroup()) { // We've found two, ignore both
                    found = NULL;
                    break; // Continue to the next test
                }
                found = c;
            }
        }
        if (found)
            return found;
    }

    // If this is a transient window don't take a guess
    if (isTransient())
        return NULL;

    // If we don't have an ID take a guess
    if (rules()->checkAutogrouping(options->isAutogroupSimilarWindows())) {
        QByteArray wRole = truncatedWindowRole(windowRole());
        foreach (Client *c, workspace()->clientList()) {
            if (desktop() != c->desktop() || activities() != c->activities())
                continue;
            QByteArray wRoleB = truncatedWindowRole(c->windowRole());
            if (resourceClass() == c->resourceClass() &&  // Same resource class
                    wRole == wRoleB && // Same window role
                    c->isNormalWindow()) { // Normal window TODO: Can modal windows be "normal"?
                if (found && found->tabGroup() != c->tabGroup())   // We've found two, ignore both
                    return NULL;
                found = c;
            }
        }
    }

    return found;
}

} // namespace
