diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -444,7 +444,6 @@ linux_dmabuf.cpp logind.cpp main.cpp - manage.cpp modifier_only_shortcuts.cpp moving_client_x11_filter.cpp netinfo.cpp diff --git a/manage.cpp b/manage.cpp deleted file mode 100644 --- a/manage.cpp +++ /dev/null @@ -1,691 +0,0 @@ -/******************************************************************** - KWin - the KDE window manager - This file is part of the KDE project. - -Copyright (C) 1999, 2000 Matthias Ettrich -Copyright (C) 2003 Lubos Lunak - -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 . -*********************************************************************/ - -// This file contains things relevant to handling incoming events. - -#include "x11client.h" - -#include - -#ifdef KWIN_BUILD_ACTIVITIES -#include "activities.h" -#endif -#include "composite.h" -#include "cursor.h" -#include "rules.h" -#include "group.h" -#include "netinfo.h" -#include "screens.h" -#include "workspace.h" -#include "xcbutils.h" - -#include - -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 X11Client::manage(xcb_window_t w, bool isMapped) -{ - StackingUpdatesBlocker stacking_blocker(workspace()); - - Xcb::WindowAttributes attr(w); - Xcb::WindowGeometry windowGeometry(w); - if (attr.isNull() || windowGeometry.isNull()) { - return false; - } - - // From this place on, manage() must not return false - blockGeometryUpdates(); - setPendingGeometryUpdate(PendingGeometryForced); // Force update when finishing with geometry changes - - embedClient(w, attr->visual, attr->colormap, windowGeometry->depth); - - m_visual = attr->visual; - bit_depth = windowGeometry->depth; - - // SELI TODO: Order all these things in some sane manner - - const NET::Properties properties = - NET::WMDesktop | - NET::WMState | - NET::WMWindowType | - NET::WMStrut | - NET::WMName | - NET::WMIconGeometry | - NET::WMIcon | - NET::WMPid | - NET::WMIconName; - const NET::Properties2 properties2 = - NET::WM2BlockCompositing | - NET::WM2WindowClass | - NET::WM2WindowRole | - NET::WM2UserTime | - NET::WM2StartupId | - NET::WM2ExtendedStrut | - NET::WM2Opacity | - NET::WM2FullscreenMonitors | - NET::WM2FrameOverlap | - NET::WM2GroupLeader | - NET::WM2Urgency | - NET::WM2Input | - NET::WM2Protocols | - NET::WM2InitialMappingState | - NET::WM2IconPixmap | - NET::WM2OpaqueRegion | - NET::WM2DesktopFileName | - NET::WM2GTKFrameExtents; - - auto wmClientLeaderCookie = fetchWmClientLeader(); - auto skipCloseAnimationCookie = fetchSkipCloseAnimation(); - auto showOnScreenEdgeCookie = fetchShowOnScreenEdge(); - auto colorSchemeCookie = fetchColorScheme(); - auto firstInTabBoxCookie = fetchFirstInTabBox(); - auto transientCookie = fetchTransient(); - auto activitiesCookie = fetchActivities(); - auto applicationMenuServiceNameCookie = fetchApplicationMenuServiceName(); - auto applicationMenuObjectPathCookie = fetchApplicationMenuObjectPath(); - - m_geometryHints.init(window()); - m_motif.init(window()); - info = new WinInfo(this, m_client, rootWindow(), properties, properties2); - - if (isDesktop() && bit_depth == 32) { - // force desktop windows to be opaque. It's a desktop after all, there is no window below - bit_depth = 24; - } - - // If it's already mapped, ignore hint - bool init_minimize = !isMapped && (info->initialMappingState() == NET::Iconic); - - m_colormap = attr->colormap; - - getResourceClass(); - readWmClientLeader(wmClientLeaderCookie); - 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); - - connect(this, &X11Client::windowClassChanged, this, &X11Client::evaluateWindowRules); - - if (Xcb::Extensions::self()->isShapeAvailable()) - xcb_shape_select_input(connection(), window(), true); - detectShape(window()); - detectNoBorder(); - fetchIconicName(); - setClientFrameExtents(info->gtkFrameExtents()); - - // Needs to be done before readTransient() because of reading the group - checkGroup(); - updateUrgency(); - updateAllowedActions(); // Group affects isMinimizable() - - setModal((info->state() & NET::Modal) != 0); // Needs to be valid before handling groups - readTransientProperty(transientCookie); - setDesktopFileName(rules()->checkDesktopFile(QByteArray(info->desktopFileName()), true).toUtf8()); - getIcons(); - connect(this, &X11Client::desktopFileNameChanged, this, &X11Client::getIcons); - - m_geometryHints.read(); - getMotifHints(); - getWmOpaqueRegion(); - readSkipCloseAnimation(skipCloseAnimationCookie); - - // TODO: Try to obey all state information from info->state() - - setOriginalSkipTaskbar((info->state() & NET::SkipTaskbar) != 0); - setSkipPager((info->state() & NET::SkipPager) != 0); - setSkipSwitcher((info->state() & NET::SkipSwitcher) != 0); - readFirstInTabBox(firstInTabBoxCookie); - - 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); - - readActivities(activitiesCookie); - - // Initial desktop placement - int desk = 0; - 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()) { - auto mainclients = mainClients(); - bool on_current = false; - bool on_all = false; - AbstractClient* maincl = nullptr; - // This is slightly duplicated from Placement::placeOnMainWindow() - for (auto it = mainclients.constBegin(); - it != mainclients.constEnd(); - ++it) { - if (mainclients.count() > 1 && // A group-transient - (*it)->isSpecialWindow() && // Don't consider toolbars etc when placing - !(info->state() & NET::Modal)) // except when it's modal (blocks specials as well) - continue; - 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 != nullptr) - desk = maincl->desktop(); - - if (maincl) - setOnActivities(maincl->activities()); - } else { // a transient shall appear on its leader and not drag that around - 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 (Activities::self() && !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(NET::OnAllDesktops) : VirtualDesktopManager::self()->current(); - desk = rules()->checkDesktop(desk, !isMapped); - if (desk != NET::OnAllDesktops) // Do range check - desk = qBound(1, desk, static_cast(VirtualDesktopManager::self()->count())); - setDesktop(desk); - 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(QStringLiteral(","))); - - QRect geom(windowGeometry.rect()); - 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()); - checkOffscreenPosition(&geom, area); - } 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 (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 (m_geometryHints.hasPosition()) { - placementDone = true; - // Disobey xinerama placement option for now (#70943) - area = workspace()->clientArea(PlacementArea, geom.center(), desktop()); - } - } - - if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom())) - placementDone = false; // Weird, do not trust. - - if (placementDone) { - QPoint position = geom.topLeft(); - // Session contains the position of the frame geometry before gravitating. - if (!session) { - position = clientPosToFramePos(position); - } - move(position); - } - - // Create client group if the window will have a decoration - bool dontKeepInArea = false; - readColorScheme(colorSchemeCookie); - - readApplicationMenuServiceName(applicationMenuServiceNameCookie); - readApplicationMenuObjectPath(applicationMenuObjectPathCookie); - - 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); - // The client may have been moved to another screen, update placement area. - area = workspace()->clientArea(PlacementArea, this); - 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 = frameGeometry(); // 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 QRect fsa = workspace()->clientArea(FullArea, geom.center(), desktop()); - const QSize cs = clientSize(); - int pseudo_max = ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | - ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0); - if (width() >= area.width()) - pseudo_max |= MaximizeHorizontal; - if (height() >= area.height()) - pseudo_max |= 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 - bool keepInFsArea = false; - if (width() < fsa.width() && (cs.width() > ss.width()+1)) { - pseudo_max &= ~MaximizeHorizontal; - keepInFsArea = true; - } - if (height() < fsa.height() && (cs.height() > ss.height()+1)) { - pseudo_max &= ~MaximizeVertical; - keepInFsArea = true; - } - - if (pseudo_max != MaximizeRestore) { - maximize((MaximizeMode)pseudo_max); - // from now on, care about maxmode, since the maximization call will override mode for fix aspects - dontKeepInArea |= (max_mode == MaximizeFull); - geom_restore = QRect(); // Use placement when unmaximizing ... - if (!(max_mode & MaximizeVertical)) { - geom_restore.setY(y()); // ...but only for horizontal direction - geom_restore.setHeight(height()); - } - if (!(max_mode & MaximizeHorizontal)) { - geom_restore.setX(x()); // ...but only for vertical direction - geom_restore.setWidth(width()); - } - } - if (keepInFsArea) - keepInArea(fsa, partial_keep_in_area); - } - } - - 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()) { - auto mainclients = mainClients(); - for (auto 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 && - workspace()->sessionManager()->state() != SessionState::Saving) { - bool visible_parent = false; - // Use allMainClients(), to include also main clients of group transients - // that have been optimized out in X11Client::checkGroupTransients() - auto mainclients = allMainClients(); - for (auto 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); - setOriginalSkipTaskbar(session->skipTaskbar); - 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 != FullScreenNone) { - setFullScreen(true, false); - geom_fs_restore = session->fsrestore; - } - checkOffscreenPosition(&geom_restore, area); - checkOffscreenPosition(&geom_fs_restore, area); - } 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( - ((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)); - setOriginalSkipTaskbar(rules()->checkSkipTaskbar(info->state() & NET::SkipTaskbar, !isMapped)); - setSkipPager(rules()->checkSkipPager(info->state() & NET::SkipPager, !isMapped)); - setSkipSwitcher(rules()->checkSkipSwitcher(info->state() & NET::SkipSwitcher, !isMapped)); - if (info->state() & NET::DemandsAttention) - demandAttention(); - if (info->state() & NET::Modal) - setModal(true); - - setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped), false); - } - - updateAllowedActions(true); - - // Set initial user time directly - m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : nullptr, asn_valid ? &asn_data : nullptr, session); - group()->updateUserTime(m_userTime); // And do what X11Client::updateUserTime() does - - // This should avoid flicker, because real restacking is done - // only after manage() finishes because of blocking, but the window is shown sooner - m_frame.lower(); - 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() == nullptr || - workspace()->activeClient()->isDesktop()); - else - allow = workspace()->allowClientActivation(this, userTime(), false); - - const bool isSessionSaving = workspace()->sessionManager()->state() == SessionState::Saving; - - // If session saving, force showing new windows (i.e. "save file?" dialogs etc.) - // also force if activation is allowed - if( !isOnCurrentDesktop() && !isMapped && !session && ( allow || isSessionSaving )) - VirtualDesktopManager::self()->setCurrent( desktop()); - - // If the window is on an inactive activity during session saving, temporarily force it to show. - if( !isMapped && !session && isSessionSaving && !isOnCurrentActivity()) { - setSessionActivityOverride( true ); - foreach( AbstractClient* c, mainClients()) { - if (X11Client *mc = dynamic_cast(c)) { - mc->setSessionActivityOverride(true); - } - } - } - - 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(); - Q_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; - - discardTemporaryRules(); - applyWindowRules(); // Just in case - RuleBook::self()->discardUsed(this, false); // Remove ApplyNow rules - updateWindowRules(Rules::All); // Was blocked while !isManaged() - - setBlockingCompositing(info->isBlockingCompositing()); - readShowOnScreenEdge(showOnScreenEdgeCookie); - - // Forward all opacity values to the frame in case there'll be other CM running. - connect(Compositor::self(), &Compositor::compositingToggled, this, - [this](bool active) { - if (active) { - return; - } - if (opacity() == 1.0) { - return; - } - NETWinInfo info(connection(), frameId(), rootWindow(), NET::Properties(), NET::Properties2()); - info.setOpacity(static_cast(opacity() * 0xffffffff)); - } - ); - - // 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 X11Client::embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth) -{ - Q_ASSERT(m_client == XCB_WINDOW_NONE); - Q_ASSERT(frameId() == XCB_WINDOW_NONE); - Q_ASSERT(m_wrapper == XCB_WINDOW_NONE); - m_client.reset(w, false); - - 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); - - m_client.selectInput(zero_value); - m_client.unmap(); - m_client.setBorderWidth(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 - 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 | XCB_EVENT_MASK_VISIBILITY_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, depth, frame, rootWindow(), 0, 0, 1, 1, 0, - XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); - m_frame.reset(frame); - - setWindowHandles(m_client); - - // Create the wrapper window - xcb_window_t wrapperId = xcb_generate_id(conn); - xcb_create_window(conn, depth, wrapperId, frame, 0, 0, 1, 1, 0, - XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); - m_wrapper.reset(wrapperId); - - m_client.reparent(m_wrapper); - - // 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. - m_frame.selectInput(frame_event_mask); - m_wrapper.selectInput(wrapper_event_mask); - m_client.selectInput(client_event_mask); - - updateMouseGrab(); -} - -} // namespace diff --git a/x11client.cpp b/x11client.cpp --- a/x11client.cpp +++ b/x11client.cpp @@ -31,6 +31,7 @@ #include "deleted.h" #include "focuschain.h" #include "group.h" +#include "netinfo.h" #include "screens.h" #include "shadow.h" #ifdef KWIN_BUILD_TABBOX @@ -44,6 +45,7 @@ #include // KDE #include +#include #include #include // Qt @@ -320,6 +322,652 @@ deleteClient(this); } +/** + * 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 X11Client::manage(xcb_window_t w, bool isMapped) +{ + StackingUpdatesBlocker stacking_blocker(workspace()); + + Xcb::WindowAttributes attr(w); + Xcb::WindowGeometry windowGeometry(w); + if (attr.isNull() || windowGeometry.isNull()) { + return false; + } + + // From this place on, manage() must not return false + blockGeometryUpdates(); + setPendingGeometryUpdate(PendingGeometryForced); // Force update when finishing with geometry changes + + embedClient(w, attr->visual, attr->colormap, windowGeometry->depth); + + m_visual = attr->visual; + bit_depth = windowGeometry->depth; + + // SELI TODO: Order all these things in some sane manner + + const NET::Properties properties = + NET::WMDesktop | + NET::WMState | + NET::WMWindowType | + NET::WMStrut | + NET::WMName | + NET::WMIconGeometry | + NET::WMIcon | + NET::WMPid | + NET::WMIconName; + const NET::Properties2 properties2 = + NET::WM2BlockCompositing | + NET::WM2WindowClass | + NET::WM2WindowRole | + NET::WM2UserTime | + NET::WM2StartupId | + NET::WM2ExtendedStrut | + NET::WM2Opacity | + NET::WM2FullscreenMonitors | + NET::WM2FrameOverlap | + NET::WM2GroupLeader | + NET::WM2Urgency | + NET::WM2Input | + NET::WM2Protocols | + NET::WM2InitialMappingState | + NET::WM2IconPixmap | + NET::WM2OpaqueRegion | + NET::WM2DesktopFileName | + NET::WM2GTKFrameExtents; + + auto wmClientLeaderCookie = fetchWmClientLeader(); + auto skipCloseAnimationCookie = fetchSkipCloseAnimation(); + auto showOnScreenEdgeCookie = fetchShowOnScreenEdge(); + auto colorSchemeCookie = fetchColorScheme(); + auto firstInTabBoxCookie = fetchFirstInTabBox(); + auto transientCookie = fetchTransient(); + auto activitiesCookie = fetchActivities(); + auto applicationMenuServiceNameCookie = fetchApplicationMenuServiceName(); + auto applicationMenuObjectPathCookie = fetchApplicationMenuObjectPath(); + + m_geometryHints.init(window()); + m_motif.init(window()); + info = new WinInfo(this, m_client, rootWindow(), properties, properties2); + + if (isDesktop() && bit_depth == 32) { + // force desktop windows to be opaque. It's a desktop after all, there is no window below + bit_depth = 24; + } + + // If it's already mapped, ignore hint + bool init_minimize = !isMapped && (info->initialMappingState() == NET::Iconic); + + m_colormap = attr->colormap; + + getResourceClass(); + readWmClientLeader(wmClientLeaderCookie); + 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); + + connect(this, &X11Client::windowClassChanged, this, &X11Client::evaluateWindowRules); + + if (Xcb::Extensions::self()->isShapeAvailable()) + xcb_shape_select_input(connection(), window(), true); + detectShape(window()); + detectNoBorder(); + fetchIconicName(); + setClientFrameExtents(info->gtkFrameExtents()); + + // Needs to be done before readTransient() because of reading the group + checkGroup(); + updateUrgency(); + updateAllowedActions(); // Group affects isMinimizable() + + setModal((info->state() & NET::Modal) != 0); // Needs to be valid before handling groups + readTransientProperty(transientCookie); + setDesktopFileName(rules()->checkDesktopFile(QByteArray(info->desktopFileName()), true).toUtf8()); + getIcons(); + connect(this, &X11Client::desktopFileNameChanged, this, &X11Client::getIcons); + + m_geometryHints.read(); + getMotifHints(); + getWmOpaqueRegion(); + readSkipCloseAnimation(skipCloseAnimationCookie); + + // TODO: Try to obey all state information from info->state() + + setOriginalSkipTaskbar((info->state() & NET::SkipTaskbar) != 0); + setSkipPager((info->state() & NET::SkipPager) != 0); + setSkipSwitcher((info->state() & NET::SkipSwitcher) != 0); + readFirstInTabBox(firstInTabBoxCookie); + + 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); + + readActivities(activitiesCookie); + + // Initial desktop placement + int desk = 0; + 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()) { + auto mainclients = mainClients(); + bool on_current = false; + bool on_all = false; + AbstractClient* maincl = nullptr; + // This is slightly duplicated from Placement::placeOnMainWindow() + for (auto it = mainclients.constBegin(); + it != mainclients.constEnd(); + ++it) { + if (mainclients.count() > 1 && // A group-transient + (*it)->isSpecialWindow() && // Don't consider toolbars etc when placing + !(info->state() & NET::Modal)) // except when it's modal (blocks specials as well) + continue; + 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 != nullptr) + desk = maincl->desktop(); + + if (maincl) + setOnActivities(maincl->activities()); + } else { // a transient shall appear on its leader and not drag that around + 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 (Activities::self() && !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(NET::OnAllDesktops) : VirtualDesktopManager::self()->current(); + desk = rules()->checkDesktop(desk, !isMapped); + if (desk != NET::OnAllDesktops) // Do range check + desk = qBound(1, desk, static_cast(VirtualDesktopManager::self()->count())); + setDesktop(desk); + 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(QStringLiteral(","))); + + QRect geom(windowGeometry.rect()); + 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()); + checkOffscreenPosition(&geom, area); + } 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 (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 (m_geometryHints.hasPosition()) { + placementDone = true; + // Disobey xinerama placement option for now (#70943) + area = workspace()->clientArea(PlacementArea, geom.center(), desktop()); + } + } + + if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom())) + placementDone = false; // Weird, do not trust. + + if (placementDone) { + QPoint position = geom.topLeft(); + // Session contains the position of the frame geometry before gravitating. + if (!session) { + position = clientPosToFramePos(position); + } + move(position); + } + + // Create client group if the window will have a decoration + bool dontKeepInArea = false; + readColorScheme(colorSchemeCookie); + + readApplicationMenuServiceName(applicationMenuServiceNameCookie); + readApplicationMenuObjectPath(applicationMenuObjectPathCookie); + + 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); + // The client may have been moved to another screen, update placement area. + area = workspace()->clientArea(PlacementArea, this); + 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 = frameGeometry(); // 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 QRect fsa = workspace()->clientArea(FullArea, geom.center(), desktop()); + const QSize cs = clientSize(); + int pseudo_max = ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | + ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0); + if (width() >= area.width()) + pseudo_max |= MaximizeHorizontal; + if (height() >= area.height()) + pseudo_max |= 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 + bool keepInFsArea = false; + if (width() < fsa.width() && (cs.width() > ss.width()+1)) { + pseudo_max &= ~MaximizeHorizontal; + keepInFsArea = true; + } + if (height() < fsa.height() && (cs.height() > ss.height()+1)) { + pseudo_max &= ~MaximizeVertical; + keepInFsArea = true; + } + + if (pseudo_max != MaximizeRestore) { + maximize((MaximizeMode)pseudo_max); + // from now on, care about maxmode, since the maximization call will override mode for fix aspects + dontKeepInArea |= (max_mode == MaximizeFull); + geom_restore = QRect(); // Use placement when unmaximizing ... + if (!(max_mode & MaximizeVertical)) { + geom_restore.setY(y()); // ...but only for horizontal direction + geom_restore.setHeight(height()); + } + if (!(max_mode & MaximizeHorizontal)) { + geom_restore.setX(x()); // ...but only for vertical direction + geom_restore.setWidth(width()); + } + } + if (keepInFsArea) + keepInArea(fsa, partial_keep_in_area); + } + } + + 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()) { + auto mainclients = mainClients(); + for (auto 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 && + workspace()->sessionManager()->state() != SessionState::Saving) { + bool visible_parent = false; + // Use allMainClients(), to include also main clients of group transients + // that have been optimized out in X11Client::checkGroupTransients() + auto mainclients = allMainClients(); + for (auto 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); + setOriginalSkipTaskbar(session->skipTaskbar); + 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 != FullScreenNone) { + setFullScreen(true, false); + geom_fs_restore = session->fsrestore; + } + checkOffscreenPosition(&geom_restore, area); + checkOffscreenPosition(&geom_fs_restore, area); + } 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( + ((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)); + setOriginalSkipTaskbar(rules()->checkSkipTaskbar(info->state() & NET::SkipTaskbar, !isMapped)); + setSkipPager(rules()->checkSkipPager(info->state() & NET::SkipPager, !isMapped)); + setSkipSwitcher(rules()->checkSkipSwitcher(info->state() & NET::SkipSwitcher, !isMapped)); + if (info->state() & NET::DemandsAttention) + demandAttention(); + if (info->state() & NET::Modal) + setModal(true); + + setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped), false); + } + + updateAllowedActions(true); + + // Set initial user time directly + m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : nullptr, asn_valid ? &asn_data : nullptr, session); + group()->updateUserTime(m_userTime); // And do what X11Client::updateUserTime() does + + // This should avoid flicker, because real restacking is done + // only after manage() finishes because of blocking, but the window is shown sooner + m_frame.lower(); + 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() == nullptr || + workspace()->activeClient()->isDesktop()); + else + allow = workspace()->allowClientActivation(this, userTime(), false); + + const bool isSessionSaving = workspace()->sessionManager()->state() == SessionState::Saving; + + // If session saving, force showing new windows (i.e. "save file?" dialogs etc.) + // also force if activation is allowed + if( !isOnCurrentDesktop() && !isMapped && !session && ( allow || isSessionSaving )) + VirtualDesktopManager::self()->setCurrent( desktop()); + + // If the window is on an inactive activity during session saving, temporarily force it to show. + if( !isMapped && !session && isSessionSaving && !isOnCurrentActivity()) { + setSessionActivityOverride( true ); + foreach( AbstractClient* c, mainClients()) { + if (X11Client *mc = dynamic_cast(c)) { + mc->setSessionActivityOverride(true); + } + } + } + + 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(); + Q_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; + + discardTemporaryRules(); + applyWindowRules(); // Just in case + RuleBook::self()->discardUsed(this, false); // Remove ApplyNow rules + updateWindowRules(Rules::All); // Was blocked while !isManaged() + + setBlockingCompositing(info->isBlockingCompositing()); + readShowOnScreenEdge(showOnScreenEdgeCookie); + + // Forward all opacity values to the frame in case there'll be other CM running. + connect(Compositor::self(), &Compositor::compositingToggled, this, + [this](bool active) { + if (active) { + return; + } + if (opacity() == 1.0) { + return; + } + NETWinInfo info(connection(), frameId(), rootWindow(), NET::Properties(), NET::Properties2()); + info.setOpacity(static_cast(opacity() * 0xffffffff)); + } + ); + + // 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 X11Client::embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth) +{ + Q_ASSERT(m_client == XCB_WINDOW_NONE); + Q_ASSERT(frameId() == XCB_WINDOW_NONE); + Q_ASSERT(m_wrapper == XCB_WINDOW_NONE); + m_client.reset(w, false); + + 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); + + m_client.selectInput(zero_value); + m_client.unmap(); + m_client.setBorderWidth(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 + 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 | XCB_EVENT_MASK_VISIBILITY_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, depth, frame, rootWindow(), 0, 0, 1, 1, 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); + m_frame.reset(frame); + + setWindowHandles(m_client); + + // Create the wrapper window + xcb_window_t wrapperId = xcb_generate_id(conn); + xcb_create_window(conn, depth, wrapperId, frame, 0, 0, 1, 1, 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); + m_wrapper.reset(wrapperId); + + m_client.reparent(m_wrapper); + + // 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. + m_frame.selectInput(frame_event_mask); + m_wrapper.selectInput(wrapper_event_mask); + m_client.selectInput(client_event_mask); + + updateMouseGrab(); +} + void X11Client::updateInputWindow() { if (!Xcb::Extensions::self()->isShapeInputAvailable())