diff --git a/client.cpp b/client.cpp index a654b7fc4..e973d3dfb 100644 --- a/client.cpp +++ b/client.cpp @@ -1,2159 +1,2174 @@ /******************************************************************** 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 . *********************************************************************/ // own #include "client.h" // kwin #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "atoms.h" #include "client_machine.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "focuschain.h" #include "group.h" #include "shadow.h" #include "workspace.h" #include "screenedge.h" #include "decorations/decorationbridge.h" #include "decorations/decoratedclient.h" #include #include // KDE #include #include #include // Qt #include #include #include #include #include // XLib #include #include #include // system #include #include // Put all externs before the namespace statement to allow the linker // to resolve them properly namespace KWin { const long ClientWinMask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_KEYMAP_STATE | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION | // need this, too! XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Creating a client: // - only by calling Workspace::createClient() // - it creates a new client and calls manage() for it // // Destroying a client: // - destroyClient() - only when the window itself has been destroyed // - releaseWindow() - the window is kept, only the client itself is destroyed /** * \class Client client.h * \brief The Client class encapsulates a window decoration frame. */ /** * This ctor is "dumb" - it only initializes data. All the real initialization * is done in manage(). */ Client::Client() : AbstractClient() , m_client() , m_wrapper() , m_frame() , m_activityUpdatesBlocked(false) , m_blockedActivityUpdatesRequireTransients(false) , m_moveResizeGrabWindow() , move_resize_has_keyboard_grab(false) , m_managed(false) , m_transientForId(XCB_WINDOW_NONE) , m_originalTransientForId(XCB_WINDOW_NONE) , shade_below(NULL) , m_motif(atoms->motif_wm_hints) , blocks_compositing(false) , shadeHoverTimer(NULL) , m_colormap(XCB_COLORMAP_NONE) , in_group(NULL) , tab_group(NULL) , ping_timer(NULL) , m_killHelperPID(0) , m_pingTimestamp(XCB_TIME_CURRENT_TIME) , m_userTime(XCB_TIME_CURRENT_TIME) // Not known yet , allowed_actions(0) , shade_geometry_change(false) , sm_stacking_order(-1) , activitiesDefined(false) , sessionActivityOverride(false) , needsXWindowMove(false) , m_decoInputExtent() , m_focusOutTimer(nullptr) , m_clientSideDecorated(false) { // TODO: Do all as initialization syncRequest.counter = syncRequest.alarm = XCB_NONE; syncRequest.timeout = syncRequest.failsafeTimeout = NULL; syncRequest.lastTimestamp = xTime(); syncRequest.isPending = false; // Set the initial mapping state mapping_state = Withdrawn; info = NULL; shade_mode = ShadeNone; deleting = false; fullscreen_mode = FullScreenNone; hidden = false; noborder = false; app_noborder = false; ignore_focus_stealing = false; check_active_modal = false; max_mode = MaximizeRestore; //Client to workspace connections require that each //client constructed be connected to the workspace wrapper geom = QRect(0, 0, 100, 100); // So that decorations don't start with size being (0,0) client_size = QSize(100, 100); ready_for_painting = false; // wait for first damage or sync reply connect(clientMachine(), &ClientMachine::localhostChanged, this, &Client::updateCaption); connect(options, &Options::condensedTitleChanged, this, &Client::updateCaption); connect(this, &Client::moveResizeCursorChanged, this, [this] (Qt::CursorShape cursor) { xcb_cursor_t nativeCursor = Cursor::x11Cursor(cursor); m_frame.defineCursor(nativeCursor); if (m_decoInputExtent.isValid()) m_decoInputExtent.defineCursor(nativeCursor); if (isMoveResize()) { // changing window attributes doesn't change cursor if there's pointer grab active xcb_change_active_pointer_grab(connection(), nativeCursor, xTime(), XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW); } }); // SELI TODO: Initialize xsizehints?? } /** * "Dumb" destructor. */ Client::~Client() { if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive ::kill(m_killHelperPID, SIGTERM); m_killHelperPID = 0; } //SWrapper::Client::clientRelease(this); if (syncRequest.alarm != XCB_NONE) xcb_sync_destroy_alarm(connection(), syncRequest.alarm); assert(!isMoveResize()); assert(m_client == XCB_WINDOW_NONE); assert(m_wrapper == XCB_WINDOW_NONE); //assert( frameId() == None ); assert(!check_active_modal); for (auto it = m_connections.constBegin(); it != m_connections.constEnd(); ++it) { disconnect(*it); } } // Use destroyClient() or releaseWindow(), Client instances cannot be deleted directly void Client::deleteClient(Client* c) { delete c; } /** * Releases the window. The client has done its job and the window is still existing. */ void Client::releaseWindow(bool on_shutdown) { assert(!deleting); deleting = true; destroyWindowManagementInterface(); Deleted* del = NULL; if (!on_shutdown) { del = Deleted::create(this); } if (isMoveResize()) emit clientFinishUserMovedResized(this); emit windowClosed(this, del); finishCompositing(); RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules StackingUpdatesBlocker blocker(workspace()); if (isMoveResize()) leaveMoveResize(); finishWindowRules(); blockGeometryUpdates(); if (isOnCurrentDesktop() && isShown(true)) addWorkspaceRepaint(visibleRect()); // Grab X during the release to make removing of properties, setting to withdrawn state // and repareting to root an atomic operation (http://lists.kde.org/?l=kde-devel&m=116448102901184&w=2) grabXServer(); exportMappingState(WithdrawnState); setModal(false); // Otherwise its mainwindow wouldn't get focus hidden = true; // So that it's not considered visible anymore (can't use hideClient(), it would set flags) if (!on_shutdown) workspace()->clientHidden(this); m_frame.unmap(); // Destroying decoration would cause ugly visual effect destroyDecoration(); cleanGrouping(); if (!on_shutdown) { workspace()->removeClient(this); // Only when the window is being unmapped, not when closing down KWin (NETWM sections 5.5,5.7) info->setDesktop(0); info->setState(0, info->state()); // Reset all state flags } else untab(); xcb_connection_t *c = connection(); m_client.deleteProperty(atoms->kde_net_wm_user_creation_time); m_client.deleteProperty(atoms->net_frame_extents); m_client.deleteProperty(atoms->kde_net_wm_frame_strut); m_client.reparent(rootWindow(), x(), y()); xcb_change_save_set(c, XCB_SET_MODE_DELETE, m_client); m_client.selectInput(XCB_EVENT_MASK_NO_EVENT); if (on_shutdown) // Map the window, so it can be found after another WM is started m_client.map(); // TODO: Preserve minimized, shaded etc. state? else // Make sure it's not mapped if the app unmapped it (#65279). The app // may do map+unmap before we initially map the window by calling rawShow() from manage(). m_client.unmap(); m_client.reset(); m_wrapper.reset(); m_frame.reset(); //frame = None; unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry if (!on_shutdown) { disownDataPassedToDeleted(); del->unrefWindow(); } deleteClient(this); ungrabXServer(); } /** * Like releaseWindow(), but this one is called when the window has been already destroyed * (E.g. The application closed it) */ void Client::destroyClient() { assert(!deleting); deleting = true; destroyWindowManagementInterface(); Deleted* del = Deleted::create(this); if (isMoveResize()) emit clientFinishUserMovedResized(this); emit windowClosed(this, del); finishCompositing(ReleaseReason::Destroyed); RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules StackingUpdatesBlocker blocker(workspace()); if (isMoveResize()) leaveMoveResize(); finishWindowRules(); blockGeometryUpdates(); if (isOnCurrentDesktop() && isShown(true)) addWorkspaceRepaint(visibleRect()); setModal(false); hidden = true; // So that it's not considered visible anymore workspace()->clientHidden(this); destroyDecoration(); cleanGrouping(); workspace()->removeClient(this); m_client.reset(); // invalidate m_wrapper.reset(); m_frame.reset(); //frame = None; unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry disownDataPassedToDeleted(); del->unrefWindow(); deleteClient(this); } void Client::updateInputWindow() { if (!Xcb::Extensions::self()->isShapeInputAvailable()) return; QRegion region; if (!noBorder() && isDecorated()) { const QMargins &r = decoration()->resizeOnlyBorders(); const int left = r.left(); const int top = r.top(); const int right = r.right(); const int bottom = r.bottom(); if (left != 0 || top != 0 || right != 0 || bottom != 0) { region = QRegion(-left, -top, decoration()->size().width() + left + right, decoration()->size().height() + top + bottom); region = region.subtracted(decoration()->rect()); } } if (region.isEmpty()) { m_decoInputExtent.reset(); return; } QRect bounds = region.boundingRect(); input_offset = bounds.topLeft(); // Move the bounding rect to screen coordinates bounds.translate(geometry().topLeft()); // Move the region to input window coordinates region.translate(-input_offset); if (!m_decoInputExtent.isValid()) { const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; const uint32_t values[] = {true, XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION }; m_decoInputExtent.create(bounds, XCB_WINDOW_CLASS_INPUT_ONLY, mask, values); if (mapping_state == Mapped) m_decoInputExtent.map(); } else { m_decoInputExtent.setGeometry(bounds); } const QVector rects = Xcb::regionToRects(region); xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, m_decoInputExtent, 0, 0, rects.count(), rects.constData()); } void Client::updateDecoration(bool check_workspace_pos, bool force) { if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) return; QRect oldgeom = geometry(); QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom()); blockGeometryUpdates(true); if (force) destroyDecoration(); if (!noBorder()) { createDecoration(oldgeom); } else destroyDecoration(); getShadow(); if (check_workspace_pos) checkWorkspacePosition(oldgeom, -2, oldClientGeom); updateInputWindow(); blockGeometryUpdates(false); updateFrameExtents(); } void Client::createDecoration(const QRect& oldgeom) { KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this); if (decoration) { QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection); connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::getShadow); connect(decoration, &KDecoration2::Decoration::resizeOnlyBordersChanged, this, &Client::updateInputWindow); connect(decoration, &KDecoration2::Decoration::bordersChanged, this, [this]() { updateFrameExtents(); GeometryUpdatesBlocker blocker(this); // TODO: this is obviously idempotent // calculateGravitation(true) would have to operate on the old border sizes // move(calculateGravitation(true)); // move(calculateGravitation(false)); QRect oldgeom = geometry(); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); if (!isShade()) checkWorkspacePosition(oldgeom); emit geometryShapeChanged(this, oldgeom); } ); connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::widthChanged, this, &Client::updateInputWindow); connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::heightChanged, this, &Client::updateInputWindow); } setDecoration(decoration); move(calculateGravitation(false)); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); if (Compositor::compositing()) { discardWindowPixmap(); } emit geometryShapeChanged(this, oldgeom); } void Client::destroyDecoration() { QRect oldgeom = geometry(); if (isDecorated()) { QPoint grav = calculateGravitation(true); AbstractClient::destroyDecoration(); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); move(grav); if (compositing()) discardWindowPixmap(); if (!deleting) { emit geometryShapeChanged(this, oldgeom); } } m_decoInputExtent.reset(); } void Client::layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const { if (!isDecorated()) { return; } QRect r = decoration()->rect(); NETStrut strut = info->frameOverlap(); // Ignore the overlap strut when compositing is disabled if (!compositing()) strut.left = strut.top = strut.right = strut.bottom = 0; else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1) { top = QRect(r.x(), r.y(), r.width(), r.height() / 3); left = QRect(r.x(), r.y() + top.height(), width() / 2, r.height() / 3); right = QRect(r.x() + left.width(), r.y() + top.height(), r.width() - left.width(), left.height()); bottom = QRect(r.x(), r.y() + top.height() + left.height(), r.width(), r.height() - left.height() - top.height()); return; } top = QRect(r.x(), r.y(), r.width(), borderTop() + strut.top); bottom = QRect(r.x(), r.y() + r.height() - borderBottom() - strut.bottom, r.width(), borderBottom() + strut.bottom); left = QRect(r.x(), r.y() + top.height(), borderLeft() + strut.left, r.height() - top.height() - bottom.height()); right = QRect(r.x() + r.width() - borderRight() - strut.right, r.y() + top.height(), borderRight() + strut.right, r.height() - top.height() - bottom.height()); } QRect Client::transparentRect() const { if (isShade()) return QRect(); NETStrut strut = info->frameOverlap(); // Ignore the strut when compositing is disabled or the decoration doesn't support it if (!compositing()) strut.left = strut.top = strut.right = strut.bottom = 0; else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1) return QRect(); const QRect r = QRect(clientPos(), clientSize()) .adjusted(strut.left, strut.top, -strut.right, -strut.bottom); if (r.isValid()) return r; return QRect(); } void Client::detectNoBorder() { if (shape()) { noborder = true; app_noborder = true; return; } switch(windowType()) { case NET::Desktop : case NET::Dock : case NET::TopMenu : case NET::Splash : case NET::Notification : case NET::OnScreenDisplay : noborder = true; app_noborder = true; break; case NET::Unknown : case NET::Normal : case NET::Toolbar : case NET::Menu : case NET::Dialog : case NET::Utility : noborder = false; break; default: abort(); } // NET::Override is some strange beast without clear definition, usually // just meaning "noborder", so let's treat it only as such flag, and ignore it as // a window type otherwise (SUPPORTED_WINDOW_TYPES_MASK doesn't include it) if (info->windowType(NET::OverrideMask) == NET::Override) { noborder = true; app_noborder = true; } } void Client::updateFrameExtents() { NETStrut strut; strut.left = borderLeft(); strut.right = borderRight(); strut.top = borderTop(); strut.bottom = borderBottom(); info->setFrameExtents(strut); } Xcb::Property Client::fetchGtkFrameExtents() const { return Xcb::Property(false, m_client, atoms->gtk_frame_extents, XCB_ATOM_CARDINAL, 0, 4); } void Client::readGtkFrameExtents(Xcb::Property &prop) { m_clientSideDecorated = !prop.isNull() && prop->type != 0; emit clientSideDecoratedChanged(); } void Client::detectGtkFrameExtents() { Xcb::Property prop = fetchGtkFrameExtents(); readGtkFrameExtents(prop); } /** * Resizes the decoration, and makes sure the decoration widget gets resize event * even if the size hasn't changed. This is needed to make sure the decoration * re-layouts (e.g. when maximization state changes, * the decoration may alter some borders, but the actual size * of the decoration stays the same). */ void Client::resizeDecoration() { triggerDecorationRepaint(); updateInputWindow(); } bool Client::noBorder() const { return noborder || isFullScreen(); } bool Client::userCanSetNoBorder() const { return !isFullScreen() && !isShade() && !tabGroup(); } void Client::setNoBorder(bool set) { if (!userCanSetNoBorder()) return; set = rules()->checkNoBorder(set); if (noborder == set) return; noborder = set; updateDecoration(true, false); updateWindowRules(Rules::NoBorder); } void Client::checkNoBorder() { setNoBorder(app_noborder); } bool Client::wantsShadowToBeRendered() const { return !isFullScreen() && maximizeMode() != MaximizeFull; } void Client::updateShape() { if (shape()) { // Workaround for #19644 - Shaped windows shouldn't have decoration if (!app_noborder) { // Only when shape is detected for the first time, still let the user to override app_noborder = true; noborder = rules()->checkNoBorder(true); updateDecoration(true); } if (noBorder()) { xcb_shape_combine(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_SHAPE_SK_BOUNDING, frameId(), clientPos().x(), clientPos().y(), window()); } } else if (app_noborder) { xcb_shape_mask(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, frameId(), 0, 0, XCB_PIXMAP_NONE); detectNoBorder(); app_noborder = noborder; noborder = rules()->checkNoBorder(noborder || m_motif.noBorder()); updateDecoration(true); } // Decoration mask (i.e. 'else' here) setting is done in setMask() // when the decoration calls it or when the decoration is created/destroyed updateInputShape(); if (compositing()) { addRepaintFull(); addWorkspaceRepaint(visibleRect()); // In case shape change removes part of this window } emit geometryShapeChanged(this, geometry()); } static Xcb::Window shape_helper_window(XCB_WINDOW_NONE); void Client::cleanupX11() { shape_helper_window.reset(); } void Client::updateInputShape() { if (hiddenPreview()) // Sets it to none, don't change return; if (Xcb::Extensions::self()->isShapeInputAvailable()) { // There appears to be no way to find out if a window has input // shape set or not, so always propagate the input shape // (it's the same like the bounding shape by default). // Also, build the shape using a helper window, not directly // in the frame window, because the sequence set-shape-to-frame, // remove-shape-of-client, add-input-shape-of-client has the problem // that after the second step there's a hole in the input shape // until the real shape of the client is added and that can make // the window lose focus (which is a problem with mouse focus policies) // TODO: It seems there is, after all - XShapeGetRectangles() - but maybe this is better if (!shape_helper_window.isValid()) shape_helper_window.create(QRect(0, 0, 1, 1)); shape_helper_window.resize(width(), height()); xcb_connection_t *c = connection(); xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING, shape_helper_window, 0, 0, frameId()); xcb_shape_combine(c, XCB_SHAPE_SO_SUBTRACT, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING, shape_helper_window, clientPos().x(), clientPos().y(), window()); xcb_shape_combine(c, XCB_SHAPE_SO_UNION, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT, shape_helper_window, clientPos().x(), clientPos().y(), window()); xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT, frameId(), 0, 0, shape_helper_window); } } void Client::hideClient(bool hide) { if (hidden == hide) return; hidden = hide; updateVisibility(); } /** * Returns whether the window is minimizable or not */ bool Client::isMinimizable() const { if (isSpecialWindow() && !isTransient()) return false; if (!rules()->checkMinimize(true)) return false; if (isTransient()) { // #66868 - Let other xmms windows be minimized when the mainwindow is minimized bool shown_mainwindow = false; auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) shown_mainwindow = true; if (!shown_mainwindow) return true; } #if 0 // This is here because kicker's taskbar doesn't provide separate entries // for windows with an explicitly given parent // TODO: perhaps this should be redone // Disabled for now, since at least modal dialogs should be minimizable // (resulting in the mainwindow being minimized too). if (transientFor() != NULL) return false; #endif if (!wantsTabFocus()) // SELI, TODO: - NET::Utility? why wantsTabFocus() - skiptaskbar? ? return false; return true; } void Client::doMinimize() { updateVisibility(); updateAllowedActions(); workspace()->updateMinimizedOfTransients(this); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Minimized); } QRect Client::iconGeometry() const { NETRect r = info->iconGeometry(); QRect geom(r.pos.x, r.pos.y, r.size.width, r.size.height); if (geom.isValid()) return geom; else { // Check all mainwindows of this window (recursively) foreach (AbstractClient * amainwin, mainClients()) { Client *mainwin = dynamic_cast(amainwin); if (!mainwin) { continue; } geom = mainwin->iconGeometry(); if (geom.isValid()) return geom; } // No mainwindow (or their parents) with icon geometry was found return AbstractClient::iconGeometry(); } } bool Client::isShadeable() const { return !isSpecialWindow() && !noBorder() && (rules()->checkShade(ShadeNormal) != rules()->checkShade(ShadeNone)); } void Client::setShade(ShadeMode mode) { if (mode == ShadeHover && isMove()) return; // causes geometry breaks and is probably nasty if (isSpecialWindow() || noBorder()) mode = ShadeNone; mode = rules()->checkShade(mode); if (shade_mode == mode) return; bool was_shade = isShade(); ShadeMode was_shade_mode = shade_mode; shade_mode = mode; // Decorations may turn off some borders when shaded // this has to happen _before_ the tab alignment since it will restrict the minimum geometry #if 0 if (decoration) decoration->borders(border_left, border_right, border_top, border_bottom); #endif // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Shaded); if (was_shade == isShade()) { // Decoration may want to update after e.g. hover-shade changes emit shadeChanged(); return; // No real change in shaded state } assert(isDecorated()); // noborder windows can't be shaded GeometryUpdatesBlocker blocker(this); // TODO: All this unmapping, resizing etc. feels too much duplicated from elsewhere if (isShade()) { // shade_mode == ShadeNormal addWorkspaceRepaint(visibleRect()); // Shade shade_geometry_change = true; QSize s(sizeForClientSize(QSize(clientSize()))); s.setHeight(borderTop() + borderBottom()); m_wrapper.selectInput(ClientWinMask); // Avoid getting UnmapNotify m_wrapper.unmap(); m_client.unmap(); m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); exportMappingState(IconicState); plainResize(s); shade_geometry_change = false; if (was_shade_mode == ShadeHover) { if (shade_below && workspace()->stackingOrder().indexOf(shade_below) > -1) workspace()->restack(this, shade_below, true); if (isActive()) workspace()->activateNextClient(this); } else if (isActive()) { workspace()->focusToNull(); } } else { shade_geometry_change = true; if (decoratedClient()) decoratedClient()->signalShadeChange(); QSize s(sizeForClientSize(clientSize())); shade_geometry_change = false; plainResize(s); geom_restore = geometry(); if ((shade_mode == ShadeHover || shade_mode == ShadeActivated) && rules()->checkAcceptFocus(info->input())) setActive(true); if (shade_mode == ShadeHover) { ToplevelList order = workspace()->stackingOrder(); // invalidate, since "this" could be the topmost toplevel and shade_below dangeling shade_below = NULL; // this is likely related to the index parameter?! for (int idx = order.indexOf(this) + 1; idx < order.count(); ++idx) { shade_below = qobject_cast(order.at(idx)); if (shade_below) { break; } } if (shade_below && shade_below->isNormalWindow()) workspace()->raiseClient(this); else shade_below = NULL; } m_wrapper.map(); m_client.map(); exportMappingState(NormalState); if (isActive()) workspace()->requestFocus(this); } info->setState(isShade() ? NET::Shaded : NET::States(0), NET::Shaded); info->setState(isShown(false) ? NET::States(0) : NET::Hidden, NET::Hidden); discardWindowPixmap(); updateVisibility(); updateAllowedActions(); updateWindowRules(Rules::Shade); emit shadeChanged(); } void Client::shadeHover() { setShade(ShadeHover); cancelShadeHoverTimer(); } void Client::shadeUnhover() { if (!tabGroup() || tabGroup()->current() == this || tabGroup()->current()->shadeMode() == ShadeNormal) setShade(ShadeNormal); cancelShadeHoverTimer(); } void Client::cancelShadeHoverTimer() { delete shadeHoverTimer; shadeHoverTimer = 0; } void Client::toggleShade() { // If the mode is ShadeHover or ShadeActive, cancel shade too setShade(shade_mode == ShadeNone ? ShadeNormal : ShadeNone); } void Client::updateVisibility() { if (deleting) return; if (hidden && isCurrentTab()) { info->setState(NET::Hidden, NET::Hidden); setSkipTaskbar(true); // Also hide from taskbar if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } if (isCurrentTab()) setSkipTaskbar(originalSkipTaskbar()); // Reset from 'hidden' if (isMinimized()) { info->setState(NET::Hidden, NET::Hidden); if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } info->setState(0, NET::Hidden); if (!isOnCurrentDesktop()) { if (compositing() && options->hiddenPreviews() != HiddenPreviewsNever) internalKeep(); else internalHide(); return; } if (!isOnCurrentActivity()) { if (compositing() && options->hiddenPreviews() != HiddenPreviewsNever) internalKeep(); else internalHide(); return; } internalShow(); } /** * Sets the client window's mapping state. Possible values are * WithdrawnState, IconicState, NormalState. */ void Client::exportMappingState(int s) { assert(m_client != XCB_WINDOW_NONE); assert(!deleting || s == WithdrawnState); if (s == WithdrawnState) { m_client.deleteProperty(atoms->wm_state); return; } assert(s == NormalState || s == IconicState); int32_t data[2]; data[0] = s; data[1] = XCB_NONE; m_client.changeProperty(atoms->wm_state, atoms->wm_state, 32, 2, data); } void Client::internalShow() { if (mapping_state == Mapped) return; MappingState old = mapping_state; mapping_state = Mapped; if (old == Unmapped || old == Withdrawn) map(); if (old == Kept) { m_decoInputExtent.map(); updateHiddenPreview(); } emit windowShown(this); } void Client::internalHide() { if (mapping_state == Unmapped) return; MappingState old = mapping_state; mapping_state = Unmapped; if (old == Mapped || old == Kept) unmap(); if (old == Kept) updateHiddenPreview(); addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); emit windowHidden(this); } void Client::internalKeep() { assert(compositing()); if (mapping_state == Kept) return; MappingState old = mapping_state; mapping_state = Kept; if (old == Unmapped || old == Withdrawn) map(); m_decoInputExtent.unmap(); if (isActive()) workspace()->focusToNull(); // get rid of input focus, bug #317484 updateHiddenPreview(); addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); } /** * Maps (shows) the client. Note that it is mapping state of the frame, * not necessarily the client window itself (i.e. a shaded window is here * considered mapped, even though it is in IconicState). */ void Client::map() { // XComposite invalidates backing pixmaps on unmap (minimize, different // virtual desktop, etc.). We kept the last known good pixmap around // for use in effects, but now we want to have access to the new pixmap if (compositing()) discardWindowPixmap(); m_frame.map(); if (!isShade()) { m_wrapper.map(); m_client.map(); m_decoInputExtent.map(); exportMappingState(NormalState); } else exportMappingState(IconicState); addLayerRepaint(visibleRect()); } /** * Unmaps the client. Again, this is about the frame. */ void Client::unmap() { // Here it may look like a race condition, as some other client might try to unmap // the window between these two XSelectInput() calls. However, they're supposed to // use XWithdrawWindow(), which also sends a synthetic event to the root window, // which won't be missed, so this shouldn't be a problem. The chance the real UnmapNotify // will be missed is also very minimal, so I don't think it's needed to grab the server // here. m_wrapper.selectInput(ClientWinMask); // Avoid getting UnmapNotify m_frame.unmap(); m_wrapper.unmap(); m_client.unmap(); m_decoInputExtent.unmap(); m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); exportMappingState(IconicState); } /** * XComposite doesn't keep window pixmaps of unmapped windows, which means * there wouldn't be any previews of windows that are minimized or on another * virtual desktop. Therefore rawHide() actually keeps such windows mapped. * However special care needs to be taken so that such windows don't interfere. * Therefore they're put very low in the stacking order and they have input shape * set to none, which hopefully is enough. If there's no input shape available, * then it's hoped that there will be some other desktop above it *shrug*. * Using normal shape would be better, but that'd affect other things, e.g. painting * of the actual preview. */ void Client::updateHiddenPreview() { if (hiddenPreview()) { workspace()->forceRestacking(); if (Xcb::Extensions::self()->isShapeInputAvailable()) { xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, frameId(), 0, 0, 0, NULL); } } else { workspace()->forceRestacking(); updateInputShape(); } } void Client::sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1, uint32_t data2, uint32_t data3, xcb_timestamp_t timestamp) { xcb_client_message_event_t ev; memset(&ev, 0, sizeof(ev)); ev.response_type = XCB_CLIENT_MESSAGE; ev.window = w; ev.type = a; ev.format = 32; ev.data.data32[0] = protocol; ev.data.data32[1] = timestamp; ev.data.data32[2] = data1; ev.data.data32[3] = data2; ev.data.data32[4] = data3; uint32_t eventMask = 0; if (w == rootWindow()) { eventMask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Magic! } xcb_send_event(connection(), false, w, eventMask, reinterpret_cast(&ev)); xcb_flush(connection()); } /** * Returns whether the window may be closed (have a close button) */ bool Client::isCloseable() const { return rules()->checkCloseable(m_motif.close() && !isSpecialWindow()); } /** * Closes the window by either sending a delete_window message or using XKill. */ void Client::closeWindow() { if (!isCloseable()) return; // Update user time, because the window may create a confirming dialog. updateUserTime(); if (info->supportsProtocol(NET::DeleteWindowProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->wm_delete_window); pingWindow(); } else // Client will not react on wm_delete_window. We have not choice // but destroy his connection to the XServer. killWindow(); } /** * Kills the window via XKill */ void Client::killWindow() { qCDebug(KWIN_CORE) << "Client::killWindow():" << caption(); killProcess(false); m_client.kill(); // Always kill this client at the server destroyClient(); } /** * Send a ping to the window using _NET_WM_PING if possible if it * doesn't respond within a reasonable time, it will be killed. */ void Client::pingWindow() { if (!info->supportsProtocol(NET::PingProtocol)) return; // Can't ping :( if (options->killPingTimeout() == 0) return; // Turned off if (ping_timer != NULL) return; // Pinging already ping_timer = new QTimer(this); connect(ping_timer, &QTimer::timeout, this, [this]() { if (unresponsive()) { qCDebug(KWIN_CORE) << "Final ping timeout, asking to kill:" << caption(); ping_timer->deleteLater(); ping_timer = nullptr; killProcess(true, m_pingTimestamp); return; } qCDebug(KWIN_CORE) << "First ping timeout:" << caption(); setUnresponsive(true); ping_timer->start(); } ); ping_timer->setSingleShot(true); // we'll run the timer twice, at first we'll desaturate the window // and the second time we'll show the "do you want to kill" prompt ping_timer->start(options->killPingTimeout() / 2); m_pingTimestamp = xTime(); workspace()->sendPingToWindow(window(), m_pingTimestamp); } void Client::gotPing(xcb_timestamp_t timestamp) { // Just plain compare is not good enough because of 64bit and truncating and whatnot if (NET::timestampCompare(timestamp, m_pingTimestamp) != 0) return; delete ping_timer; ping_timer = NULL; setUnresponsive(false); if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive ::kill(m_killHelperPID, SIGTERM); m_killHelperPID = 0; } } void Client::killProcess(bool ask, xcb_timestamp_t timestamp) { if (m_killHelperPID && !::kill(m_killHelperPID, 0)) // means the process is alive return; Q_ASSERT(!ask || timestamp != XCB_TIME_CURRENT_TIME); pid_t pid = info->pid(); if (pid <= 0 || clientMachine()->hostName().isEmpty()) // Needed properties missing return; qCDebug(KWIN_CORE) << "Kill process:" << pid << "(" << clientMachine()->hostName() << ")"; if (!ask) { if (!clientMachine()->isLocal()) { QStringList lst; lst << QString::fromUtf8(clientMachine()->hostName()) << QStringLiteral("kill") << QString::number(pid); QProcess::startDetached(QStringLiteral("xon"), lst); } else ::kill(pid, SIGTERM); } else { QString hostname = clientMachine()->isLocal() ? QStringLiteral("localhost") : QString::fromUtf8(clientMachine()->hostName()); QProcess::startDetached(QStringLiteral(KWIN_KILLER_BIN), QStringList() << QStringLiteral("--pid") << QString::number(unsigned(pid)) << QStringLiteral("--hostname") << hostname << QStringLiteral("--windowname") << captionNormal() << QStringLiteral("--applicationname") << QString::fromUtf8(resourceClass()) << QStringLiteral("--wid") << QString::number(window()) << QStringLiteral("--timestamp") << QString::number(timestamp), QString(), &m_killHelperPID); } } void Client::doSetSkipTaskbar() { info->setState(skipTaskbar() ? NET::SkipTaskbar : NET::States(0), NET::SkipTaskbar); } void Client::doSetSkipPager() { info->setState(skipPager() ? NET::SkipPager : NET::States(0), NET::SkipPager); } void Client::doSetDesktop(int desktop, int was_desk) { Q_UNUSED(desktop) Q_UNUSED(was_desk) updateVisibility(); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Desktop); } /** * Sets whether the client is on @p activity. * If you remove it from its last activity, then it's on all activities. * * Note: If it was on all activities and you try to remove it from one, nothing will happen; * I don't think that's an important enough use case to handle here. */ void Client::setOnActivity(const QString &activity, bool enable) { #ifdef KWIN_BUILD_ACTIVITIES if (! Activities::self()) { return; } QStringList newActivitiesList = activities(); if (newActivitiesList.contains(activity) == enable) //nothing to do return; if (enable) { QStringList allActivities = Activities::self()->all(); if (!allActivities.contains(activity)) //bogus ID return; newActivitiesList.append(activity); } else newActivitiesList.removeOne(activity); setOnActivities(newActivitiesList); #else Q_UNUSED(activity) Q_UNUSED(enable) #endif } /** * set exactly which activities this client is on */ void Client::setOnActivities(QStringList newActivitiesList) { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } QString joinedActivitiesList = newActivitiesList.join(QStringLiteral(",")); joinedActivitiesList = rules()->checkActivity(joinedActivitiesList, false); newActivitiesList = joinedActivitiesList.split(u',', QString::SkipEmptyParts); QStringList allActivities = Activities::self()->all(); auto it = newActivitiesList.begin(); while (it != newActivitiesList.end()) { if (! allActivities.contains(*it)) { it = newActivitiesList.erase(it); } else { it++; } } if (// If we got the request to be on all activities explicitly newActivitiesList.isEmpty() || joinedActivitiesList == Activities::nullUuid() || // If we got a list of activities that covers all activities (newActivitiesList.count() > 1 && newActivitiesList.count() == allActivities.count())) { activityList.clear(); const QByteArray nullUuid = Activities::nullUuid().toUtf8(); m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, nullUuid.length(), nullUuid.constData()); } else { QByteArray joined = joinedActivitiesList.toAscii(); activityList = newActivitiesList; m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, joined.length(), joined.constData()); } updateActivities(false); #else Q_UNUSED(newActivitiesList) #endif } void Client::blockActivityUpdates(bool b) { if (b) { ++m_activityUpdatesBlocked; } else { Q_ASSERT(m_activityUpdatesBlocked); --m_activityUpdatesBlocked; if (!m_activityUpdatesBlocked) updateActivities(m_blockedActivityUpdatesRequireTransients); } } /** * update after activities changed */ void Client::updateActivities(bool includeTransients) { if (m_activityUpdatesBlocked) { m_blockedActivityUpdatesRequireTransients |= includeTransients; return; } emit activitiesChanged(this); m_blockedActivityUpdatesRequireTransients = false; // reset FocusChain::self()->update(this, FocusChain::MakeFirst); updateVisibility(); updateWindowRules(Rules::Activity); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Activity); } /** * Returns the list of activities the client window is on. * if it's on all activities, the list will be empty. * Don't use this, use isOnActivity() and friends (from class Toplevel) */ QStringList Client::activities() const { if (sessionActivityOverride) { return QStringList(); } return activityList; } /** * if @p on is true, sets on all activities. * if it's false, sets it to only be on the current activity */ void Client::setOnAllActivities(bool on) { #ifdef KWIN_BUILD_ACTIVITIES if (on == isOnAllActivities()) return; if (on) { setOnActivities(QStringList()); } else { setOnActivity(Activities::self()->current(), true); } #else Q_UNUSED(on) #endif } /** * Performs the actual focusing of the window using XSetInputFocus and WM_TAKE_FOCUS */ void Client::takeFocus() { if (rules()->checkAcceptFocus(info->input())) m_client.focus(); else demandAttention(false); // window cannot take input, at least withdraw urgency if (info->supportsProtocol(NET::TakeFocusProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->wm_take_focus, 0, 0, 0, XCB_CURRENT_TIME); } workspace()->setShouldGetFocus(this); bool breakShowingDesktop = !keepAbove(); if (breakShowingDesktop) { foreach (const Client *c, group()->members()) { if (c->isDesktop()) { breakShowingDesktop = false; break; } } } if (breakShowingDesktop) workspace()->setShowingDesktop(false); } /** * Returns whether the window provides context help or not. If it does, * you should show a help menu item or a help button like '?' and call * contextHelp() if this is invoked. * * \sa contextHelp() */ bool Client::providesContextHelp() const { return info->supportsProtocol(NET::ContextHelpProtocol); } /** * Invokes context help on the window. Only works if the window * actually provides context help. * * \sa providesContextHelp() */ void Client::showContextHelp() { if (info->supportsProtocol(NET::ContextHelpProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_context_help); } } /** * Fetches the window's caption (WM_NAME property). It will be * stored in the client's caption(). */ void Client::fetchName() { setCaption(readName()); } static inline QString readNameProperty(xcb_window_t w, xcb_atom_t atom) { const auto cookie = xcb_icccm_get_text_property_unchecked(connection(), w, atom); xcb_icccm_get_text_property_reply_t reply; if (xcb_icccm_get_wm_name_reply(connection(), cookie, &reply, nullptr)) { QString retVal; if (reply.encoding == atoms->utf8_string) { retVal = QString::fromUtf8(QByteArray(reply.name, reply.name_len)); } else if (reply.encoding == XCB_ATOM_STRING) { retVal = QString::fromLocal8Bit(QByteArray(reply.name, reply.name_len)); } xcb_icccm_get_text_property_reply_wipe(&reply); return retVal.simplified(); } return QString(); } QString Client::readName() const { if (info->name() && info->name()[0] != '\0') return QString::fromUtf8(info->name()).simplified(); else { return readNameProperty(window(), XCB_ATOM_WM_NAME); } } // The list is taken from http://www.unicode.org/reports/tr9/ (#154840) static const QChar LRM(0x200E); void Client::setCaption(const QString& _s, bool force) { if (!force && _s == cap_normal) return; QString s(_s); for (int i = 0; i < s.length(); ++i) if (!s[i].isPrint()) s[i] = QChar(u' '); const bool changed = (s != cap_normal); cap_normal = s; if (!force && !changed) { emit captionChanged(); return; } bool reset_name = force; bool was_suffix = (!cap_suffix.isEmpty()); cap_suffix.clear(); QString machine_suffix; if (!options->condensedTitle()) { // machine doesn't qualify for "clean" if (clientMachine()->hostName() != ClientMachine::localhost() && !clientMachine()->isLocal()) machine_suffix = QLatin1String(" <@") + QString::fromUtf8(clientMachine()->hostName()) + QLatin1Char('>') + LRM; } QString shortcut_suffix = shortcutCaptionSuffix(); cap_suffix = machine_suffix + shortcut_suffix; if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) { int i = 2; do { cap_suffix = machine_suffix + QLatin1String(" <") + QString::number(i) + QLatin1Char('>') + LRM; i++; } while (findClientWithSameCaption()); info->setVisibleName(caption().toUtf8().constData()); reset_name = false; } if ((was_suffix && cap_suffix.isEmpty()) || reset_name) { // If it was new window, it may have old value still set, if the window is reused info->setVisibleName(""); info->setVisibleIconName(""); } else if (!cap_suffix.isEmpty() && !cap_iconic.isEmpty()) // Keep the same suffix in iconic name if it's set info->setVisibleIconName(QString(cap_iconic + cap_suffix).toUtf8().constData()); emit captionChanged(); } void Client::updateCaption() { setCaption(cap_normal, true); } void Client::fetchIconicName() { QString s; if (info->iconName() && info->iconName()[0] != '\0') s = QString::fromUtf8(info->iconName()); else s = readNameProperty(window(), XCB_ATOM_WM_ICON_NAME); if (s != cap_iconic) { bool was_set = !cap_iconic.isEmpty(); cap_iconic = s; if (!cap_suffix.isEmpty()) { if (!cap_iconic.isEmpty()) // Keep the same suffix in iconic name if it's set info->setVisibleIconName(QString(s + cap_suffix).toUtf8().constData()); else if (was_set) info->setVisibleIconName(""); } } } bool Client::tabTo(Client *other, bool behind, bool activate) { Q_ASSERT(other && other != this); if (tab_group && tab_group == other->tabGroup()) { // special case: move inside group tab_group->move(this, other, behind); return true; } GeometryUpdatesBlocker blocker(this); const bool wasBlocking = signalsBlocked(); blockSignals(true); // prevent client emitting "retabbed to nowhere" cause it's about to be entabbed the next moment untab(); blockSignals(wasBlocking); TabGroup *newGroup = other->tabGroup() ? other->tabGroup() : new TabGroup(other); if (!newGroup->add(this, other, behind, activate)) { if (newGroup->count() < 2) { // adding "c" to "to" failed for whatever reason newGroup->remove(other); delete newGroup; } return false; } return true; } bool Client::untab(const QRect &toGeometry, bool clientRemoved) { TabGroup *group = tab_group; if (group && group->remove(this)) { // remove sets the tabgroup to "0", therefore the pointer is cached if (group->isEmpty()) { delete group; } if (clientRemoved) return true; // there's been a broadcast signal that this client is now removed - don't touch it setClientShown(!(isMinimized() || isShade())); bool keepSize = toGeometry.size() == size(); bool changedSize = false; if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { changedSize = true; setQuickTileMode(QuickTileFlag::None); // if we leave a quicktiled group, assume that the user wants to untile } if (toGeometry.isValid()) { if (maximizeMode() != MaximizeRestore) { changedSize = true; maximize(MaximizeRestore); // explicitly calling for a geometry -> unmaximize } if (keepSize && changedSize) { geom_restore = geometry(); // checkWorkspacePosition() invokes it QPoint cpoint = Cursor::pos(); QPoint point = cpoint; point.setX((point.x() - toGeometry.x()) * geom_restore.width() / toGeometry.width()); point.setY((point.y() - toGeometry.y()) * geom_restore.height() / toGeometry.height()); geom_restore.moveTo(cpoint-point); } else { geom_restore = toGeometry; // checkWorkspacePosition() invokes it } setGeometry(geom_restore); checkWorkspacePosition(); } return true; } return false; } void Client::setTabGroup(TabGroup *group) { tab_group = group; if (group) { unsigned long data[] = {qHash(group)}; //->id(); m_client.changeProperty(atoms->kde_net_wm_tab_group, XCB_ATOM_CARDINAL, 32, 1, data); } else m_client.deleteProperty(atoms->kde_net_wm_tab_group); emit tabGroupChanged(); } bool Client::isCurrentTab() const { return !tab_group || tab_group->current() == this; } void Client::syncTabGroupFor(QString property, bool fromThisClient) { if (tab_group) tab_group->sync(property.toAscii().data(), fromThisClient ? this : tab_group->current()); } void Client::setClientShown(bool shown) { if (deleting) return; // Don't change shown status if this client is being deleted if (shown != hidden) return; // nothing to change hidden = !shown; if (options->isInactiveTabsSkipTaskbar()) setSkipTaskbar(hidden); // TODO: Causes reshuffle of the taskbar if (shown) { map(); takeFocus(); autoRaise(); FocusChain::self()->update(this, FocusChain::MakeFirst); } else { unmap(); // Don't move tabs to the end of the list when another tab get's activated if (isCurrentTab()) FocusChain::self()->update(this, FocusChain::MakeLast); addWorkspaceRepaint(visibleRect()); } } void Client::getMotifHints() { const bool wasClosable = m_motif.close(); const bool wasNoBorder = m_motif.noBorder(); if (m_managed) // only on property change, initial read is prefetched m_motif.fetch(); m_motif.read(); if (m_motif.hasDecoration() && m_motif.noBorder() != wasNoBorder) { // If we just got a hint telling us to hide decorations, we do so. if (m_motif.noBorder()) noborder = rules()->checkNoBorder(true); // If the Motif hint is now telling us to show decorations, we only do so if the app didn't // instruct us to hide decorations in some other way, though. else if (!app_noborder) noborder = rules()->checkNoBorder(false); } // mminimize; - Ignore, bogus - E.g. shading or sending to another desktop is "minimizing" too // mmaximize; - Ignore, bogus - Maximizing is basically just resizing const bool closabilityChanged = wasClosable != m_motif.close(); if (isManaged()) updateDecoration(true); // Check if noborder state has changed if (closabilityChanged) { emit closeableChanged(isCloseable()); } } void Client::getIcons() { // First read icons from the window itself const QString themedIconName = iconFromDesktopFile(); if (!themedIconName.isEmpty()) { setIcon(QIcon::fromTheme(themedIconName)); return; } QIcon icon; auto readIcon = [this, &icon](int size, bool scale = true) { const QPixmap pix = KWindowSystem::icon(window(), size, size, scale, KWindowSystem::NETWM | KWindowSystem::WMHints, info); if (!pix.isNull()) { icon.addPixmap(pix); } }; readIcon(16); readIcon(32); readIcon(48, false); readIcon(64, false); readIcon(128, false); if (icon.isNull()) { // Then try window group icon = group()->icon(); } if (icon.isNull() && isTransient()) { // Then mainclients auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd() && icon.isNull(); ++it) { if (!(*it)->icon().isNull()) { icon = (*it)->icon(); break; } } } if (icon.isNull()) { // And if nothing else, load icon from classhint or xapp icon icon.addPixmap(KWindowSystem::icon(window(), 32, 32, true, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 16, 16, true, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 64, 64, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 128, 128, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); } setIcon(icon); } void Client::getSyncCounter() { // TODO: make sync working on XWayland static const bool isX11 = kwinApp()->operationMode() == Application::OperationModeX11; if (!Xcb::Extensions::self()->isSyncAvailable() || !isX11) return; Xcb::Property syncProp(false, window(), atoms->net_wm_sync_request_counter, XCB_ATOM_CARDINAL, 0, 1); const xcb_sync_counter_t counter = syncProp.value(XCB_NONE); if (counter != XCB_NONE) { syncRequest.counter = counter; syncRequest.value.hi = 0; syncRequest.value.lo = 0; auto *c = connection(); xcb_sync_set_counter(c, syncRequest.counter, syncRequest.value); if (syncRequest.alarm == XCB_NONE) { const uint32_t mask = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE_TYPE | XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_EVENTS; const uint32_t values[] = { syncRequest.counter, XCB_SYNC_VALUETYPE_RELATIVE, XCB_SYNC_TESTTYPE_POSITIVE_TRANSITION, 1 }; syncRequest.alarm = xcb_generate_id(c); auto cookie = xcb_sync_create_alarm_checked(c, syncRequest.alarm, mask, values); ScopedCPointer error(xcb_request_check(c, cookie)); if (!error.isNull()) { syncRequest.alarm = XCB_NONE; } else { xcb_sync_change_alarm_value_list_t value; memset(&value, 0, sizeof(value)); value.value.hi = 0; value.value.lo = 1; value.delta.hi = 0; value.delta.lo = 1; xcb_sync_change_alarm_aux(c, syncRequest.alarm, XCB_SYNC_CA_DELTA | XCB_SYNC_CA_VALUE, &value); } } } } /** * Send the client a _NET_SYNC_REQUEST */ void Client::sendSyncRequest() { if (syncRequest.counter == XCB_NONE || syncRequest.isPending) return; // do NOT, NEVER send a sync request when there's one on the stack. the clients will just stop respoding. FOREVER! ... if (!syncRequest.failsafeTimeout) { syncRequest.failsafeTimeout = new QTimer(this); connect(syncRequest.failsafeTimeout, &QTimer::timeout, this, [this]() { // client does not respond to XSYNC requests in reasonable time, remove support if (!ready_for_painting) { // failed on initial pre-show request setReadyForPainting(); setupWindowManagementInterface(); return; } // failed during resize syncRequest.isPending = false; syncRequest.counter = syncRequest.alarm = XCB_NONE; delete syncRequest.timeout; delete syncRequest.failsafeTimeout; syncRequest.timeout = syncRequest.failsafeTimeout = nullptr; syncRequest.lastTimestamp = XCB_CURRENT_TIME; } ); syncRequest.failsafeTimeout->setSingleShot(true); } // if there's no response within 10 seconds, sth. went wrong and we remove XSYNC support from this client. // see events.cpp Client::syncEvent() syncRequest.failsafeTimeout->start(ready_for_painting ? 10000 : 1000); // We increment before the notify so that after the notify // syncCounterSerial will equal the value we are expecting // in the acknowledgement const uint32_t oldLo = syncRequest.value.lo; syncRequest.value.lo++;; if (oldLo > syncRequest.value.lo) { syncRequest.value.hi++; } if (syncRequest.lastTimestamp >= xTime()) { updateXTime(); } // Send the message to client sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_sync_request, syncRequest.value.lo, syncRequest.value.hi); syncRequest.isPending = true; syncRequest.lastTimestamp = xTime(); } bool Client::wantsInput() const { return rules()->checkAcceptFocus(acceptsFocus() || info->supportsProtocol(NET::TakeFocusProtocol)); } bool Client::acceptsFocus() const { return info->input(); } void Client::setBlockingCompositing(bool block) { const bool usedToBlock = blocks_compositing; blocks_compositing = rules()->checkBlockCompositing(block && options->windowsBlockCompositing()); if (usedToBlock != blocks_compositing) { emit blockingCompositingChanged(blocks_compositing ? this : 0); } } void Client::updateAllowedActions(bool force) { if (!isManaged() && !force) return; NET::Actions old_allowed_actions = NET::Actions(allowed_actions); allowed_actions = 0; if (isMovable()) allowed_actions |= NET::ActionMove; if (isResizable()) allowed_actions |= NET::ActionResize; if (isMinimizable()) allowed_actions |= NET::ActionMinimize; if (isShadeable()) allowed_actions |= NET::ActionShade; // Sticky state not supported if (isMaximizable()) allowed_actions |= NET::ActionMax; if (userCanSetFullScreen()) allowed_actions |= NET::ActionFullScreen; allowed_actions |= NET::ActionChangeDesktop; // Always (Pagers shouldn't show Docks etc.) if (isCloseable()) allowed_actions |= NET::ActionClose; if (old_allowed_actions == allowed_actions) return; // TODO: This could be delayed and compressed - It's only for pagers etc. anyway info->setAllowedActions(allowed_actions); // ONLY if relevant features have changed (and the window didn't just get/loose moveresize for maximization state changes) const NET::Actions relevant = ~(NET::ActionMove|NET::ActionResize); if ((allowed_actions & relevant) != (old_allowed_actions & relevant)) { if ((allowed_actions & NET::ActionMinimize) != (old_allowed_actions & NET::ActionMinimize)) { emit minimizeableChanged(allowed_actions & NET::ActionMinimize); } if ((allowed_actions & NET::ActionShade) != (old_allowed_actions & NET::ActionShade)) { emit shadeableChanged(allowed_actions & NET::ActionShade); } if ((allowed_actions & NET::ActionMax) != (old_allowed_actions & NET::ActionMax)) { emit maximizeableChanged(allowed_actions & NET::ActionMax); } } } void Client::debug(QDebug& stream) const { print(stream); } Xcb::StringProperty Client::fetchActivities() const { #ifdef KWIN_BUILD_ACTIVITIES return Xcb::StringProperty(window(), atoms->activities); #else return Xcb::StringProperty(); #endif } void Client::readActivities(Xcb::StringProperty &property) { #ifdef KWIN_BUILD_ACTIVITIES QStringList newActivitiesList; QString prop = QString::fromUtf8(property); activitiesDefined = !prop.isEmpty(); if (prop == Activities::nullUuid()) { //copied from setOnAllActivities to avoid a redundant XChangeProperty. if (!activityList.isEmpty()) { activityList.clear(); updateActivities(true); } return; } if (prop.isEmpty()) { //note: this makes it *act* like it's on all activities but doesn't set the property to 'ALL' if (!activityList.isEmpty()) { activityList.clear(); updateActivities(true); } return; } newActivitiesList = prop.split(u','); if (newActivitiesList == activityList) return; //expected change, it's ok. //otherwise, somebody else changed it. we need to validate before reacting. //if the activities are not synced, and there are existing clients with //activities specified, somebody has restarted kwin. we can not validate //activities in this case. we need to trust the old values. if (Activities::self() && Activities::self()->serviceStatus() != KActivities::Consumer::Unknown) { QStringList allActivities = Activities::self()->all(); if (allActivities.isEmpty()) { qCDebug(KWIN_CORE) << "no activities!?!?"; //don't touch anything, there's probably something bad going on and we don't wanna make it worse return; } for (int i = 0; i < newActivitiesList.size(); ++i) { if (! allActivities.contains(newActivitiesList.at(i))) { qCDebug(KWIN_CORE) << "invalid:" << newActivitiesList.at(i); newActivitiesList.removeAt(i--); } } } setOnActivities(newActivitiesList); #else Q_UNUSED(property) #endif } void Client::checkActivities() { #ifdef KWIN_BUILD_ACTIVITIES Xcb::StringProperty property = fetchActivities(); readActivities(property); #endif } void Client::setSessionActivityOverride(bool needed) { sessionActivityOverride = needed; updateActivities(false); } QRect Client::decorationRect() const { return QRect(0, 0, width(), height()); } Xcb::Property Client::fetchFirstInTabBox() const { return Xcb::Property(false, m_client, atoms->kde_first_in_window_list, atoms->kde_first_in_window_list, 0, 1); } void Client::readFirstInTabBox(Xcb::Property &property) { setFirstInTabBox(property.toBool(32, atoms->kde_first_in_window_list)); } void Client::updateFirstInTabBox() { // TODO: move into KWindowInfo Xcb::Property property = fetchFirstInTabBox(); readFirstInTabBox(property); } Xcb::StringProperty Client::fetchColorScheme() const { return Xcb::StringProperty(m_client, atoms->kde_color_sheme); } void Client::readColorScheme(Xcb::StringProperty &property) { AbstractClient::updateColorScheme(rules()->checkDecoColor(QString::fromUtf8(property))); } void Client::updateColorScheme() { Xcb::StringProperty property = fetchColorScheme(); readColorScheme(property); } bool Client::isClient() const { return true; } NET::WindowType Client::windowType(bool direct, int supportedTypes) const { // TODO: does it make sense to cache the returned window type for SUPPORTED_MANAGED_WINDOW_TYPES_MASK? if (supportedTypes == 0) { supportedTypes = SUPPORTED_MANAGED_WINDOW_TYPES_MASK; } NET::WindowType wt = info->windowType(NET::WindowTypes(supportedTypes)); if (direct) { return wt; } NET::WindowType wt2 = rules()->checkType(wt); if (wt != wt2) { wt = wt2; info->setWindowType(wt); // force hint change } // hacks here if (wt == NET::Unknown) // this is more or less suggested in NETWM spec wt = isTransient() ? NET::Dialog : NET::Normal; return wt; } void Client::cancelFocusOutTimer() { if (m_focusOutTimer) { m_focusOutTimer->stop(); } } xcb_window_t Client::frameId() const { return m_frame; } Xcb::Property Client::fetchShowOnScreenEdge() const { return Xcb::Property(false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1); } void Client::readShowOnScreenEdge(Xcb::Property &property) { //value comes in two parts, edge in the lower byte //then the type in the upper byte // 0 = autohide // 1 = raise in front on activate const uint32_t value = property.value(ElectricNone); ElectricBorder border = ElectricNone; switch (value & 0xFF) { case 0: border = ElectricTop; break; case 1: border = ElectricRight; break; case 2: border = ElectricBottom; break; case 3: border = ElectricLeft; break; } if (border != ElectricNone) { disconnect(m_edgeRemoveConnection); disconnect(m_edgeGeometryTrackingConnection); bool successfullyHidden = false; if (((value >> 8) & 0xFF) == 1) { setKeepBelow(true); successfullyHidden = keepBelow(); //request could have failed due to user kwin rules m_edgeRemoveConnection = connect(this, &AbstractClient::keepBelowChanged, this, [this](){ if (!keepBelow()) { ScreenEdges::self()->reserve(this, ElectricNone); } }); } else { hideClient(true); successfullyHidden = isHiddenInternal(); m_edgeGeometryTrackingConnection = connect(this, &Client::geometryChanged, this, [this, border](){ hideClient(true); ScreenEdges::self()->reserve(this, border); }); } if (successfullyHidden) { ScreenEdges::self()->reserve(this, border); } else { ScreenEdges::self()->reserve(this, ElectricNone); } } else if (!property.isNull() && property->type != XCB_ATOM_NONE) { // property value is incorrect, delete the property // so that the client knows that it is not hidden xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show); } else { // restore // TODO: add proper unreserve //this will call showOnScreenEdge to reset the state disconnect(m_edgeGeometryTrackingConnection); ScreenEdges::self()->reserve(this, ElectricNone); } } void Client::updateShowOnScreenEdge() { Xcb::Property property = fetchShowOnScreenEdge(); readShowOnScreenEdge(property); } void Client::showOnScreenEdge() { disconnect(m_edgeRemoveConnection); hideClient(false); setKeepBelow(false); xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show); } void Client::addDamage(const QRegion &damage) { if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead if (syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now setReadyForPainting(); setupWindowManagementInterface(); } } repaints_region += damage; Toplevel::addDamage(damage); } bool Client::belongsToSameApplication(const AbstractClient *other, bool active_hack) const { const Client *c2 = dynamic_cast(other); if (!c2) { return false; } return Client::belongToSameApplication(this, c2, active_hack); } void Client::updateTabGroupStates(TabGroup::States states) { if (auto t = tabGroup()) { t->updateStates(this, states); } } QSize Client::resizeIncrements() const { return m_geometryHints.resizeIncrements(); } Xcb::StringProperty Client::fetchApplicationMenuServiceName() const { return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_service_name); } void Client::readApplicationMenuServiceName(Xcb::StringProperty &property) { updateApplicationMenuServiceName(QString::fromUtf8(property)); } void Client::checkApplicationMenuServiceName() { Xcb::StringProperty property = fetchApplicationMenuServiceName(); readApplicationMenuServiceName(property); } Xcb::StringProperty Client::fetchApplicationMenuObjectPath() const { return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_object_path); } void Client::readApplicationMenuObjectPath(Xcb::StringProperty &property) { updateApplicationMenuObjectPath(QString::fromUtf8(property)); } void Client::checkApplicationMenuObjectPath() { Xcb::StringProperty property = fetchApplicationMenuObjectPath(); readApplicationMenuObjectPath(property); } +void Client::handleSync() +{ + setReadyForPainting(); + setupWindowManagementInterface(); + syncRequest.isPending = false; + if (syncRequest.failsafeTimeout) + syncRequest.failsafeTimeout->stop(); + if (isResize()) { + if (syncRequest.timeout) + syncRequest.timeout->stop(); + performMoveResize(); + } else // setReadyForPainting does as well, but there's a small chance for resize syncs after the resize ended + addRepaintFull(); +} + } // namespace diff --git a/client.h b/client.h index 202254583..1af55b79f 100644 --- a/client.h +++ b/client.h @@ -1,744 +1,747 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_CLIENT_H #define KWIN_CLIENT_H // kwin #include "options.h" #include "rules.h" #include "tabgroup.h" #include "abstract_client.h" #include "xcbutils.h" // Qt #include #include #include #include #include // X #include // TODO: Cleanup the order of things in this .h file class QTimer; class KStartupInfoData; class KStartupInfoId; -struct xcb_sync_alarm_notify_event_t; - namespace KWin { /** * @brief Defines Predicates on how to search for a Client. * * Used by Workspace::findClient. */ enum class Predicate { WindowMatch, WrapperIdMatch, FrameIdMatch, InputIdMatch }; class KWIN_EXPORT Client : public AbstractClient { Q_OBJECT /** * By how much the window wishes to grow/shrink at least. Usually QSize(1,1). * MAY BE DISOBEYED BY THE WM! It's only for information, do NOT rely on it at all. * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. */ Q_PROPERTY(QSize basicUnit READ basicUnit) /** * The "Window Tabs" Group this Client belongs to. **/ Q_PROPERTY(KWin::TabGroup* tabGroup READ tabGroup NOTIFY tabGroupChanged SCRIPTABLE false) /** * A client can block compositing. That is while the Client is alive and the state is set, * Compositing is suspended and is resumed when there are no Clients blocking compositing any * more. * * This is actually set by a window property, unfortunately not used by the target application * group. For convenience it's exported as a property to the scripts. * * Use with care! **/ Q_PROPERTY(bool blocksCompositing READ isBlockingCompositing WRITE setBlockingCompositing NOTIFY blockingCompositingChanged) /** * Whether the Client uses client side window decorations. * Only GTK+ are detected. **/ Q_PROPERTY(bool clientSideDecorated READ isClientSideDecorated NOTIFY clientSideDecoratedChanged) public: explicit Client(); xcb_window_t wrapperId() const; xcb_window_t inputId() const { return m_decoInputExtent; } virtual xcb_window_t frameId() const override; bool isTransient() const override; bool groupTransient() const; bool wasOriginallyGroupTransient() const; QList mainClients() const override; // Call once before loop , is not indirect bool hasTransient(const AbstractClient* c, bool indirect) const override; void checkTransient(xcb_window_t w); AbstractClient* findModal(bool allow_itself = false) override; const Group* group() const; Group* group(); void checkGroup(Group* gr = NULL, bool force = false); void changeClientLeaderGroup(Group* gr); void updateWindowRules(Rules::Types selection) override; void updateFullscreenMonitors(NETFullscreenMonitors topology); bool hasNETSupport() const; QSize minSize() const override; QSize maxSize() const override; QSize basicUnit() const; virtual QSize clientSize() const; QPoint inputPos() const { return input_offset; } // Inside of geometry() bool windowEvent(xcb_generic_event_t *e); - void syncEvent(xcb_sync_alarm_notify_event_t* e); NET::WindowType windowType(bool direct = false, int supported_types = 0) const; bool manage(xcb_window_t w, bool isMapped); void releaseWindow(bool on_shutdown = false); void destroyClient(); virtual QStringList activities() const; void setOnActivity(const QString &activity, bool enable); void setOnAllActivities(bool set) override; void setOnActivities(QStringList newActivitiesList) override; void updateActivities(bool includeTransients); void blockActivityUpdates(bool b = true) override; /// Is not minimized and not hidden. I.e. normally visible on some virtual desktop. bool isShown(bool shaded_is_shown) const override; bool isHiddenInternal() const override; // For compositing ShadeMode shadeMode() const override; // Prefer isShade() void setShade(ShadeMode mode) override; bool isShadeable() const override; bool isMaximizable() const override; QRect geometryRestore() const override; MaximizeMode maximizeMode() const override; bool isMinimizable() const override; QRect iconGeometry() const override; void setFullScreen(bool set, bool user = true) override; bool isFullScreen() const override; bool isFullScreenable() const override; bool isFullScreenable(bool fullscreen_hack) const; bool userCanSetFullScreen() const override; QRect geometryFSRestore() const { return geom_fs_restore; // Only for session saving } int fullScreenMode() const { return fullscreen_mode; // only for session saving } bool noBorder() const override; void setNoBorder(bool set) override; bool userCanSetNoBorder() const override; void checkNoBorder(); int sessionStackingOrder() const; // Auxiliary functions, depend on the windowType bool wantsInput() const override; bool isResizable() const override; bool isMovable() const override; bool isMovableAcrossScreens() const override; bool isCloseable() const override; ///< May be closed by the user (May have a close button) void takeFocus() override; void updateDecoration(bool check_workspace_pos, bool force = false) override; void updateShape(); using AbstractClient::setGeometry; void setGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) override; /// plainResize() simply resizes void plainResize(int w, int h, ForceGeometry_t force = NormalGeometrySet); void plainResize(const QSize& s, ForceGeometry_t force = NormalGeometrySet); /// resizeWithChecks() resizes according to gravity, and checks workarea position using AbstractClient::resizeWithChecks; void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) override; void resizeWithChecks(int w, int h, xcb_gravity_t gravity, ForceGeometry_t force = NormalGeometrySet); void resizeWithChecks(const QSize& s, xcb_gravity_t gravity, ForceGeometry_t force = NormalGeometrySet); QSize sizeForClientSize(const QSize&, Sizemode mode = SizemodeAny, bool noframe = false) const override; bool providesContextHelp() const override; Options::WindowOperation mouseButtonToWindowOperation(Qt::MouseButtons button); bool performMouseCommand(Options::MouseCommand, const QPoint& globalPos) override; QRect adjustedClientArea(const QRect& desktop, const QRect& area) const; xcb_colormap_t colormap() const; /// Updates visibility depending on being shaded, virtual desktop, etc. void updateVisibility(); /// Hides a client - Basically like minimize, but without effects, it's simply hidden void hideClient(bool hide) override; bool hiddenPreview() const; ///< Window is mapped in order to get a window pixmap virtual bool setupCompositing(); void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release) override; void setBlockingCompositing(bool block); inline bool isBlockingCompositing() { return blocks_compositing; } QString captionNormal() const override { return cap_normal; } QString captionSuffix() const override { return cap_suffix; } using AbstractClient::keyPressEvent; void keyPressEvent(uint key_code, xcb_timestamp_t time); // FRAME ?? void updateMouseGrab() override; xcb_window_t moveResizeGrabWindow() const; const QPoint calculateGravitation(bool invert, int gravity = 0) const; // FRAME public? void NETMoveResize(int x_root, int y_root, NET::Direction direction); void NETMoveResizeWindow(int flags, int x, int y, int width, int height); void restackWindow(xcb_window_t above, int detail, NET::RequestSource source, xcb_timestamp_t timestamp, bool send_event = false); void gotPing(xcb_timestamp_t timestamp); void updateUserTime(xcb_timestamp_t time = XCB_TIME_CURRENT_TIME); xcb_timestamp_t userTime() const override; bool hasUserTimeSupport() const; /// Does 'delete c;' static void deleteClient(Client* c); static bool belongToSameApplication(const Client* c1, const Client* c2, bool active_hack = false); static bool sameAppWindowRoleMatch(const Client* c1, const Client* c2, bool active_hack); void killWindow() override; void toggleShade(); void showContextHelp() override; void cancelShadeHoverTimer(); void checkActiveModal(); StrutRect strutRect(StrutArea area) const; StrutRects strutRects() const; bool hasStrut() const override; // Tabbing functions TabGroup* tabGroup() const override; // Returns a pointer to client_group Q_INVOKABLE inline bool tabBefore(Client *other, bool activate) { return tabTo(other, false, activate); } Q_INVOKABLE inline bool tabBehind(Client *other, bool activate) { return tabTo(other, true, activate); } /** * Syncs the *dynamic* @param property @param fromThisClient or the @link currentTab() to * all members of the @link tabGroup() (if there is one) * * eg. if you call: * client->setProperty("kwin_tiling_floats", true); * client->syncTabGroupFor("kwin_tiling_floats", true) * all clients in this tabGroup will have ::property("kwin_tiling_floats").toBool() == true * * WARNING: non dynamic properties are ignored - you're not supposed to alter/update such explicitly */ Q_INVOKABLE void syncTabGroupFor(QString property, bool fromThisClient = false); Q_INVOKABLE bool untab(const QRect &toGeometry = QRect(), bool clientRemoved = false) override; /** * Set tab group - this is to be invoked by TabGroup::add/remove(client) and NO ONE ELSE */ void setTabGroup(TabGroup* group); /* * If shown is true the client is mapped and raised, if false * the client is unmapped and hidden, this function is called * when the tabbing group of the client switches its visible * client. */ void setClientShown(bool shown); /* * When a click is done in the decoration and it calls the group * to change the visible client it starts to move-resize the new * client, this function stops it. */ bool isCurrentTab() const override; /** * Whether or not the window has a strut that expands through the invisible area of * an xinerama setup where the monitors are not the same resolution. */ bool hasOffscreenXineramaStrut() const; // Decorations <-> Effects QRect decorationRect() const; QRect transparentRect() const; bool isClientSideDecorated() const; bool wantsShadowToBeRendered() const override; void layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const override; Xcb::Property fetchFirstInTabBox() const; void readFirstInTabBox(Xcb::Property &property); void updateFirstInTabBox(); Xcb::StringProperty fetchColorScheme() const; void readColorScheme(Xcb::StringProperty &property); void updateColorScheme() override; //sets whether the client should be faked as being on all activities (and be shown during session save) void setSessionActivityOverride(bool needed); virtual bool isClient() const; template void print(T &stream) const; void cancelFocusOutTimer(); /** * Restores the Client after it had been hidden due to show on screen edge functionality. * In addition the property gets deleted so that the Client knows that it is visible again. **/ void showOnScreenEdge() override; Xcb::StringProperty fetchApplicationMenuServiceName() const; void readApplicationMenuServiceName(Xcb::StringProperty &property); void checkApplicationMenuServiceName(); Xcb::StringProperty fetchApplicationMenuObjectPath() const; void readApplicationMenuObjectPath(Xcb::StringProperty &property); void checkApplicationMenuObjectPath(); + struct SyncRequest { + xcb_sync_counter_t counter; + xcb_sync_int64_t value; + xcb_sync_alarm_t alarm; + xcb_timestamp_t lastTimestamp; + QTimer *timeout, *failsafeTimeout; + bool isPending; + }; + const SyncRequest &getSyncRequest() const { + return syncRequest; + } + void handleSync(); + static void cleanupX11(); public Q_SLOTS: void closeWindow() override; void updateCaption() override; private Q_SLOTS: void shadeHover(); void shadeUnhover(); private: // Use Workspace::createClient() virtual ~Client(); ///< Use destroyClient() or releaseWindow() // Handlers for X11 events bool mapRequestEvent(xcb_map_request_event_t *e); void unmapNotifyEvent(xcb_unmap_notify_event_t *e); void destroyNotifyEvent(xcb_destroy_notify_event_t *e); void configureRequestEvent(xcb_configure_request_event_t *e); virtual void propertyNotifyEvent(xcb_property_notify_event_t *e) override; void clientMessageEvent(xcb_client_message_event_t *e) override; void enterNotifyEvent(xcb_enter_notify_event_t *e); void leaveNotifyEvent(xcb_leave_notify_event_t *e); void focusInEvent(xcb_focus_in_event_t *e); void focusOutEvent(xcb_focus_out_event_t *e); virtual void damageNotifyEvent(); bool buttonPressEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root, xcb_timestamp_t time = XCB_CURRENT_TIME); bool buttonReleaseEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root); bool motionNotifyEvent(xcb_window_t w, int state, int x, int y, int x_root, int y_root); Client* findAutogroupCandidate() const; protected: virtual void debug(QDebug& stream) const; void addDamage(const QRegion &damage) override; bool belongsToSameApplication(const AbstractClient *other, bool active_hack) const override; void doSetActive() override; void doSetKeepAbove() override; void doSetKeepBelow() override; void doSetDesktop(int desktop, int was_desk) override; void doMinimize() override; void doSetSkipPager() override; void doSetSkipTaskbar() override; bool belongsToDesktop() const override; bool isActiveFullScreen() const override; void setGeometryRestore(const QRect &geo) override; void updateTabGroupStates(TabGroup::States states) override; void doMove(int x, int y) override; bool doStartMoveResize() override; void doPerformMoveResize() override; bool isWaitingForMoveResizeSync() const override; void doResizeSync() override; QSize resizeIncrements() const override; bool acceptsFocus() const override; //Signals for the scripting interface //Signals make an excellent way for communication //in between objects as compared to simple function //calls Q_SIGNALS: void clientManaging(KWin::Client*); void clientFullScreenSet(KWin::Client*, bool, bool); /** * Emitted whenever the Client's TabGroup changed. That is whenever the Client is moved to * another group, but not when a Client gets added or removed to the Client's ClientGroup. **/ void tabGroupChanged(); /** * Emitted whenever the Client want to show it menu */ void showRequest(); /** * Emitted whenever the Client's menu is closed */ void menuHidden(); /** * Emitted whenever the Client's menu is available **/ void appMenuAvailable(); /** * Emitted whenever the Client's menu is unavailable */ void appMenuUnavailable(); /** * Emitted whenever the Client's block compositing state changes. **/ void blockingCompositingChanged(KWin::Client *client); void clientSideDecoratedChanged(); private: void exportMappingState(int s); // ICCCM 4.1.3.1, 4.1.4, NETWM 2.5.1 bool isManaged() const; ///< Returns false if this client is not yet managed void updateAllowedActions(bool force = false); QRect fullscreenMonitorsArea(NETFullscreenMonitors topology) const; void changeMaximize(bool horizontal, bool vertical, bool adjust) override; int checkFullScreenHack(const QRect& geom) const; // 0 - None, 1 - One xinerama screen, 2 - Full area void updateFullScreenHack(const QRect& geom); void getWmNormalHints(); void getMotifHints(); void getIcons(); void fetchName(); void fetchIconicName(); QString readName() const; void setCaption(const QString& s, bool force = false); bool hasTransientInternal(const Client* c, bool indirect, ConstClientList& set) const; void setShortcutInternal() override; void configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool); NETExtendedStrut strut() const; int checkShadeGeometry(int w, int h); void getSyncCounter(); void sendSyncRequest(); void leaveMoveResize() override; void positionGeometryTip() override; void grabButton(int mod); void ungrabButton(int mod); void resizeDecoration(); void createDecoration(const QRect &oldgeom); void pingWindow(); void killProcess(bool ask, xcb_timestamp_t timestamp = XCB_TIME_CURRENT_TIME); void updateUrgency(); static void sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1 = 0, uint32_t data2 = 0, uint32_t data3 = 0, xcb_timestamp_t timestamp = xTime()); void embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth); void detectNoBorder(); Xcb::Property fetchGtkFrameExtents() const; void readGtkFrameExtents(Xcb::Property &prop); void detectGtkFrameExtents(); void destroyDecoration() override; void updateFrameExtents(); void internalShow(); void internalHide(); void internalKeep(); void map(); void unmap(); void updateHiddenPreview(); void updateInputShape(); xcb_timestamp_t readUserTimeMapTimestamp(const KStartupInfoId* asn_id, const KStartupInfoData* asn_data, bool session) const; xcb_timestamp_t readUserCreationTime() const; void startupIdChanged(); void updateInputWindow(); bool tabTo(Client *other, bool behind, bool activate); Xcb::Property fetchShowOnScreenEdge() const; void readShowOnScreenEdge(Xcb::Property &property); /** * Reads the property and creates/destroys the screen edge if required * and shows/hides the client. **/ void updateShowOnScreenEdge(); Xcb::Window m_client; Xcb::Window m_wrapper; Xcb::Window m_frame; QStringList activityList; int m_activityUpdatesBlocked; bool m_blockedActivityUpdatesRequireTransients; Xcb::Window m_moveResizeGrabWindow; bool move_resize_has_keyboard_grab; bool m_managed; Xcb::GeometryHints m_geometryHints; void sendSyntheticConfigureNotify(); enum MappingState { Withdrawn, ///< Not handled, as per ICCCM WithdrawnState Mapped, ///< The frame is mapped Unmapped, ///< The frame is not mapped Kept ///< The frame should be unmapped, but is kept (For compositing) }; MappingState mapping_state; Xcb::TransientFor fetchTransient() const; void readTransientProperty(Xcb::TransientFor &transientFor); void readTransient(); xcb_window_t verifyTransientFor(xcb_window_t transient_for, bool set); void addTransient(AbstractClient* cl) override; void removeTransient(AbstractClient* cl) override; void removeFromMainClients(); void cleanGrouping(); void checkGroupTransients(); void setTransient(xcb_window_t new_transient_for_id); xcb_window_t m_transientForId; xcb_window_t m_originalTransientForId; ShadeMode shade_mode; Client *shade_below; uint deleting : 1; ///< True when doing cleanup and destroying the client Xcb::MotifHints m_motif; uint hidden : 1; ///< Forcibly hidden by calling hide() uint noborder : 1; uint app_noborder : 1; ///< App requested no border via window type, shape extension, etc. uint ignore_focus_stealing : 1; ///< Don't apply focus stealing prevention to this client bool blocks_compositing; // DON'T reorder - Saved to config files !!! enum FullScreenMode { FullScreenNone, FullScreenNormal, FullScreenHack ///< Non-NETWM fullscreen (noborder and size of desktop) }; FullScreenMode fullscreen_mode; MaximizeMode max_mode; QRect geom_restore; QRect geom_fs_restore; QTimer* shadeHoverTimer; xcb_colormap_t m_colormap; QString cap_normal, cap_iconic, cap_suffix; Group* in_group; TabGroup* tab_group; QTimer* ping_timer; qint64 m_killHelperPID; xcb_timestamp_t m_pingTimestamp; xcb_timestamp_t m_userTime; NET::Actions allowed_actions; QSize client_size; bool shade_geometry_change; - struct { - xcb_sync_counter_t counter; - xcb_sync_int64_t value; - xcb_sync_alarm_t alarm; - xcb_timestamp_t lastTimestamp; - QTimer *timeout, *failsafeTimeout; - bool isPending; - } syncRequest; + SyncRequest syncRequest; static bool check_active_modal; ///< \see Client::checkActiveModal() int sm_stacking_order; friend struct ResetupRulesProcedure; friend bool performTransiencyCheck(); Xcb::StringProperty fetchActivities() const; void readActivities(Xcb::StringProperty &property); void checkActivities(); bool activitiesDefined; //whether the x property was actually set bool sessionActivityOverride; bool needsXWindowMove; Xcb::Window m_decoInputExtent; QPoint input_offset; QTimer *m_focusOutTimer; QList m_connections; bool m_clientSideDecorated; QMetaObject::Connection m_edgeRemoveConnection; QMetaObject::Connection m_edgeGeometryTrackingConnection; }; inline xcb_window_t Client::wrapperId() const { return m_wrapper; } inline bool Client::isClientSideDecorated() const { return m_clientSideDecorated; } inline bool Client::groupTransient() const { return m_transientForId == rootWindow(); } // Needed because verifyTransientFor() may set transient_for_id to root window, // if the original value has a problem (window doesn't exist, etc.) inline bool Client::wasOriginallyGroupTransient() const { return m_originalTransientForId == rootWindow(); } inline bool Client::isTransient() const { return m_transientForId != XCB_WINDOW_NONE; } inline const Group* Client::group() const { return in_group; } inline Group* Client::group() { return in_group; } inline TabGroup* Client::tabGroup() const { return tab_group; } inline bool Client::isShown(bool shaded_is_shown) const { return !isMinimized() && (!isShade() || shaded_is_shown) && !hidden && (!tabGroup() || tabGroup()->current() == this); } inline bool Client::isHiddenInternal() const { return hidden; } inline ShadeMode Client::shadeMode() const { return shade_mode; } inline QRect Client::geometryRestore() const { return geom_restore; } inline void Client::setGeometryRestore(const QRect &geo) { geom_restore = geo; } inline MaximizeMode Client::maximizeMode() const { return max_mode; } inline bool Client::isFullScreen() const { return fullscreen_mode != FullScreenNone; } inline bool Client::hasNETSupport() const { return info->hasNETSupport(); } inline xcb_colormap_t Client::colormap() const { return m_colormap; } inline int Client::sessionStackingOrder() const { return sm_stacking_order; } inline bool Client::isManaged() const { return m_managed; } inline QSize Client::clientSize() const { return client_size; } inline void Client::plainResize(const QSize& s, ForceGeometry_t force) { plainResize(s.width(), s.height(), force); } inline void Client::resizeWithChecks(int w, int h, AbstractClient::ForceGeometry_t force) { resizeWithChecks(w, h, XCB_GRAVITY_BIT_FORGET, force); } inline void Client::resizeWithChecks(const QSize& s, xcb_gravity_t gravity, ForceGeometry_t force) { resizeWithChecks(s.width(), s.height(), gravity, force); } inline bool Client::hasUserTimeSupport() const { return info->userTime() != -1U; } inline xcb_window_t Client::moveResizeGrabWindow() const { return m_moveResizeGrabWindow; } inline bool Client::hiddenPreview() const { return mapping_state == Kept; } template inline void Client::print(T &stream) const { stream << "\'ID:" << window() << ";WMCLASS:" << resourceClass() << ":" << resourceName() << ";Caption:" << caption() << "\'"; } } // namespace Q_DECLARE_METATYPE(KWin::Client*) Q_DECLARE_METATYPE(QList) #endif diff --git a/events.cpp b/events.cpp index 2f58be3a3..17a26068a 100644 --- a/events.cpp +++ b/events.cpp @@ -1,1368 +1,1345 @@ /******************************************************************** 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 "client.h" #include "cursor.h" #include "focuschain.h" #include "netinfo.h" #include "workspace.h" #include "atoms.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "group.h" #include "rules.h" #include "unmanaged.h" #include "useractions.h" #include "effects.h" #include "screens.h" #include "xcbutils.h" #include #include #include #include #include #include #include #include #include #include #ifdef XCB_ICCCM_FOUND #include #endif #include "composite.h" #include "x11eventfilter.h" #include "wayland_server.h" #include #ifndef XCB_GE_GENERIC #define XCB_GE_GENERIC 35 typedef struct xcb_ge_generic_event_t { uint8_t response_type; /**< */ uint8_t extension; /**< */ uint16_t sequence; /**< */ uint32_t length; /**< */ uint16_t event_type; /**< */ uint8_t pad0[22]; /**< */ uint32_t full_sequence; /**< */ } xcb_ge_generic_event_t; #endif namespace KWin { // **************************************** // Workspace // **************************************** static xcb_window_t findEventWindow(xcb_generic_event_t *event) { const uint8_t eventType = event->response_type & ~0x80; switch(eventType) { case XCB_KEY_PRESS: case XCB_KEY_RELEASE: return reinterpret_cast(event)->event; case XCB_BUTTON_PRESS: case XCB_BUTTON_RELEASE: return reinterpret_cast(event)->event; case XCB_MOTION_NOTIFY: return reinterpret_cast(event)->event; case XCB_ENTER_NOTIFY: case XCB_LEAVE_NOTIFY: return reinterpret_cast(event)->event; case XCB_FOCUS_IN: case XCB_FOCUS_OUT: return reinterpret_cast(event)->event; case XCB_EXPOSE: return reinterpret_cast(event)->window; case XCB_GRAPHICS_EXPOSURE: return reinterpret_cast(event)->drawable; case XCB_NO_EXPOSURE: return reinterpret_cast(event)->drawable; case XCB_VISIBILITY_NOTIFY: return reinterpret_cast(event)->window; case XCB_CREATE_NOTIFY: return reinterpret_cast(event)->window; case XCB_DESTROY_NOTIFY: return reinterpret_cast(event)->window; case XCB_UNMAP_NOTIFY: return reinterpret_cast(event)->window; case XCB_MAP_NOTIFY: return reinterpret_cast(event)->window; case XCB_MAP_REQUEST: return reinterpret_cast(event)->window; case XCB_REPARENT_NOTIFY: return reinterpret_cast(event)->window; case XCB_CONFIGURE_NOTIFY: return reinterpret_cast(event)->window; case XCB_CONFIGURE_REQUEST: return reinterpret_cast(event)->window; case XCB_GRAVITY_NOTIFY: return reinterpret_cast(event)->window; case XCB_RESIZE_REQUEST: return reinterpret_cast(event)->window; case XCB_CIRCULATE_NOTIFY: case XCB_CIRCULATE_REQUEST: return reinterpret_cast(event)->window; case XCB_PROPERTY_NOTIFY: return reinterpret_cast(event)->window; case XCB_COLORMAP_NOTIFY: return reinterpret_cast(event)->window; case XCB_CLIENT_MESSAGE: return reinterpret_cast(event)->window; default: // extension handling if (eventType == Xcb::Extensions::self()->shapeNotifyEvent()) { return reinterpret_cast(event)->affected_window; } if (eventType == Xcb::Extensions::self()->damageNotifyEvent()) { return reinterpret_cast(event)->drawable; } return XCB_WINDOW_NONE; } } QVector s_xcbEerrors({ QByteArrayLiteral("Success"), QByteArrayLiteral("BadRequest"), QByteArrayLiteral("BadValue"), QByteArrayLiteral("BadWindow"), QByteArrayLiteral("BadPixmap"), QByteArrayLiteral("BadAtom"), QByteArrayLiteral("BadCursor"), QByteArrayLiteral("BadFont"), QByteArrayLiteral("BadMatch"), QByteArrayLiteral("BadDrawable"), QByteArrayLiteral("BadAccess"), QByteArrayLiteral("BadAlloc"), QByteArrayLiteral("BadColor"), QByteArrayLiteral("BadGC"), QByteArrayLiteral("BadIDChoice"), QByteArrayLiteral("BadName"), QByteArrayLiteral("BadLength"), QByteArrayLiteral("BadImplementation"), QByteArrayLiteral("Unknown")}); void Workspace::registerEventFilter(X11EventFilter *filter) { if (filter->isGenericEvent()) m_genericEventFilters.append(filter); else m_eventFilters.append(filter); } void Workspace::unregisterEventFilter(X11EventFilter *filter) { if (filter->isGenericEvent()) m_genericEventFilters.removeOne(filter); else m_eventFilters.removeOne(filter); } /*! Handles workspace specific XCB event */ bool Workspace::workspaceEvent(xcb_generic_event_t *e) { const uint8_t eventType = e->response_type & ~0x80; if (!eventType) { // let's check whether it's an error from one of the extensions KWin uses xcb_generic_error_t *error = reinterpret_cast(e); const QVector extensions = Xcb::Extensions::self()->extensions(); for (const auto &extension : extensions) { if (error->major_code == extension.majorOpcode) { QByteArray errorName; if (error->error_code < s_xcbEerrors.size()) { errorName = s_xcbEerrors.at(error->error_code); } else if (error->error_code >= extension.errorBase) { const int index = error->error_code - extension.errorBase; if (index >= 0 && index < extension.errorCodes.size()) { errorName = extension.errorCodes.at(index); } } if (errorName.isEmpty()) { errorName = QByteArrayLiteral("Unknown"); } qCWarning(KWIN_CORE, "XCB error: %d (%s), sequence: %d, resource id: %d, major code: %d (%s), minor code: %d (%s)", int(error->error_code), errorName.constData(), int(error->sequence), int(error->resource_id), int(error->major_code), extension.name.constData(), int(error->minor_code), extension.opCodes.size() > error->minor_code ? extension.opCodes.at(error->minor_code).constData() : "Unknown"); return true; } } return false; } if (eventType == XCB_GE_GENERIC) { xcb_ge_generic_event_t *ge = reinterpret_cast(e); foreach (X11EventFilter *filter, m_genericEventFilters) { if (filter->extension() == ge->extension && filter->genericEventTypes().contains(ge->event_type) && filter->event(e)) { return true; } } } else { foreach (X11EventFilter *filter, m_eventFilters) { if (filter->eventTypes().contains(eventType) && filter->event(e)) { return true; } } } if (effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab() && (eventType == XCB_KEY_PRESS || eventType == XCB_KEY_RELEASE)) return false; // let Qt process it, it'll be intercepted again in eventFilter() // events that should be handled before Clients can get them switch (eventType) { case XCB_CONFIGURE_NOTIFY: if (reinterpret_cast(e)->event == rootWindow()) markXStackingOrderAsDirty(); break; }; const xcb_window_t eventWindow = findEventWindow(e); if (eventWindow != XCB_WINDOW_NONE) { if (Client* c = findClient(Predicate::WindowMatch, eventWindow)) { if (c->windowEvent(e)) return true; } else if (Client* c = findClient(Predicate::WrapperIdMatch, eventWindow)) { if (c->windowEvent(e)) return true; } else if (Client* c = findClient(Predicate::FrameIdMatch, eventWindow)) { if (c->windowEvent(e)) return true; } else if (Client *c = findClient(Predicate::InputIdMatch, eventWindow)) { if (c->windowEvent(e)) return true; } else if (Unmanaged* c = findUnmanaged(eventWindow)) { if (c->windowEvent(e)) return true; } } switch (eventType) { case XCB_CREATE_NOTIFY: { const auto *event = reinterpret_cast(e); if (event->parent == rootWindow() && !QWidget::find(event->window) && !event->override_redirect) { // see comments for allowClientActivation() updateXTime(); const xcb_timestamp_t t = xTime(); xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, event->window, atoms->kde_net_wm_user_creation_time, XCB_ATOM_CARDINAL, 32, 1, &t); } break; } case XCB_UNMAP_NOTIFY: { const auto *event = reinterpret_cast(e); return (event->event != event->window); // hide wm typical event from Qt } case XCB_REPARENT_NOTIFY: { //do not confuse Qt with these events. After all, _we_ are the //window manager who does the reparenting. return true; } case XCB_MAP_REQUEST: { updateXTime(); const auto *event = reinterpret_cast(e); if (Client* c = findClient(Predicate::WindowMatch, event->window)) { // e->xmaprequest.window is different from e->xany.window // TODO this shouldn't be necessary now c->windowEvent(e); FocusChain::self()->update(c, FocusChain::Update); } else if ( true /*|| e->xmaprequest.parent != root */ ) { // NOTICE don't check for the parent being the root window, this breaks when some app unmaps // a window, changes something and immediately maps it back, without giving KWin // a chance to reparent it back to root // since KWin can get MapRequest only for root window children and // children of WindowWrapper (=clients), the check is AFAIK useless anyway // NOTICE: The save-set support in Client::mapRequestEvent() actually requires that // this code doesn't check the parent to be root. if (!createClient(event->window, false)) { xcb_map_window(connection(), event->window); const uint32_t values[] = { XCB_STACK_MODE_ABOVE }; xcb_configure_window(connection(), event->window, XCB_CONFIG_WINDOW_STACK_MODE, values); } } return true; } case XCB_MAP_NOTIFY: { const auto *event = reinterpret_cast(e); if (event->override_redirect) { Unmanaged* c = findUnmanaged(event->window); if (c == NULL) c = createUnmanaged(event->window); if (c) return c->windowEvent(e); } return (event->event != event->window); // hide wm typical event from Qt } case XCB_CONFIGURE_REQUEST: { const auto *event = reinterpret_cast(e); if (event->parent == rootWindow()) { uint32_t values[5] = { 0, 0, 0, 0, 0}; const uint32_t value_mask = event->value_mask & (XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH); int i = 0; if (value_mask & XCB_CONFIG_WINDOW_X) { values[i++] = event->x; } if (value_mask & XCB_CONFIG_WINDOW_Y) { values[i++] = event->y; } if (value_mask & XCB_CONFIG_WINDOW_WIDTH) { values[i++] = event->width; } if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) { values[i++] = event->height; } if (value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) { values[i++] = event->border_width; } xcb_configure_window(connection(), event->window, value_mask, values); return true; } break; } case XCB_FOCUS_IN: { const auto *event = reinterpret_cast(e); if (event->event == rootWindow() && (event->detail == XCB_NOTIFY_DETAIL_NONE || event->detail == XCB_NOTIFY_DETAIL_POINTER_ROOT || event->detail == XCB_NOTIFY_DETAIL_INFERIOR)) { Xcb::CurrentInput currentInput; updateXTime(); // focusToNull() uses xTime(), which is old now (FocusIn has no timestamp) // it seems we can "loose" focus reversions when the closing client hold a grab // => catch the typical pattern (though we don't want the focus on the root anyway) #348935 const bool lostFocusPointerToRoot = currentInput->focus == rootWindow() && event->detail == XCB_NOTIFY_DETAIL_INFERIOR; if (!currentInput.isNull() && (currentInput->focus == XCB_WINDOW_NONE || currentInput->focus == XCB_INPUT_FOCUS_POINTER_ROOT || lostFocusPointerToRoot)) { //kWarning( 1212 ) << "X focus set to None/PointerRoot, reseting focus" ; AbstractClient *c = mostRecentlyActivatedClient(); if (c != NULL) requestFocus(c, true); else if (activateNextClient(NULL)) ; // ok, activated else focusToNull(); } } } // fall through case XCB_FOCUS_OUT: return true; // always eat these, they would tell Qt that KWin is the active app default: - if (eventType == Xcb::Extensions::self()->syncAlarmNotifyEvent() && Xcb::Extensions::self()->isSyncAvailable()) { - for (Client *c : clients) - c->syncEvent(reinterpret_cast< xcb_sync_alarm_notify_event_t* >(e)); - for (Client *c : desktops) - c->syncEvent(reinterpret_cast< xcb_sync_alarm_notify_event_t* >(e)); - } break; } return false; } // Used only to filter events that need to be processed by Qt first // (e.g. keyboard input to be composed), otherwise events are // handle by the XEvent filter above bool Workspace::workspaceEvent(QEvent* e) { if ((e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease || e->type() == QEvent::ShortcutOverride) && effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) { static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(static_cast< QKeyEvent* >(e)); return true; } return false; } // **************************************** // Client // **************************************** /*! General handler for XEvents concerning the client window */ bool Client::windowEvent(xcb_generic_event_t *e) { if (findEventWindow(e) == window()) { // avoid doing stuff on frame or wrapper NET::Properties dirtyProperties; NET::Properties2 dirtyProperties2; double old_opacity = opacity(); info->event(e, &dirtyProperties, &dirtyProperties2); // pass through the NET stuff if ((dirtyProperties & NET::WMName) != 0) fetchName(); if ((dirtyProperties & NET::WMIconName) != 0) fetchIconicName(); if ((dirtyProperties & NET::WMStrut) != 0 || (dirtyProperties2 & NET::WM2ExtendedStrut) != 0) { workspace()->updateClientArea(); } if ((dirtyProperties & NET::WMIcon) != 0) getIcons(); // Note there's a difference between userTime() and info->userTime() // info->userTime() is the value of the property, userTime() also includes // updates of the time done by KWin (ButtonPress on windowrapper etc.). if ((dirtyProperties2 & NET::WM2UserTime) != 0) { workspace()->setWasUserInteraction(); updateUserTime(info->userTime()); } if ((dirtyProperties2 & NET::WM2StartupId) != 0) startupIdChanged(); if (dirtyProperties2 & NET::WM2Opacity) { if (compositing()) { addRepaintFull(); emit opacityChanged(this, old_opacity); } else { // forward to the frame if there's possibly another compositing manager running NETWinInfo i(connection(), frameId(), rootWindow(), 0, 0); i.setOpacity(info->opacity()); } } if (dirtyProperties2 & NET::WM2FrameOverlap) { // ### Inform the decoration } if (dirtyProperties2.testFlag(NET::WM2WindowRole)) { emit windowRoleChanged(); } if (dirtyProperties2.testFlag(NET::WM2WindowClass)) { getResourceClass(); } if (dirtyProperties2.testFlag(NET::WM2BlockCompositing)) { setBlockingCompositing(info->isBlockingCompositing()); } if (dirtyProperties2.testFlag(NET::WM2GroupLeader)) { checkGroup(); updateAllowedActions(); // Group affects isMinimizable() } if (dirtyProperties2.testFlag(NET::WM2Urgency)) { updateUrgency(); } if (dirtyProperties2 & NET::WM2OpaqueRegion) { getWmOpaqueRegion(); } if (dirtyProperties2 & NET::WM2DesktopFileName) { setDesktopFileName(QByteArray(info->desktopFileName())); } } const uint8_t eventType = e->response_type & ~0x80; switch(eventType) { case XCB_UNMAP_NOTIFY: unmapNotifyEvent(reinterpret_cast(e)); break; case XCB_DESTROY_NOTIFY: destroyNotifyEvent(reinterpret_cast(e)); break; case XCB_MAP_REQUEST: // this one may pass the event to workspace return mapRequestEvent(reinterpret_cast(e)); case XCB_CONFIGURE_REQUEST: configureRequestEvent(reinterpret_cast(e)); break; case XCB_PROPERTY_NOTIFY: propertyNotifyEvent(reinterpret_cast(e)); break; case XCB_KEY_PRESS: updateUserTime(reinterpret_cast(e)->time); break; case XCB_BUTTON_PRESS: { const auto *event = reinterpret_cast(e); updateUserTime(event->time); buttonPressEvent(event->event, event->detail, event->state, event->event_x, event->event_y, event->root_x, event->root_y, event->time); break; } case XCB_KEY_RELEASE: // don't update user time on releases // e.g. if the user presses Alt+F2, the Alt release // would appear as user input to the currently active window break; case XCB_BUTTON_RELEASE: { const auto *event = reinterpret_cast(e); // don't update user time on releases // e.g. if the user presses Alt+F2, the Alt release // would appear as user input to the currently active window buttonReleaseEvent(event->event, event->detail, event->state, event->event_x, event->event_y, event->root_x, event->root_y); break; } case XCB_MOTION_NOTIFY: { const auto *event = reinterpret_cast(e); motionNotifyEvent(event->event, event->state, event->event_x, event->event_y, event->root_x, event->root_y); workspace()->updateFocusMousePosition(QPoint(event->root_x, event->root_y)); break; } case XCB_ENTER_NOTIFY: { auto *event = reinterpret_cast(e); enterNotifyEvent(event); // MotionNotify is guaranteed to be generated only if the mouse // move start and ends in the window; for cases when it only // starts or only ends there, Enter/LeaveNotify are generated. // Fake a MotionEvent in such cases to make handle of mouse // events simpler (Qt does that too). motionNotifyEvent(event->event, event->state, event->event_x, event->event_y, event->root_x, event->root_y); workspace()->updateFocusMousePosition(QPoint(event->root_x, event->root_y)); break; } case XCB_LEAVE_NOTIFY: { auto *event = reinterpret_cast(e); motionNotifyEvent(event->event, event->state, event->event_x, event->event_y, event->root_x, event->root_y); leaveNotifyEvent(event); // not here, it'd break following enter notify handling // workspace()->updateFocusMousePosition( QPoint( e->xcrossing.x_root, e->xcrossing.y_root )); break; } case XCB_FOCUS_IN: focusInEvent(reinterpret_cast(e)); break; case XCB_FOCUS_OUT: focusOutEvent(reinterpret_cast(e)); break; case XCB_REPARENT_NOTIFY: break; case XCB_CLIENT_MESSAGE: clientMessageEvent(reinterpret_cast(e)); break; case XCB_EXPOSE: { xcb_expose_event_t *event = reinterpret_cast(e); if (event->window == frameId() && !Compositor::self()->isActive()) { // TODO: only repaint required areas triggerDecorationRepaint(); } break; } default: if (eventType == Xcb::Extensions::self()->shapeNotifyEvent() && reinterpret_cast(e)->affected_window == window()) { detectShape(window()); // workaround for #19644 updateShape(); } if (eventType == Xcb::Extensions::self()->damageNotifyEvent() && reinterpret_cast(e)->drawable == frameId()) damageNotifyEvent(); break; } return true; // eat all events } /*! Handles map requests of the client window */ bool Client::mapRequestEvent(xcb_map_request_event_t *e) { if (e->window != window()) { // Special support for the save-set feature, which is a bit broken. // If there's a window from one client embedded in another one, // e.g. using XEMBED, and the embedder suddenly loses its X connection, // save-set will reparent the embedded window to its closest ancestor // that will remains. Unfortunately, with reparenting window managers, // this is not the root window, but the frame (or in KWin's case, // it's the wrapper for the client window). In this case, // the wrapper will get ReparentNotify for a window it won't know, // which will be ignored, and then it gets MapRequest, as save-set // always maps. Returning true here means that Workspace::workspaceEvent() // will handle this MapRequest and manage this window (i.e. act as if // it was reparented to root window). if (e->parent == wrapperId()) return false; return true; // no messing with frame etc. } // also copied in clientMessage() if (isMinimized()) unminimize(); if (isShade()) setShade(ShadeNone); if (!isOnCurrentDesktop()) { if (workspace()->allowClientActivation(this)) workspace()->activateClient(this); else demandAttention(); } return true; } /*! Handles unmap notify events of the client window */ void Client::unmapNotifyEvent(xcb_unmap_notify_event_t *e) { if (e->window != window()) return; if (e->event != wrapperId()) { // most probably event from root window when initially reparenting bool ignore = true; if (e->event == rootWindow() && (e->response_type & 0x80)) ignore = false; // XWithdrawWindow() if (ignore) return; } // check whether this is result of an XReparentWindow - client then won't be parented by wrapper // in this case do not release the client (causes reparent to root, removal from saveSet and what not) // but just destroy the client Xcb::Tree tree(m_client); xcb_window_t daddy = tree.parent(); if (daddy == m_wrapper) { releaseWindow(); // unmapped from a regular client state } else { destroyClient(); // the client was moved to some other parent } } void Client::destroyNotifyEvent(xcb_destroy_notify_event_t *e) { if (e->window != window()) return; destroyClient(); } /*! Handles client messages for the client window */ void Client::clientMessageEvent(xcb_client_message_event_t *e) { Toplevel::clientMessageEvent(e); if (e->window != window()) return; // ignore frame/wrapper // WM_STATE if (e->type == atoms->wm_change_state) { if (e->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) minimize(); return; } } /*! Handles configure requests of the client window */ void Client::configureRequestEvent(xcb_configure_request_event_t *e) { if (e->window != window()) return; // ignore frame/wrapper if (isResize() || isMove()) return; // we have better things to do right now if (fullscreen_mode == FullScreenNormal) { // refuse resizing of fullscreen windows // but allow resizing fullscreen hacks in order to let them cancel fullscreen mode sendSyntheticConfigureNotify(); return; } if (isSplash()) { // no manipulations with splashscreens either sendSyntheticConfigureNotify(); return; } if (e->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) { // first, get rid of a window border m_client.setBorderWidth(0); } if (e->value_mask & (XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_WIDTH)) configureRequest(e->value_mask, e->x, e->y, e->width, e->height, 0, false); if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) restackWindow(e->sibling, e->stack_mode, NET::FromApplication, userTime(), false); // Sending a synthetic configure notify always is fine, even in cases where // the ICCCM doesn't require this - it can be though of as 'the WM decided to move // the window later'. The client should not cause that many configure request, // so this should not have any significant impact. With user moving/resizing // the it should be optimized though (see also Client::setGeometry()/plainResize()/move()). sendSyntheticConfigureNotify(); // SELI TODO accept configure requests for isDesktop windows (because kdesktop // may get XRANDR resize event before kwin), but check it's still at the bottom? } /*! Handles property changes of the client window */ void Client::propertyNotifyEvent(xcb_property_notify_event_t *e) { Toplevel::propertyNotifyEvent(e); if (e->window != window()) return; // ignore frame/wrapper switch(e->atom) { case XCB_ATOM_WM_NORMAL_HINTS: getWmNormalHints(); break; case XCB_ATOM_WM_NAME: fetchName(); break; case XCB_ATOM_WM_ICON_NAME: fetchIconicName(); break; case XCB_ATOM_WM_TRANSIENT_FOR: readTransient(); break; case XCB_ATOM_WM_HINTS: getIcons(); // because KWin::icon() uses WMHints as fallback break; default: if (e->atom == atoms->motif_wm_hints) { getMotifHints(); } else if (e->atom == atoms->net_wm_sync_request_counter) getSyncCounter(); else if (e->atom == atoms->activities) checkActivities(); else if (e->atom == atoms->kde_first_in_window_list) updateFirstInTabBox(); else if (e->atom == atoms->kde_color_sheme) updateColorScheme(); else if (e->atom == atoms->kde_screen_edge_show) updateShowOnScreenEdge(); else if (e->atom == atoms->gtk_frame_extents) detectGtkFrameExtents(); else if (e->atom == atoms->kde_net_wm_appmenu_service_name) checkApplicationMenuServiceName(); else if (e->atom == atoms->kde_net_wm_appmenu_object_path) checkApplicationMenuObjectPath(); break; } } void Client::enterNotifyEvent(xcb_enter_notify_event_t *e) { if (e->event != frameId()) return; // care only about entering the whole frame #define MOUSE_DRIVEN_FOCUS (!options->focusPolicyIsReasonable() || \ (options->focusPolicy() == Options::FocusFollowsMouse && options->isNextFocusPrefersMouse())) if (e->mode == XCB_NOTIFY_MODE_NORMAL || (e->mode == XCB_NOTIFY_MODE_UNGRAB && MOUSE_DRIVEN_FOCUS)) { if (options->isShadeHover()) { cancelShadeHoverTimer(); if (isShade()) { shadeHoverTimer = new QTimer(this); connect(shadeHoverTimer, SIGNAL(timeout()), this, SLOT(shadeHover())); shadeHoverTimer->setSingleShot(true); shadeHoverTimer->start(options->shadeHoverInterval()); } } #undef MOUSE_DRIVEN_FOCUS enterEvent(QPoint(e->root_x, e->root_y)); return; } } void Client::leaveNotifyEvent(xcb_leave_notify_event_t *e) { if (e->event != frameId()) return; // care only about leaving the whole frame if (e->mode == XCB_NOTIFY_MODE_NORMAL) { if (!isMoveResizePointerButtonDown()) { setMoveResizePointerMode(PositionCenter); updateCursor(); } bool lostMouse = !rect().contains(QPoint(e->event_x, e->event_y)); // 'lostMouse' wouldn't work with e.g. B2 or Keramik, which have non-rectangular decorations // (i.e. the LeaveNotify event comes before leaving the rect and no LeaveNotify event // comes after leaving the rect) - so lets check if the pointer is really outside the window // TODO this still sucks if a window appears above this one - it should lose the mouse // if this window is another client, but not if it's a popup ... maybe after KDE3.1 :( // (repeat after me 'AARGHL!') if (!lostMouse && e->detail != XCB_NOTIFY_DETAIL_INFERIOR) { Xcb::Pointer pointer(frameId()); if (!pointer || !pointer->same_screen || pointer->child == XCB_WINDOW_NONE) { // really lost the mouse lostMouse = true; } } if (lostMouse) { leaveEvent(); cancelShadeHoverTimer(); if (shade_mode == ShadeHover && !isMoveResize() && !isMoveResizePointerButtonDown()) { shadeHoverTimer = new QTimer(this); connect(shadeHoverTimer, SIGNAL(timeout()), this, SLOT(shadeUnhover())); shadeHoverTimer->setSingleShot(true); shadeHoverTimer->start(options->shadeHoverInterval()); } if (isDecorated()) { // sending a move instead of a leave. With leave we need to send proper coords, with move it's handled internally QHoverEvent leaveEvent(QEvent::HoverMove, QPointF(-1, -1), QPointF(-1, -1), Qt::NoModifier); QCoreApplication::sendEvent(decoration(), &leaveEvent); } } if (options->focusPolicy() == Options::FocusStrictlyUnderMouse && isActive() && lostMouse) { workspace()->requestDelayFocus(0); } return; } } #define XCapL KKeyServer::modXLock() #define XNumL KKeyServer::modXNumLock() #define XScrL KKeyServer::modXScrollLock() void Client::grabButton(int modifier) { unsigned int mods[ 8 ] = { 0, XCapL, XNumL, XNumL | XCapL, XScrL, XScrL | XCapL, XScrL | XNumL, XScrL | XNumL | XCapL }; for (int i = 0; i < 8; ++i) m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, modifier | mods[ i ]); } void Client::ungrabButton(int modifier) { unsigned int mods[ 8 ] = { 0, XCapL, XNumL, XNumL | XCapL, XScrL, XScrL | XCapL, XScrL | XNumL, XScrL | XNumL | XCapL }; for (int i = 0; i < 8; ++i) m_wrapper.ungrabButton(modifier | mods[ i ]); } #undef XCapL #undef XNumL #undef XScrL /* Releases the passive grab for some modifier combinations when a window becomes active. This helps broken X programs that missinterpret LeaveNotify events in grab mode to work properly (Motif, AWT, Tk, ...) */ void Client::updateMouseGrab() { if (workspace()->globalShortcutsDisabled()) { m_wrapper.ungrabButton(); // keep grab for the simple click without modifiers if needed (see below) bool not_obscured = workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), -1, true, false) == this; if (!(!options->isClickRaise() || not_obscured)) grabButton(XCB_NONE); return; } if (isActive() && !TabBox::TabBox::self()->forcedGlobalMouseGrab()) { // see TabBox::establishTabBoxGrab() // first grab all modifier combinations m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); // remove the grab for no modifiers only if the window // is unobscured or if the user doesn't want click raise // (it is unobscured if it the topmost in the unconstrained stacking order, i.e. it is // the most recently raised window) bool not_obscured = workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), -1, true, false) == this; if (!options->isClickRaise() || not_obscured) ungrabButton(XCB_NONE); else grabButton(XCB_NONE); ungrabButton(XCB_MOD_MASK_SHIFT); ungrabButton(XCB_MOD_MASK_CONTROL); ungrabButton(XCB_MOD_MASK_CONTROL | XCB_MOD_MASK_SHIFT); } else { m_wrapper.ungrabButton(); // simply grab all modifier combinations m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); } } static bool modKeyDown(int state) { const uint keyModX = (options->keyCmdAllModKey() == Qt::Key_Meta) ? KKeyServer::modXMeta() : KKeyServer::modXAlt(); return keyModX && (state & KKeyServer::accelModMaskX()) == keyModX; } // return value matters only when filtering events before decoration gets them bool Client::buttonPressEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root, xcb_timestamp_t time) { if (isMoveResizePointerButtonDown()) { if (w == wrapperId()) xcb_allow_events(connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } if (w == wrapperId() || w == frameId() || w == inputId()) { // FRAME neco s tohohle by se melo zpracovat, nez to dostane dekorace updateUserTime(time); const bool bModKeyHeld = modKeyDown(state); if (isSplash() && button == XCB_BUTTON_INDEX_1 && !bModKeyHeld) { // hide splashwindow if the user clicks on it hideClient(true); if (w == wrapperId()) xcb_allow_events(connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } Options::MouseCommand com = Options::MouseNothing; bool was_action = false; if (bModKeyHeld) { was_action = true; switch(button) { case XCB_BUTTON_INDEX_1: com = options->commandAll1(); break; case XCB_BUTTON_INDEX_2: com = options->commandAll2(); break; case XCB_BUTTON_INDEX_3: com = options->commandAll3(); break; case XCB_BUTTON_INDEX_4: case XCB_BUTTON_INDEX_5: com = options->operationWindowMouseWheel(button == XCB_BUTTON_INDEX_4 ? 120 : -120); break; } } else { if (w == wrapperId()) { if (button < 4) { com = getMouseCommand(x11ToQtMouseButton(button), &was_action); } else if (button < 6) { com = getWheelCommand(Qt::Vertical, &was_action); } } } if (was_action) { bool replay = performMouseCommand(com, QPoint(x_root, y_root)); if (isSpecialWindow()) replay = true; if (w == wrapperId()) // these can come only from a grab xcb_allow_events(connection(), replay ? XCB_ALLOW_REPLAY_POINTER : XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } } if (w == wrapperId()) { // these can come only from a grab xcb_allow_events(connection(), XCB_ALLOW_REPLAY_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } if (w == inputId()) { x = x_root - geometry().x(); y = y_root - geometry().y(); // New API processes core events FIRST and only passes unused ones to the decoration QMouseEvent ev(QMouseEvent::MouseButtonPress, QPoint(x, y), QPoint(x_root, y_root), x11ToQtMouseButton(button), x11ToQtMouseButtons(state), Qt::KeyboardModifiers()); return processDecorationButtonPress(&ev, true); } if (w == frameId() && isDecorated()) { if (button >= 4 && button <= 7) { const Qt::KeyboardModifiers modifiers = x11ToQtKeyboardModifiers(state); // Logic borrowed from qapplication_x11.cpp const int delta = 120 * ((button == 4 || button == 6) ? 1 : -1); const bool hor = (((button == 4 || button == 5) && (modifiers & Qt::AltModifier)) || (button == 6 || button == 7)); const QPoint angle = hor ? QPoint(delta, 0) : QPoint(0, delta); QWheelEvent event(QPointF(x, y), QPointF(x_root, y_root), QPoint(), angle, delta, hor ? Qt::Horizontal : Qt::Vertical, x11ToQtMouseButtons(state), modifiers); event.setAccepted(false); QCoreApplication::sendEvent(decoration(), &event); if (!event.isAccepted() && !hor) { if (titlebarPositionUnderMouse()) { performMouseCommand(options->operationTitlebarMouseWheel(delta), QPoint(x_root, y_root)); } } } else { QMouseEvent event(QEvent::MouseButtonPress, QPointF(x, y), QPointF(x_root, y_root), x11ToQtMouseButton(button), x11ToQtMouseButtons(state), x11ToQtKeyboardModifiers(state)); event.setAccepted(false); QCoreApplication::sendEvent(decoration(), &event); if (!event.isAccepted()) { processDecorationButtonPress(&event); } } return true; } return true; } // return value matters only when filtering events before decoration gets them bool Client::buttonReleaseEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root) { if (w == frameId() && isDecorated()) { // wheel handled on buttonPress if (button < 4 || button > 7) { QMouseEvent event(QEvent::MouseButtonRelease, QPointF(x, y), QPointF(x_root, y_root), x11ToQtMouseButton(button), x11ToQtMouseButtons(state) & ~x11ToQtMouseButton(button), x11ToQtKeyboardModifiers(state)); event.setAccepted(false); QCoreApplication::sendEvent(decoration(), &event); if (event.isAccepted() || !titlebarPositionUnderMouse()) { invalidateDecorationDoubleClickTimer(); // click was for the deco and shall not init a doubleclick } } } if (w == wrapperId()) { xcb_allow_events(connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } if (w != frameId() && w != inputId() && w != moveResizeGrabWindow()) return true; if (w == frameId() && workspace()->userActionsMenu() && workspace()->userActionsMenu()->isShown()) { const_cast(workspace()->userActionsMenu())->grabInput(); } x = this->x(); // translate from grab window to local coords y = this->y(); // Check whether other buttons are still left pressed int buttonMask = XCB_BUTTON_MASK_1 | XCB_BUTTON_MASK_2 | XCB_BUTTON_MASK_3; if (button == XCB_BUTTON_INDEX_1) buttonMask &= ~XCB_BUTTON_MASK_1; else if (button == XCB_BUTTON_INDEX_2) buttonMask &= ~XCB_BUTTON_MASK_2; else if (button == XCB_BUTTON_INDEX_3) buttonMask &= ~XCB_BUTTON_MASK_3; if ((state & buttonMask) == 0) { endMoveResize(); } return true; } // return value matters only when filtering events before decoration gets them bool Client::motionNotifyEvent(xcb_window_t w, int state, int x, int y, int x_root, int y_root) { if (w == frameId() && isDecorated() && !isMinimized()) { // TODO Mouse move event dependent on state QHoverEvent event(QEvent::HoverMove, QPointF(x, y), QPointF(x, y)); QCoreApplication::instance()->sendEvent(decoration(), &event); } if (w != frameId() && w != inputId() && w != moveResizeGrabWindow()) return true; // care only about the whole frame if (!isMoveResizePointerButtonDown()) { if (w == inputId()) { int x = x_root - geometry().x();// + padding_left; int y = y_root - geometry().y();// + padding_top; if (isDecorated()) { QHoverEvent event(QEvent::HoverMove, QPointF(x, y), QPointF(x, y)); QCoreApplication::instance()->sendEvent(decoration(), &event); } } Position newmode = modKeyDown(state) ? PositionCenter : mousePosition(); if (newmode != moveResizePointerMode()) { setMoveResizePointerMode(newmode); updateCursor(); } return false; } if (w == moveResizeGrabWindow()) { x = this->x(); // translate from grab window to local coords y = this->y(); } handleMoveResize(QPoint(x, y), QPoint(x_root, y_root)); return true; } void Client::focusInEvent(xcb_focus_in_event_t *e) { if (e->event != window()) return; // only window gets focus if (e->mode == XCB_NOTIFY_MODE_UNGRAB) return; // we don't care if (e->detail == XCB_NOTIFY_DETAIL_POINTER) return; // we don't care if (!isShown(false) || !isOnCurrentDesktop()) // we unmapped it, but it got focus meanwhile -> return; // activateNextClient() already transferred focus elsewhere workspace()->forEachClient([](Client *client) { client->cancelFocusOutTimer(); }); // check if this client is in should_get_focus list or if activation is allowed bool activate = workspace()->allowClientActivation(this, -1U, true); workspace()->gotFocusIn(this); // remove from should_get_focus list if (activate) setActive(true); else { workspace()->restoreFocus(); demandAttention(); } } void Client::focusOutEvent(xcb_focus_out_event_t *e) { if (e->event != window()) return; // only window gets focus if (e->mode == XCB_NOTIFY_MODE_GRAB) return; // we don't care if (isShade()) return; // here neither if (e->detail != XCB_NOTIFY_DETAIL_NONLINEAR && e->detail != XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL) // SELI check all this return; // hack for motif apps like netscape if (QApplication::activePopupWidget()) return; // When a client loses focus, FocusOut events are usually immediatelly // followed by FocusIn events for another client that gains the focus // (unless the focus goes to another screen, or to the nofocus widget). // Without this check, the former focused client would have to be // deactivated, and after that, the new one would be activated, with // a short time when there would be no active client. This can cause // flicker sometimes, e.g. when a fullscreen is shown, and focus is transferred // from it to its transient, the fullscreen would be kept in the Active layer // at the beginning and at the end, but not in the middle, when the active // client would be temporarily none (see Client::belongToLayer() ). // Therefore the setActive(false) call is moved to the end of the current // event queue. If there is a matching FocusIn event in the current queue // this will be processed before the setActive(false) call and the activation // of the Client which gained FocusIn will automatically deactivate the // previously active client. if (!m_focusOutTimer) { m_focusOutTimer = new QTimer(this); m_focusOutTimer->setSingleShot(true); m_focusOutTimer->setInterval(0); connect(m_focusOutTimer, &QTimer::timeout, [this]() { setActive(false); }); } m_focusOutTimer->start(); } // performs _NET_WM_MOVERESIZE void Client::NETMoveResize(int x_root, int y_root, NET::Direction direction) { if (direction == NET::Move) { // move cursor to the provided position to prevent the window jumping there on first movement // the expectation is that the cursor is already at the provided position, // thus it's more a safety measurement Cursor::setPos(QPoint(x_root, y_root)); performMouseCommand(Options::MouseMove, QPoint(x_root, y_root)); } else if (isMoveResize() && direction == NET::MoveResizeCancel) { finishMoveResize(true); setMoveResizePointerButtonDown(false); updateCursor(); } else if (direction >= NET::TopLeft && direction <= NET::Left) { static const Position convert[] = { PositionTopLeft, PositionTop, PositionTopRight, PositionRight, PositionBottomRight, PositionBottom, PositionBottomLeft, PositionLeft }; if (!isResizable() || isShade()) return; if (isMoveResize()) finishMoveResize(false); setMoveResizePointerButtonDown(true); setMoveOffset(QPoint(x_root - x(), y_root - y())); // map from global setInvertedMoveOffset(rect().bottomRight() - moveOffset()); setUnrestrictedMoveResize(false); setMoveResizePointerMode(convert[ direction ]); if (!startMoveResize()) setMoveResizePointerButtonDown(false); updateCursor(); } else if (direction == NET::KeyboardMove) { // ignore mouse coordinates given in the message, mouse position is used by the moving algorithm Cursor::setPos(geometry().center()); performMouseCommand(Options::MouseUnrestrictedMove, geometry().center()); } else if (direction == NET::KeyboardSize) { // ignore mouse coordinates given in the message, mouse position is used by the resizing algorithm Cursor::setPos(geometry().bottomRight()); performMouseCommand(Options::MouseUnrestrictedResize, geometry().bottomRight()); } } void Client::keyPressEvent(uint key_code, xcb_timestamp_t time) { updateUserTime(time); AbstractClient::keyPressEvent(key_code); } -void Client::syncEvent(xcb_sync_alarm_notify_event_t* e) -{ - if (e->alarm == syncRequest.alarm && e->counter_value.hi == syncRequest.value.hi && e->counter_value.lo == syncRequest.value.lo) { - setReadyForPainting(); - setupWindowManagementInterface(); - syncRequest.isPending = false; - if (syncRequest.failsafeTimeout) - syncRequest.failsafeTimeout->stop(); - if (isResize()) { - if (syncRequest.timeout) - syncRequest.timeout->stop(); - performMoveResize(); - } else // setReadyForPainting does as well, but there's a small chance for resize syncs after the resize ended - addRepaintFull(); - } -} - // **************************************** // Unmanaged // **************************************** bool Unmanaged::windowEvent(xcb_generic_event_t *e) { double old_opacity = opacity(); NET::Properties dirtyProperties; NET::Properties2 dirtyProperties2; info->event(e, &dirtyProperties, &dirtyProperties2); // pass through the NET stuff if (dirtyProperties2 & NET::WM2Opacity) { if (compositing()) { addRepaintFull(); emit opacityChanged(this, old_opacity); } } if (dirtyProperties2 & NET::WM2OpaqueRegion) { getWmOpaqueRegion(); } if (dirtyProperties2.testFlag(NET::WM2WindowRole)) { emit windowRoleChanged(); } if (dirtyProperties2.testFlag(NET::WM2WindowClass)) { getResourceClass(); } const uint8_t eventType = e->response_type & ~0x80; switch (eventType) { case XCB_DESTROY_NOTIFY: release(ReleaseReason::Destroyed); break; case XCB_UNMAP_NOTIFY:{ workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event // unmap notify might have been emitted due to a destroy notify // but unmap notify gets emitted before the destroy notify, nevertheless at this // point the window is already destroyed. This means any XCB request with the window // will cause an error. // To not run into these errors we try to wait for the destroy notify. For this we // generate a round trip to the X server and wait a very short time span before // handling the release. updateXTime(); // using 1 msec to not just move it at the end of the event loop but add an very short // timespan to cover cases like unmap() followed by destroy(). The only other way to // ensure that the window is not destroyed when we do the release handling is to grab // the XServer which we do not want to do for an Unmanaged. The timespan of 1 msec is // short enough to not cause problems in the close window animations. // It's of course still possible that we miss the destroy in which case non-fatal // X errors are reported to the event loop and logged by Qt. QTimer::singleShot(1, this, SLOT(release())); break; } case XCB_CONFIGURE_NOTIFY: configureNotifyEvent(reinterpret_cast(e)); break; case XCB_PROPERTY_NOTIFY: propertyNotifyEvent(reinterpret_cast(e)); break; case XCB_CLIENT_MESSAGE: clientMessageEvent(reinterpret_cast(e)); break; default: { if (eventType == Xcb::Extensions::self()->shapeNotifyEvent()) { detectShape(window()); addRepaintFull(); addWorkspaceRepaint(geometry()); // in case shape change removes part of this window emit geometryShapeChanged(this, geometry()); } if (eventType == Xcb::Extensions::self()->damageNotifyEvent()) damageNotifyEvent(); break; } } return false; // don't eat events, even our own unmanaged widgets are tracked } void Unmanaged::configureNotifyEvent(xcb_configure_notify_event_t *e) { if (effects) static_cast(effects)->checkInputWindowStacking(); // keep them on top QRect newgeom(e->x, e->y, e->width, e->height); if (newgeom != geom) { addWorkspaceRepaint(visibleRect()); // damage old area QRect old = geom; geom = newgeom; emit geometryChanged(); // update shadow region addRepaintFull(); if (old.size() != geom.size()) discardWindowPixmap(); emit geometryShapeChanged(this, old); } } // **************************************** // Toplevel // **************************************** void Toplevel::propertyNotifyEvent(xcb_property_notify_event_t *e) { if (e->window != window()) return; // ignore frame/wrapper switch(e->atom) { default: if (e->atom == atoms->wm_client_leader) getWmClientLeader(); else if (e->atom == atoms->kde_net_wm_shadow) getShadow(); else if (e->atom == atoms->kde_skip_close_animation) getSkipCloseAnimation(); break; } } void Toplevel::clientMessageEvent(xcb_client_message_event_t *e) { if (e->type == atoms->wl_surface_id) { m_surfaceId = e->data.data32[0]; if (auto w = waylandServer()) { if (auto s = KWayland::Server::SurfaceInterface::get(m_surfaceId, w->xWaylandConnection())) { setSurface(s); } } emit surfaceIdChanged(m_surfaceId); } } } // namespace diff --git a/plugins/platforms/x11/standalone/CMakeLists.txt b/plugins/platforms/x11/standalone/CMakeLists.txt index 4ccb0711e..ebea1553d 100644 --- a/plugins/platforms/x11/standalone/CMakeLists.txt +++ b/plugins/platforms/x11/standalone/CMakeLists.txt @@ -1,42 +1,43 @@ set(X11PLATFORM_SOURCES edge.cpp logging.cpp x11cursor.cpp x11_platform.cpp screens_xrandr.cpp windowselector.cpp overlaywindow_x11.cpp screenedges_filter.cpp non_composited_outline.cpp x11_decoration_renderer.cpp xfixes_cursor_event_filter.cpp effects_x11.cpp effects_mouse_interception_x11_filter.cpp + sync_filter.cpp ) if(X11_Xinput_FOUND) set(X11PLATFORM_SOURCES ${X11PLATFORM_SOURCES} xinputintegration.cpp) endif() if(HAVE_EPOXY_GLX) set(X11PLATFORM_SOURCES ${X11PLATFORM_SOURCES} glxbackend.cpp glx_context_attribute_builder.cpp) endif() include_directories(${CMAKE_SOURCE_DIR}/platformsupport/scenes/opengl) add_library(KWinX11Platform MODULE ${X11PLATFORM_SOURCES}) target_link_libraries(KWinX11Platform eglx11common kwin kwinxrenderutils SceneOpenGLBackend Qt5::X11Extras XCB::CURSOR) if(X11_Xinput_FOUND) target_link_libraries(KWinX11Platform ${X11_Xinput_LIB}) endif() if(HAVE_DL_LIBRARY) target_link_libraries(KWinX11Platform ${DL_LIBRARY}) endif() install( TARGETS KWinX11Platform DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kwin.platforms/ ) diff --git a/plugins/platforms/x11/standalone/sync_filter.cpp b/plugins/platforms/x11/standalone/sync_filter.cpp new file mode 100644 index 000000000..7f2506cc8 --- /dev/null +++ b/plugins/platforms/x11/standalone/sync_filter.cpp @@ -0,0 +1,48 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +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 . +*********************************************************************/ +#include "sync_filter.h" +#include "client.h" +#include "workspace.h" +#include "xcbutils.h" + +namespace KWin +{ + +SyncFilter::SyncFilter() + : X11EventFilter(QVector{Xcb::Extensions::self()->syncAlarmNotifyEvent()}) +{ +} + +bool SyncFilter::event(xcb_generic_event_t *event) +{ + auto e = reinterpret_cast< xcb_sync_alarm_notify_event_t* >(event); + auto client = workspace()->findClient( + [e] (const Client *c) { + const auto syncRequest = c->getSyncRequest(); + return e->alarm == syncRequest.alarm && e->counter_value.hi == syncRequest.value.hi && e->counter_value.lo == syncRequest.value.lo; + } + ); + if (client) { + client->handleSync(); + } + return false; +} + +} diff --git a/plugins/platforms/x11/standalone/sync_filter.h b/plugins/platforms/x11/standalone/sync_filter.h new file mode 100644 index 000000000..5549b3ff3 --- /dev/null +++ b/plugins/platforms/x11/standalone/sync_filter.h @@ -0,0 +1,38 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +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 . +*********************************************************************/ +#ifndef KWIN_SYNC_FILTER_H +#define KWIN_SYNC_FILTER_H +#include "x11eventfilter.h" + +namespace KWin +{ +class X11Cursor; + +class SyncFilter : public X11EventFilter +{ +public: + explicit SyncFilter(); + + bool event(xcb_generic_event_t *event) override; +}; + +} + +#endif diff --git a/plugins/platforms/x11/standalone/x11_platform.cpp b/plugins/platforms/x11/standalone/x11_platform.cpp index 6010339c1..11af70ff1 100644 --- a/plugins/platforms/x11/standalone/x11_platform.cpp +++ b/plugins/platforms/x11/standalone/x11_platform.cpp @@ -1,426 +1,434 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 . *********************************************************************/ #include "x11_platform.h" #include "x11cursor.h" #include "edge.h" +#include "sync_filter.h" #include "windowselector.h" #include #include #if HAVE_EPOXY_GLX #include "glxbackend.h" #endif #if HAVE_X11_XINPUT #include "xinputintegration.h" #endif #include "abstract_client.h" #include "effects_x11.h" #include "eglonxbackend.h" #include "keyboard_input.h" #include "logging.h" #include "screens_xrandr.h" #include "screenedges_filter.h" #include "options.h" #include "overlaywindow_x11.h" #include "non_composited_outline.h" #include "workspace.h" #include "x11_decoration_renderer.h" #include #include #include #include #include #include namespace KWin { X11StandalonePlatform::X11StandalonePlatform(QObject *parent) : Platform(parent) , m_x11Display(QX11Info::display()) { #if HAVE_X11_XINPUT if (!qEnvironmentVariableIsSet("KWIN_NO_XI2")) { m_xinputIntegration = new XInputIntegration(m_x11Display, this); m_xinputIntegration->init(); if (!m_xinputIntegration->hasXinput()) { delete m_xinputIntegration; m_xinputIntegration = nullptr; } else { connect(kwinApp(), &Application::workspaceCreated, m_xinputIntegration, &XInputIntegration::startListening); } } #endif + connect(kwinApp(), &Application::workspaceCreated, this, + [this] { + if (Xcb::Extensions::self()->isSyncAvailable()) { + m_syncFilter = std::make_unique(); + } + } + ); } X11StandalonePlatform::~X11StandalonePlatform() { if (m_openGLFreezeProtectionThread) { m_openGLFreezeProtectionThread->quit(); m_openGLFreezeProtectionThread->wait(); delete m_openGLFreezeProtectionThread; } XRenderUtils::cleanup(); } void X11StandalonePlatform::init() { if (!QX11Info::isPlatformX11()) { emit initFailed(); return; } XRenderUtils::init(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); setReady(true); emit screensQueried(); } Screens *X11StandalonePlatform::createScreens(QObject *parent) { return new XRandRScreens(parent); } OpenGLBackend *X11StandalonePlatform::createOpenGLBackend() { switch (options->glPlatformInterface()) { #if HAVE_EPOXY_GLX case GlxPlatformInterface: if (hasGlx()) { return new GlxBackend(m_x11Display); } else { qCWarning(KWIN_X11STANDALONE) << "Glx not available, trying EGL instead."; // no break, needs fall-through #if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) Q_FALLTHROUGH(); #endif } #endif case EglPlatformInterface: return new EglOnXBackend(m_x11Display); default: // no backend available return nullptr; } } Edge *X11StandalonePlatform::createScreenEdge(ScreenEdges *edges) { if (m_screenEdgesFilter.isNull()) { m_screenEdgesFilter.reset(new ScreenEdgesFilter); } return new WindowBasedEdge(edges); } void X11StandalonePlatform::createPlatformCursor(QObject *parent) { auto c = new X11Cursor(parent, m_xinputIntegration != nullptr); #if HAVE_X11_XINPUT if (m_xinputIntegration) { m_xinputIntegration->setCursor(c); // we know we have xkb already auto xkb = input()->keyboard()->xkb(); xkb->reconfigure(); } #endif } bool X11StandalonePlatform::requiresCompositing() const { return false; } bool X11StandalonePlatform::openGLCompositingIsBroken() const { const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); return KConfigGroup(kwinApp()->config(), "Compositing").readEntry(unsafeKey, false); } QString X11StandalonePlatform::compositingNotPossibleReason() const { // first off, check whether we figured that we'll crash on detection because of a buggy driver KConfigGroup gl_workaround_group(kwinApp()->config(), "Compositing"); const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") && gl_workaround_group.readEntry(unsafeKey, false)) return i18n("OpenGL compositing (the default) has crashed KWin in the past.
" "This was most likely due to a driver bug." "

If you think that you have meanwhile upgraded to a stable driver,
" "you can reset this protection but be aware that this might result in an immediate crash!

" "

Alternatively, you might want to use the XRender backend instead.

"); if (!Xcb::Extensions::self()->isCompositeAvailable() || !Xcb::Extensions::self()->isDamageAvailable()) { return i18n("Required X extensions (XComposite and XDamage) are not available."); } #if !defined( KWIN_HAVE_XRENDER_COMPOSITING ) if (!hasGlx()) return i18n("GLX/OpenGL are not available and only OpenGL support is compiled."); #else if (!(hasGlx() || (Xcb::Extensions::self()->isRenderAvailable() && Xcb::Extensions::self()->isFixesAvailable()))) { return i18n("GLX/OpenGL and XRender/XFixes are not available."); } #endif return QString(); } bool X11StandalonePlatform::compositingPossible() const { // first off, check whether we figured that we'll crash on detection because of a buggy driver KConfigGroup gl_workaround_group(kwinApp()->config(), "Compositing"); const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") && gl_workaround_group.readEntry(unsafeKey, false)) return false; if (!Xcb::Extensions::self()->isCompositeAvailable()) { qCDebug(KWIN_CORE) << "No composite extension available"; return false; } if (!Xcb::Extensions::self()->isDamageAvailable()) { qCDebug(KWIN_CORE) << "No damage extension available"; return false; } if (hasGlx()) return true; #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (Xcb::Extensions::self()->isRenderAvailable() && Xcb::Extensions::self()->isFixesAvailable()) return true; #endif if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) { return true; } else if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) { return true; } qCDebug(KWIN_CORE) << "No OpenGL or XRender/XFixes support"; return false; } bool X11StandalonePlatform::hasGlx() { return Xcb::Extensions::self()->hasGlx(); } void X11StandalonePlatform::createOpenGLSafePoint(OpenGLSafePoint safePoint) { const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); auto group = KConfigGroup(kwinApp()->config(), "Compositing"); switch (safePoint) { case OpenGLSafePoint::PreInit: group.writeEntry(unsafeKey, true); group.sync(); // Deliberately continue with PreFrame #if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) Q_FALLTHROUGH(); #endif case OpenGLSafePoint::PreFrame: if (m_openGLFreezeProtectionThread == nullptr) { Q_ASSERT(m_openGLFreezeProtection == nullptr); m_openGLFreezeProtectionThread = new QThread(this); m_openGLFreezeProtectionThread->setObjectName("FreezeDetector"); m_openGLFreezeProtectionThread->start(); m_openGLFreezeProtection = new QTimer; m_openGLFreezeProtection->setInterval(15000); m_openGLFreezeProtection->setSingleShot(true); m_openGLFreezeProtection->start(); m_openGLFreezeProtection->moveToThread(m_openGLFreezeProtectionThread); connect(m_openGLFreezeProtection, &QTimer::timeout, m_openGLFreezeProtection, [] { const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); auto group = KConfigGroup(kwinApp()->config(), "Compositing"); group.writeEntry(unsafeKey, true); group.sync(); qFatal("Freeze in OpenGL initialization detected"); }, Qt::DirectConnection); } else { Q_ASSERT(m_openGLFreezeProtection); QMetaObject::invokeMethod(m_openGLFreezeProtection, "start", Qt::QueuedConnection); } break; case OpenGLSafePoint::PostInit: group.writeEntry(unsafeKey, false); group.sync(); // Deliberately continue with PostFrame #if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) Q_FALLTHROUGH(); #endif case OpenGLSafePoint::PostFrame: QMetaObject::invokeMethod(m_openGLFreezeProtection, "stop", Qt::QueuedConnection); break; case OpenGLSafePoint::PostLastGuardedFrame: m_openGLFreezeProtection->deleteLater(); m_openGLFreezeProtection = nullptr; m_openGLFreezeProtectionThread->quit(); m_openGLFreezeProtectionThread->wait(); delete m_openGLFreezeProtectionThread; m_openGLFreezeProtectionThread = nullptr; break; } } PlatformCursorImage X11StandalonePlatform::cursorImage() const { auto c = kwinApp()->x11Connection(); QScopedPointer cursor( xcb_xfixes_get_cursor_image_reply(c, xcb_xfixes_get_cursor_image_unchecked(c), nullptr)); if (cursor.isNull()) { return PlatformCursorImage(); } QImage qcursorimg((uchar *) xcb_xfixes_get_cursor_image_cursor_image(cursor.data()), cursor->width, cursor->height, QImage::Format_ARGB32_Premultiplied); // deep copy of image as the data is going to be freed return PlatformCursorImage(qcursorimg.copy(), QPoint(cursor->xhot, cursor->yhot)); } void X11StandalonePlatform::doHideCursor() { xcb_xfixes_hide_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); } void X11StandalonePlatform::doShowCursor() { xcb_xfixes_show_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); } void X11StandalonePlatform::startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName) { if (m_windowSelector.isNull()) { m_windowSelector.reset(new WindowSelector); } m_windowSelector->start(callback, cursorName); } void X11StandalonePlatform::setupActionForGlobalAccel(QAction *action) { connect(action, &QAction::triggered, kwinApp(), [action] { QVariant timestamp = action->property("org.kde.kglobalaccel.activationTimestamp"); bool ok = false; const quint32 t = timestamp.toULongLong(&ok); if (ok) { kwinApp()->setX11Time(t); } }); } OverlayWindow *X11StandalonePlatform::createOverlayWindow() { return new OverlayWindowX11(); } /* Updates xTime(). This used to simply fetch current timestamp from the server, but that can cause xTime() to be newer than timestamp of events that are still in our events queue, thus e.g. making XSetInputFocus() caused by such event to be ignored. Therefore events queue is searched for first event with timestamp, and extra PropertyNotify is generated in order to make sure such event is found. */ void X11StandalonePlatform::updateXTime() { // NOTE: QX11Info::getTimestamp does not yet search the event queue as the old // solution did. This means there might be regressions currently. See the // documentation above on how it should be done properly. kwinApp()->setX11Time(QX11Info::getTimestamp(), Application::TimestampUpdate::Always); } OutlineVisual *X11StandalonePlatform::createOutline(Outline *outline) { // first try composited Outline auto ret = Platform::createOutline(outline); if (!ret) { ret = new NonCompositedOutlineVisual(outline); } return ret; } Decoration::Renderer *X11StandalonePlatform::createDecorationRenderer(Decoration::DecoratedClientImpl *client) { auto renderer = Platform::createDecorationRenderer(client); if (!renderer) { renderer = new Decoration::X11Renderer(client); } return renderer; } void X11StandalonePlatform::invertScreen() { using namespace Xcb::RandR; bool succeeded = false; if (Xcb::Extensions::self()->isRandrAvailable()) { const auto active_client = workspace()->activeClient(); ScreenResources res((active_client && active_client->window() != XCB_WINDOW_NONE) ? active_client->window() : rootWindow()); if (!res.isNull()) { for (int j = 0; j < res->num_crtcs; ++j) { auto crtc = res.crtcs()[j]; CrtcGamma gamma(crtc); if (gamma.isNull()) { continue; } if (gamma->size) { qCDebug(KWIN_CORE) << "inverting screen using xcb_randr_set_crtc_gamma"; const int half = gamma->size / 2 + 1; uint16_t *red = gamma.red(); uint16_t *green = gamma.green(); uint16_t *blue = gamma.blue(); for (int i = 0; i < half; ++i) { auto invert = [&gamma, i](uint16_t *ramp) { qSwap(ramp[i], ramp[gamma->size - 1 - i]); }; invert(red); invert(green); invert(blue); } xcb_randr_set_crtc_gamma(connection(), crtc, gamma->size, red, green, blue); succeeded = true; } } } } if (!succeeded) { Platform::invertScreen(); } } void X11StandalonePlatform::createEffectsHandler(Compositor *compositor, Scene *scene) { new EffectsHandlerImplX11(compositor, scene); } QVector X11StandalonePlatform::supportedCompositors() const { QVector compositors; #if HAVE_EPOXY_GLX compositors << OpenGLCompositing; #endif #ifdef KWIN_HAVE_XRENDER_COMPOSITING compositors << XRenderCompositing; #endif compositors << NoCompositing; return compositors; } } diff --git a/plugins/platforms/x11/standalone/x11_platform.h b/plugins/platforms/x11/standalone/x11_platform.h index 48dc89d22..07d79b889 100644 --- a/plugins/platforms/x11/standalone/x11_platform.h +++ b/plugins/platforms/x11/standalone/x11_platform.h @@ -1,97 +1,101 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 . *********************************************************************/ #ifndef KWIN_X11STANDALONE_PLATFORM_H #define KWIN_X11STANDALONE_PLATFORM_H #include "platform.h" #include #include +#include + namespace KWin { +class SyncFilter; class XInputIntegration; class WindowSelector; class X11EventFilter; class KWIN_EXPORT X11StandalonePlatform : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "x11.json") public: X11StandalonePlatform(QObject *parent = nullptr); virtual ~X11StandalonePlatform(); void init() override; Screens *createScreens(QObject *parent = nullptr) override; OpenGLBackend *createOpenGLBackend() override; Edge *createScreenEdge(ScreenEdges *parent) override; void createPlatformCursor(QObject *parent = nullptr) override; bool requiresCompositing() const override; bool compositingPossible() const override; QString compositingNotPossibleReason() const override; bool openGLCompositingIsBroken() const override; void createOpenGLSafePoint(OpenGLSafePoint safePoint) override; void startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName = QByteArray()) override; PlatformCursorImage cursorImage() const override; void setupActionForGlobalAccel(QAction *action) override; OverlayWindow *createOverlayWindow() override; void updateXTime() override; OutlineVisual *createOutline(Outline *outline) override; Decoration::Renderer *createDecorationRenderer(Decoration::DecoratedClientImpl *client) override; void invertScreen() override; void createEffectsHandler(Compositor *compositor, Scene *scene) override; QVector supportedCompositors() const override; protected: void doHideCursor() override; void doShowCursor() override; private: /** * Tests whether GLX is supported and returns @c true * in case KWin is compiled with OpenGL support and GLX * is available. * * If KWin is compiled with OpenGL ES or without OpenGL at * all, @c false is returned. * @returns @c true if GLX is available, @c false otherwise and if not build with OpenGL support. **/ static bool hasGlx(); XInputIntegration *m_xinputIntegration = nullptr; QThread *m_openGLFreezeProtectionThread = nullptr; QTimer *m_openGLFreezeProtection = nullptr; Display *m_x11Display; QScopedPointer m_windowSelector; QScopedPointer m_screenEdgesFilter; + std::unique_ptr m_syncFilter; }; } #endif