diff --git a/client.cpp b/client.cpp index 0a71adca3..2e19cfeba 100644 --- a/client.cpp +++ b/client.cpp @@ -1,2196 +1,2197 @@ /******************************************************************** 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 #include #include #include #include // XLib #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) - , needsSessionInteract(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(); } checkNonExistentClients(); 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(); checkNonExistentClients(); 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") << caption(false /*full*/) << 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 (needsSessionInteract) { + 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); QWhatsThis::enterWhatsThisMode(); // SELI TODO: ? } } /** * Fetches the window's caption (WM_NAME property). It will be * stored in the client's caption(). */ void Client::fetchName() { setCaption(readName()); } QString Client::readName() const { if (info->name() && info->name()[0] != '\0') return QString::fromUtf8(info->name()).simplified(); else return KWindowSystem::readNameProperty(window(), XCB_ATOM_WM_NAME).simplified(); } // The list is taken from http://www.unicode.org/reports/tr9/ (#154840) static const QChar LRM(0x200E); static const QChar RLM(0x200F); static const QChar LRE(0x202A); static const QChar RLE(0x202B); static const QChar LRO(0x202D); static const QChar RLO(0x202E); static const QChar PDF(0x202C); 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' '); cap_normal = s; if (options->condensedTitle()) { static QScriptEngine engine; static QScriptProgram stripTitle; static QScriptValue script; if (stripTitle.isNull()) { const QString scriptFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(KWIN_NAME "/stripTitle.js")); if (!scriptFile.isEmpty()) { QFile f(scriptFile); if (f.open(QIODevice::ReadOnly|QIODevice::Text)) { f.reset(); stripTitle = QScriptProgram(QString::fromLocal8Bit(f.readAll()), QStringLiteral("stripTitle.js")); f.close(); } } if (stripTitle.isNull()) stripTitle = QScriptProgram(QStringLiteral("(function(title, wm_name, wm_class){ return title ; })"), QStringLiteral("stripTitle.js")); script = engine.evaluate(stripTitle); } QScriptValueList args; args << _s << QString::fromUtf8(resourceName()) << QString::fromUtf8(resourceClass()); s = script.call(QScriptValue(), args).toString(); } if (!force && s == cap_deco) return; cap_deco = s; 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 = !shortcut().isEmpty() ? (QLatin1String(" {") + shortcut().toString() + QLatin1Char('}')) : QString(); cap_suffix = machine_suffix + shortcut_suffix; auto fetchNameInternalPredicate = [this](const Client *cl) { return (!cl->isSpecialWindow() || cl->isToolbar()) && cl != this && cl->caption() == caption(); }; if ((!isSpecialWindow() || isToolbar()) && workspace()->findClient(fetchNameInternalPredicate)) { int i = 2; do { cap_suffix = machine_suffix + QLatin1String(" <") + QString::number(i) + QLatin1Char('>') + LRM; i++; } while (workspace()->findClient(fetchNameInternalPredicate)); 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::evaluateWindowRules() { setupWindowRules(true); applyWindowRules(); } void Client::fetchIconicName() { QString s; if (info->iconName() && info->iconName()[0] != '\0') s = QString::fromUtf8(info->iconName()); else s = KWindowSystem::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(""); } } } /** * \reimp */ QString Client::caption(bool full, bool stripped) const { QString cap = stripped ? cap_deco : cap_normal; if (full) { cap += cap_suffix; if (unresponsive()) { cap += QLatin1String(" "); cap += i18nc("Application is not responding, appended to window title", "(Not Responding)"); } } return cap; } 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() != QuickTileNone) { changedSize = true; setQuickTileMode(QuickTileNone); // 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() { if (!Xcb::Extensions::self()->isSyncAvailable()) 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::setSessionInteract(bool needed) +void Client::setSessionActivityOverride(bool needed) { - needsSessionInteract = 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 = client_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); } } // namespace diff --git a/client.h b/client.h index 4f660ff6e..71d492e0b 100644 --- a/client.h +++ b/client.h @@ -1,767 +1,767 @@ /******************************************************************** 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); const WindowRules* rules() const override; void removeRule(Rules* r); void setupWindowRules(bool ignore_temporary); void applyWindowRules(); 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); 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; const QKeySequence &shortcut() const override; void setShortcut(const QString& cut) 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 caption(bool full = true, bool stripped = false) const override; 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(); - //sets whether the client should be treated as a SessionInteract window - void setSessionInteract(bool needed); + //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(); static void cleanupX11(); public Q_SLOTS: void closeWindow() override; void updateCaption(); void evaluateWindowRules(); 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; private Q_SLOTS: void delayedSetShortcut(); //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 finishWindowRules(); void setShortcutInternal(const QKeySequence &cut = QKeySequence()); 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; WindowRules client_rules; // 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, cap_deco; 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; static bool check_active_modal; ///< \see Client::checkActiveModal() QKeySequence _shortcut; 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 needsSessionInteract; + 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 const WindowRules* Client::rules() const { return &client_rules; } inline xcb_window_t Client::moveResizeGrabWindow() const { return m_moveResizeGrabWindow; } inline const QKeySequence &Client::shortcut() const { return _shortcut; } inline void Client::removeRule(Rules* rule) { client_rules.remove(rule); } 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/manage.cpp b/manage.cpp index b89348851..6ffb2220b 100644 --- a/manage.cpp +++ b/manage.cpp @@ -1,781 +1,791 @@ /******************************************************************** 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 #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "cursor.h" #include "rules.h" #include "group.h" #include "netinfo.h" #include "screens.h" #include "workspace.h" #include "xcbutils.h" #include namespace KWin { /** * Manages the clients. This means handling the very first maprequest: * reparenting, initial geometry, initial state, placement, etc. * Returns false if KWin is not going to manage this window. */ bool Client::manage(xcb_window_t w, bool isMapped) { StackingUpdatesBlocker stacking_blocker(workspace()); Xcb::WindowAttributes attr(w); Xcb::WindowGeometry windowGeometry(w); if (attr.isNull() || windowGeometry.isNull()) { return false; } // From this place on, manage() must not return false blockGeometryUpdates(); setPendingGeometryUpdate(PendingGeometryForced); // Force update when finishing with geometry changes embedClient(w, attr->visual, attr->colormap, windowGeometry->depth); m_visual = attr->visual; bit_depth = windowGeometry->depth; // SELI TODO: Order all these things in some sane manner const NET::Properties properties = NET::WMDesktop | NET::WMState | NET::WMWindowType | NET::WMStrut | NET::WMName | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMIconName; const NET::Properties2 properties2 = NET::WM2BlockCompositing | NET::WM2WindowClass | NET::WM2WindowRole | NET::WM2UserTime | NET::WM2StartupId | NET::WM2ExtendedStrut | NET::WM2Opacity | NET::WM2FullscreenMonitors | NET::WM2FrameOverlap | NET::WM2GroupLeader | NET::WM2Urgency | NET::WM2Input | NET::WM2Protocols | NET::WM2InitialMappingState | NET::WM2IconPixmap | NET::WM2OpaqueRegion | NET::WM2DesktopFileName; auto wmClientLeaderCookie = fetchWmClientLeader(); auto skipCloseAnimationCookie = fetchSkipCloseAnimation(); auto gtkFrameExtentsCookie = fetchGtkFrameExtents(); auto showOnScreenEdgeCookie = fetchShowOnScreenEdge(); auto colorSchemeCookie = fetchColorScheme(); auto firstInTabBoxCookie = fetchFirstInTabBox(); auto transientCookie = fetchTransient(); auto activitiesCookie = fetchActivities(); auto applicationMenuServiceNameCookie = fetchApplicationMenuServiceName(); auto applicationMenuObjectPathCookie = fetchApplicationMenuObjectPath(); m_geometryHints.init(window()); m_motif.init(window()); info = new WinInfo(this, m_client, rootWindow(), properties, properties2); if (isDesktop() && bit_depth == 32) { // force desktop windows to be opaque. It's a desktop after all, there is no window below bit_depth = 24; } // If it's already mapped, ignore hint bool init_minimize = !isMapped && (info->initialMappingState() == NET::Iconic); m_colormap = attr->colormap; getResourceClass(); readWmClientLeader(wmClientLeaderCookie); getWmClientMachine(); getSyncCounter(); // First only read the caption text, so that setupWindowRules() can use it for matching, // and only then really set the caption using setCaption(), which checks for duplicates etc. // and also relies on rules already existing cap_normal = readName(); setupWindowRules(false); setCaption(cap_normal, true); if (Xcb::Extensions::self()->isShapeAvailable()) xcb_shape_select_input(connection(), window(), true); detectShape(window()); readGtkFrameExtents(gtkFrameExtentsCookie); detectNoBorder(); fetchIconicName(); // Needs to be done before readTransient() because of reading the group checkGroup(); updateUrgency(); updateAllowedActions(); // Group affects isMinimizable() setModal((info->state() & NET::Modal) != 0); // Needs to be valid before handling groups readTransientProperty(transientCookie); setDesktopFileName(QByteArray(info->desktopFileName())); getIcons(); connect(this, &Client::desktopFileNameChanged, this, &Client::getIcons); m_geometryHints.read(); getMotifHints(); getWmOpaqueRegion(); readSkipCloseAnimation(skipCloseAnimationCookie); // TODO: Try to obey all state information from info->state() setOriginalSkipTaskbar((info->state() & NET::SkipTaskbar) != 0); setSkipPager((info->state() & NET::SkipPager) != 0); readFirstInTabBox(firstInTabBoxCookie); setupCompositing(); KStartupInfoId asn_id; KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data); // Make sure that the input window is created before we update the stacking order updateInputWindow(); workspace()->updateClientLayer(this); SessionInfo* session = workspace()->takeSessionInfo(this); if (session) { init_minimize = session->minimized; noborder = session->noBorder; } setShortcut(rules()->checkShortcut(session ? session->shortcut : QString(), true)); init_minimize = rules()->checkMinimize(init_minimize, !isMapped); noborder = rules()->checkNoBorder(noborder, !isMapped); readActivities(activitiesCookie); // Initial desktop placement int desk = 0; if (session) { desk = session->desktop; if (session->onAllDesktops) desk = NET::OnAllDesktops; setOnActivities(session->activities); } else { // If this window is transient, ensure that it is opened on the // same window as its parent. this is necessary when an application // starts up on a different desktop than is currently displayed if (isTransient()) { auto mainclients = mainClients(); bool on_current = false; bool on_all = false; AbstractClient* maincl = nullptr; // This is slightly duplicated from Placement::placeOnMainWindow() for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) { if (mainclients.count() > 1 && // A group-transient (*it)->isSpecialWindow() && // Don't consider toolbars etc when placing !(info->state() & NET::Modal)) // except when it's modal (blocks specials as well) continue; maincl = *it; if ((*it)->isOnCurrentDesktop()) on_current = true; if ((*it)->isOnAllDesktops()) on_all = true; } if (on_all) desk = NET::OnAllDesktops; else if (on_current) desk = VirtualDesktopManager::self()->current(); else if (maincl != NULL) desk = maincl->desktop(); if (maincl) setOnActivities(maincl->activities()); } else { // a transient shall appear on its leader and not drag that around if (info->desktop()) desk = info->desktop(); // Window had the initial desktop property, force it if (desktop() == 0 && asn_valid && asn_data.desktop() != 0) desk = asn_data.desktop(); } #ifdef KWIN_BUILD_ACTIVITIES if (Activities::self() && !isMapped && !noborder && isNormalWindow() && !activitiesDefined) { //a new, regular window, when we're not recovering from a crash, //and it hasn't got an activity. let's try giving it the current one. //TODO: decide whether to keep this before the 4.6 release //TODO: if we are keeping it (at least as an option), replace noborder checking //with a public API for setting windows to be on all activities. //something like KWindowSystem::setOnAllActivities or //KActivityConsumer::setOnAllActivities setOnActivity(Activities::self()->current(), true); } #endif } if (desk == 0) // Assume window wants to be visible on the current desktop desk = isDesktop() ? static_cast(NET::OnAllDesktops) : VirtualDesktopManager::self()->current(); desk = rules()->checkDesktop(desk, !isMapped); if (desk != NET::OnAllDesktops) // Do range check desk = qBound(1, desk, static_cast(VirtualDesktopManager::self()->count())); setDesktop(desk); info->setDesktop(desk); workspace()->updateOnAllDesktopsOfTransients(this); // SELI TODO //onAllDesktopsChange(); // Decoration doesn't exist here yet QString activitiesList; activitiesList = rules()->checkActivity(activitiesList, !isMapped); if (!activitiesList.isEmpty()) setOnActivities(activitiesList.split(QStringLiteral(","))); QRect geom(windowGeometry.rect()); bool placementDone = false; if (session) geom = session->geometry; QRect area; bool partial_keep_in_area = isMapped || session; if (isMapped || session) { area = workspace()->clientArea(FullArea, geom.center(), desktop()); checkOffscreenPosition(&geom, area); } else { int screen = asn_data.xinerama() == -1 ? screens()->current() : asn_data.xinerama(); screen = rules()->checkScreen(screen, !isMapped); area = workspace()->clientArea(PlacementArea, screens()->geometry(screen).center(), desktop()); } if (int type = checkFullScreenHack(geom)) { fullscreen_mode = FullScreenHack; if (rules()->checkStrictGeometry(false)) { geom = type == 2 // 1 = It's xinerama-aware fullscreen hack, 2 = It's full area ? workspace()->clientArea(FullArea, geom.center(), desktop()) : workspace()->clientArea(ScreenArea, geom.center(), desktop()); } else geom = workspace()->clientArea(FullScreenArea, geom.center(), desktop()); placementDone = true; } if (isDesktop()) // KWin doesn't manage desktop windows placementDone = true; bool usePosition = false; if (isMapped || session || placementDone) placementDone = true; // Use geometry else if (isTransient() && !isUtility() && !isDialog() && !isSplash()) usePosition = true; else if (isTransient() && !hasNETSupport()) usePosition = true; else if (isDialog() && hasNETSupport()) { // If the dialog is actually non-NETWM transient window, don't try to apply placement to it, // it breaks with too many things (xmms, display) if (mainClients().count() >= 1) { #if 1 // #78082 - Ok, it seems there are after all some cases when an application has a good // reason to specify a position for its dialog. Too bad other WMs have never bothered // with placement for dialogs, so apps always specify positions for their dialogs, // including such silly positions like always centered on the screen or under mouse. // Using ignoring requested position in window-specific settings helps, and now // there's also _NET_WM_FULL_PLACEMENT. usePosition = true; #else ; // Force using placement policy #endif } else usePosition = true; } else if (isSplash()) ; // Force using placement policy else usePosition = true; if (!rules()->checkIgnoreGeometry(!usePosition, true)) { if (m_geometryHints.hasPosition()) { placementDone = true; // Disobey xinerama placement option for now (#70943) area = workspace()->clientArea(PlacementArea, geom.center(), desktop()); } } //if ( true ) // Size is always obeyed for now, only with constraints applied // if (( xSizeHint.flags & USSize ) || ( xSizeHint.flags & PSize )) // { // // Keep in mind that we now actually have a size :-) // } if (m_geometryHints.hasMaxSize()) geom.setSize(geom.size().boundedTo( rules()->checkMaxSize(m_geometryHints.maxSize()))); if (m_geometryHints.hasMinSize()) geom.setSize(geom.size().expandedTo( rules()->checkMinSize(m_geometryHints.minSize()))); if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom())) placementDone = false; // Weird, do not trust. if (placementDone) move(geom.x(), geom.y()); // Before gravitating // Create client group if the window will have a decoration bool dontKeepInArea = false; setTabGroup(NULL); if (!noBorder() && false) { const bool autogrouping = rules()->checkAutogrouping(options->isAutogroupSimilarWindows()); const bool autogroupInFg = rules()->checkAutogroupInForeground(options->isAutogroupInForeground()); // Automatically add to previous groups on session restore if (session && session->tabGroupClient && !workspace()->hasClient(session->tabGroupClient)) session->tabGroupClient = NULL; if (session && session->tabGroupClient && session->tabGroupClient != this) { tabBehind(session->tabGroupClient, autogroupInFg); } else if (isMapped && autogrouping) { // If the window is already mapped (Restarted KWin) add any windows that already have the // same geometry to the same client group. (May incorrectly handle maximized windows) foreach (Client *other, workspace()->clientList()) { if (other->maximizeMode() != MaximizeFull && geom == QRect(other->pos(), other->clientSize()) && desk == other->desktop() && activities() == other->activities()) { tabBehind(other, autogroupInFg); break; } } } if (!(tab_group || isMapped || session)) { // Attempt to automatically group similar windows Client* similar = findAutogroupCandidate(); if (similar && !similar->noBorder()) { if (autogroupInFg) { similar->setDesktop(desk); // can happen when grouping by id. ... similar->setMinimized(false); // ... or anyway - still group, but "here" and visible } if (!similar->isMinimized()) { // do not attempt to tab in background of a hidden group geom = QRect(similar->pos() + similar->clientPos(), similar->clientSize()); updateDecoration(false); if (tabBehind(similar, autogroupInFg)) { // Don't move entire group geom = QRect(similar->pos() + similar->clientPos(), similar->clientSize()); placementDone = true; dontKeepInArea = true; } } } } } readColorScheme(colorSchemeCookie); readApplicationMenuServiceName(applicationMenuServiceNameCookie); readApplicationMenuObjectPath(applicationMenuObjectPathCookie); updateDecoration(false); // Also gravitates // TODO: Is CentralGravity right here, when resizing is done after gravitating? plainResize(rules()->checkSize(sizeForClientSize(geom.size()), !isMapped)); QPoint forced_pos = rules()->checkPosition(invalidPoint, !isMapped); if (forced_pos != invalidPoint) { move(forced_pos); placementDone = true; // Don't keep inside workarea if the window has specially configured position partial_keep_in_area = true; area = workspace()->clientArea(FullArea, geom.center(), desktop()); } if (!placementDone) { // Placement needs to be after setting size Placement::self()->place(this, area); dontKeepInArea = true; placementDone = true; } // bugs #285967, #286146, #183694 // geometry() now includes the requested size and the decoration and is at the correct screen/position (hopefully) // Maximization for oversized windows must happen NOW. // If we effectively pass keepInArea(), the window will resizeWithChecks() - i.e. constrained // to the combo of all screen MINUS all struts on the edges // If only one screen struts, this will affect screens as a side-effect, the window is artificailly shrinked // below the screen size and as result no more maximized what breaks KMainWindow's stupid width+1, height+1 hack // TODO: get KMainWindow a correct state storage what will allow to store the restore size as well. if (!session) { // has a better handling of this geom_restore = geometry(); // Remember restore geometry if (isMaximizable() && (width() >= area.width() || height() >= area.height())) { // Window is too large for the screen, maximize in the // directions necessary const QSize ss = workspace()->clientArea(ScreenArea, area.center(), desktop()).size(); const QRect fsa = workspace()->clientArea(FullArea, geom.center(), desktop()); const QSize cs = clientSize(); int pseudo_max = ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0); if (width() >= area.width()) pseudo_max |= MaximizeHorizontal; if (height() >= area.height()) pseudo_max |= MaximizeVertical; // heuristics: // if decorated client is smaller than the entire screen, the user might want to move it around (multiscreen) // in this case, if the decorated client is bigger than the screen (+1), we don't take this as an // attempt for maximization, but just constrain the size (the window simply wants to be bigger) // NOTICE // i intended a second check on cs < area.size() ("the managed client ("minus border") is smaller // than the workspace") but gtk / gimp seems to store it's size including the decoration, // thus a former maximized window wil become non-maximized bool keepInFsArea = false; if (width() < fsa.width() && (cs.width() > ss.width()+1)) { pseudo_max &= ~MaximizeHorizontal; keepInFsArea = true; } if (height() < fsa.height() && (cs.height() > ss.height()+1)) { pseudo_max &= ~MaximizeVertical; keepInFsArea = true; } if (pseudo_max != MaximizeRestore) { maximize((MaximizeMode)pseudo_max); // from now on, care about maxmode, since the maximization call will override mode for fix aspects dontKeepInArea |= (max_mode == MaximizeFull); geom_restore = QRect(); // Use placement when unmaximizing ... if (!(max_mode & MaximizeVertical)) { geom_restore.setY(y()); // ...but only for horizontal direction geom_restore.setHeight(height()); } if (!(max_mode & MaximizeHorizontal)) { geom_restore.setX(x()); // ...but only for vertical direction geom_restore.setWidth(width()); } } if (keepInFsArea) keepInArea(fsa, partial_keep_in_area); } } if ((!isSpecialWindow() || isToolbar()) && isMovable() && !dontKeepInArea) keepInArea(area, partial_keep_in_area); updateShape(); // CT: Extra check for stupid jdk 1.3.1. But should make sense in general // if client has initial state set to Iconic and is transient with a parent // window that is not Iconic, set init_state to Normal if (init_minimize && isTransient()) { auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) init_minimize = false; // SELI TODO: Even e.g. for NET::Utility? } // If a dialog is shown for minimized window, minimize it too if (!init_minimize && isTransient() && mainClients().count() > 0 && !workspace()->sessionSaving()) { bool visible_parent = false; // Use allMainClients(), to include also main clients of group transients // that have been optimized out in Client::checkGroupTransients() auto mainclients = allMainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) visible_parent = true; if (!visible_parent) { init_minimize = true; demandAttention(); } } if (init_minimize) minimize(true); // No animation // Other settings from the previous session if (session) { // Session restored windows are not considered to be new windows WRT rules, // I.e. obey only forcing rules setKeepAbove(session->keepAbove); setKeepBelow(session->keepBelow); setOriginalSkipTaskbar(session->skipTaskbar); setSkipPager(session->skipPager); setSkipSwitcher(session->skipSwitcher); setShade(session->shaded ? ShadeNormal : ShadeNone); setOpacity(session->opacity); geom_restore = session->restore; if (session->maximized != MaximizeRestore) { maximize(MaximizeMode(session->maximized)); } if (session->fullscreen == FullScreenHack) ; // Nothing, this should be already set again above else if (session->fullscreen != FullScreenNone) { setFullScreen(true, false); geom_fs_restore = session->fsrestore; } checkOffscreenPosition(&geom_restore, area); checkOffscreenPosition(&geom_fs_restore, area); } else { // Window may want to be maximized // done after checking that the window isn't larger than the workarea, so that // the restore geometry from the checks above takes precedence, and window // isn't restored larger than the workarea MaximizeMode maxmode = static_cast( ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0)); MaximizeMode forced_maxmode = rules()->checkMaximize(maxmode, !isMapped); // Either hints were set to maximize, or is forced to maximize, // or is forced to non-maximize and hints were set to maximize if (forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore) maximize(forced_maxmode); // Read other initial states setShade(rules()->checkShade(info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped)); setKeepAbove(rules()->checkKeepAbove(info->state() & NET::KeepAbove, !isMapped)); setKeepBelow(rules()->checkKeepBelow(info->state() & NET::KeepBelow, !isMapped)); setOriginalSkipTaskbar(rules()->checkSkipTaskbar(info->state() & NET::SkipTaskbar, !isMapped)); setSkipPager(rules()->checkSkipPager(info->state() & NET::SkipPager, !isMapped)); setSkipSwitcher(rules()->checkSkipSwitcher(false, !isMapped)); if (info->state() & NET::DemandsAttention) demandAttention(); if (info->state() & NET::Modal) setModal(true); if (fullscreen_mode != FullScreenHack) setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped), false); } updateAllowedActions(true); // Set initial user time directly m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : NULL, asn_valid ? &asn_data : NULL, session); group()->updateUserTime(m_userTime); // And do what Client::updateUserTime() does // This should avoid flicker, because real restacking is done // only after manage() finishes because of blocking, but the window is shown sooner m_frame.lower(); if (session && session->stackingOrder != -1) { sm_stacking_order = session->stackingOrder; workspace()->restoreSessionStackingOrder(this); } if (compositing()) // Sending ConfigureNotify is done when setting mapping state below, // Getting the first sync response means window is ready for compositing sendSyncRequest(); else ready_for_painting = true; // set to true in case compositing is turned on later. bug #160393 if (isShown(true)) { bool allow; if (session) allow = session->active && (!workspace()->wasUserInteraction() || workspace()->activeClient() == NULL || workspace()->activeClient()->isDesktop()); else allow = workspace()->allowClientActivation(this, userTime(), false); // If session saving, force showing new windows (i.e. "save file?" dialogs etc.) // also force if activation is allowed if( !isOnCurrentDesktop() && !isMapped && !session && ( allow || workspace()->sessionSaving() )) VirtualDesktopManager::self()->setCurrent( desktop()); + // If the window is on an inactive activity during session saving, temporarily force it to show. + if( !isMapped && !session && workspace()->sessionSaving() && !isOnCurrentActivity()) { + setSessionActivityOverride( true ); + foreach( AbstractClient* c, mainClients()) { + if (Client *mc = dynamic_cast(c)) { + mc->setSessionActivityOverride(true); + } + } + } + if (isOnCurrentDesktop() && !isMapped && !allow && (!session || session->stackingOrder < 0)) workspace()->restackClientUnderActive(this); updateVisibility(); if (!isMapped) { if (allow && isOnCurrentDesktop()) { if (!isSpecialWindow()) if (options->focusPolicyIsReasonable() && wantsTabFocus()) workspace()->requestFocus(this); } else if (!session && !isSpecialWindow()) demandAttention(); } } else updateVisibility(); assert(mapping_state != Withdrawn); m_managed = true; blockGeometryUpdates(false); if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) { // No known user time, set something old m_userTime = xTime() - 1000000; if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) // Let's be paranoid m_userTime = xTime() - 1000000 + 10; } //sendSyntheticConfigureNotify(); // Done when setting mapping state delete session; client_rules.discardTemporary(); applyWindowRules(); // Just in case RuleBook::self()->discardUsed(this, false); // Remove ApplyNow rules updateWindowRules(Rules::All); // Was blocked while !isManaged() setBlockingCompositing(info->isBlockingCompositing()); readShowOnScreenEdge(showOnScreenEdgeCookie); // TODO: there's a small problem here - isManaged() depends on the mapping state, // but this client is not yet in Workspace's client list at this point, will // be only done in addClient() emit clientManaging(this); return true; } // Called only from manage() void Client::embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth) { assert(m_client == XCB_WINDOW_NONE); assert(frameId() == XCB_WINDOW_NONE); assert(m_wrapper == XCB_WINDOW_NONE); m_client.reset(w, false); const uint32_t zero_value = 0; xcb_connection_t *conn = connection(); // We don't want the window to be destroyed when we quit xcb_change_save_set(conn, XCB_SET_MODE_INSERT, m_client); m_client.selectInput(zero_value); m_client.unmap(); m_client.setBorderWidth(zero_value); // Note: These values must match the order in the xcb_cw_t enum const uint32_t cw_values[] = { 0, // back_pixmap 0, // border_pixel colormap, // colormap Cursor::x11Cursor(Qt::ArrowCursor) }; const uint32_t cw_mask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP | XCB_CW_CURSOR; const uint32_t common_event_mask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_KEYMAP_STATE | XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; const uint32_t frame_event_mask = common_event_mask | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_VISIBILITY_CHANGE; const uint32_t wrapper_event_mask = common_event_mask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; const uint32_t client_event_mask = XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_COLOR_MAP_CHANGE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE; // Create the frame window xcb_window_t frame = xcb_generate_id(conn); xcb_create_window(conn, depth, frame, rootWindow(), 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); m_frame.reset(frame); setWindowHandles(m_client); // Create the wrapper window xcb_window_t wrapperId = xcb_generate_id(conn); xcb_create_window(conn, depth, wrapperId, frame, 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); m_wrapper.reset(wrapperId); m_client.reparent(m_wrapper); // We could specify the event masks when we create the windows, but the original // Xlib code didn't. Let's preserve that behavior here for now so we don't end up // receiving any unexpected events from the wrapper creation or the reparenting. m_frame.selectInput(frame_event_mask); m_wrapper.selectInput(wrapper_event_mask); m_client.selectInput(client_event_mask); updateMouseGrab(); } // To accept "mainwindow#1" to "mainwindow#2" static QByteArray truncatedWindowRole(QByteArray a) { int i = a.indexOf('#'); if (i == -1) return a; QByteArray b(a); b.truncate(i); return b; } Client* Client::findAutogroupCandidate() const { // Attempt to find a similar window to the input. If we find multiple possibilities that are in // different groups then ignore all of them. This function is for automatic window grouping. Client *found = NULL; // See if the window has a group ID to match with QString wGId = rules()->checkAutogroupById(QString()); if (!wGId.isEmpty()) { foreach (Client *c, workspace()->clientList()) { if (activities() != c->activities()) continue; // don't cross activities if (wGId == c->rules()->checkAutogroupById(QString())) { if (found && found->tabGroup() != c->tabGroup()) { // We've found two, ignore both found = NULL; break; // Continue to the next test } found = c; } } if (found) return found; } // If this is a transient window don't take a guess if (isTransient()) return NULL; // If we don't have an ID take a guess if (rules()->checkAutogrouping(options->isAutogroupSimilarWindows())) { QByteArray wRole = truncatedWindowRole(windowRole()); foreach (Client *c, workspace()->clientList()) { if (desktop() != c->desktop() || activities() != c->activities()) continue; QByteArray wRoleB = truncatedWindowRole(c->windowRole()); if (resourceClass() == c->resourceClass() && // Same resource class wRole == wRoleB && // Same window role c->isNormalWindow()) { // Normal window TODO: Can modal windows be "normal"? if (found && found->tabGroup() != c->tabGroup()) // We've found two, ignore both return NULL; found = c; } } } return found; } } // namespace diff --git a/sm.cpp b/sm.cpp index e7c31a7cc..16b95f034 100644 --- a/sm.cpp +++ b/sm.cpp @@ -1,529 +1,528 @@ /******************************************************************** 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 . *********************************************************************/ #include "sm.h" #include #include #include #include #include #include "workspace.h" #include "client.h" #include #include #include #include namespace KWin { static bool gs_sessionManagerIsKSMServer = false; static KConfig *sessionConfig(QString id, QString key) { static KConfig *config = nullptr; static QString lastId; static QString lastKey; static QString pattern = QString(QLatin1String("session/%1_%2_%3")).arg(qApp->applicationName()); if (id != lastId || key != lastKey) { delete config; config = nullptr; } lastId = id; lastKey = key; if (!config) { config = new KConfig(pattern.arg(id).arg(key), KConfig::SimpleConfig); } return config; } void Workspace::saveState(QSessionManager &sm) { // If the session manager is ksmserver, save stacking // order, active window, active desktop etc. in phase 1, // as ksmserver assures no interaction will be done // before the WM finishes phase 1. Saving in phase 2 is // too late, as possible user interaction may change some things. // Phase2 is still needed though (ICCCM 5.2) KConfig *config = sessionConfig(sm.sessionId(), sm.sessionKey()); if (!sm.isPhase2()) { KConfigGroup cg(config, "Session"); cg.writeEntry("AllowsInteraction", sm.allowsInteraction()); sessionSaveStarted(); if (gs_sessionManagerIsKSMServer) // save stacking order etc. before "save file?" etc. dialogs change it storeSession(config, SMSavePhase0); config->markAsClean(); // don't write Phase #1 data to disk sm.release(); // Qt doesn't automatically release in this case (bug?) sm.requestPhase2(); return; } storeSession(config, gs_sessionManagerIsKSMServer ? SMSavePhase2 : SMSavePhase2Full); config->sync(); // inform the smserver on how to clean-up after us const QString localFilePath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + config->name(); if (QFile::exists(localFilePath)) { // expectable for the sync sm.setDiscardCommand(QStringList() << QStringLiteral("rm") << localFilePath); } } // I bet this is broken, just like everywhere else in KDE void Workspace::commitData(QSessionManager &sm) { if (!sm.isPhase2()) sessionSaveStarted(); } // Workspace /*! Stores the current session in the config file \sa loadSessionInfo() */ void Workspace::storeSession(KConfig* config, SMSavePhase phase) { KConfigGroup cg(config, "Session"); int count = 0; int active_client = -1; for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) { Client* c = (*it); QByteArray sessionId = c->sessionId(); QByteArray wmCommand = c->wmCommand(); if (sessionId.isEmpty()) // remember also applications that are not XSMP capable // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF if (wmCommand.isEmpty()) continue; count++; if (c->isActive()) active_client = count; if (phase == SMSavePhase2 || phase == SMSavePhase2Full) storeClient(cg, count, c); } if (phase == SMSavePhase0) { // it would be much simpler to save these values to the config file, // but both Qt and KDE treat phase1 and phase2 separately, // which results in different sessionkey and different config file :( session_active_client = active_client; session_desktop = VirtualDesktopManager::self()->current(); } else if (phase == SMSavePhase2) { cg.writeEntry("count", count); cg.writeEntry("active", session_active_client); cg.writeEntry("desktop", session_desktop); } else { // SMSavePhase2Full cg.writeEntry("count", count); cg.writeEntry("active", session_active_client); cg.writeEntry("desktop", VirtualDesktopManager::self()->current()); } } void Workspace::storeClient(KConfigGroup &cg, int num, Client *c) { - c->setSessionInteract(false); //make sure we get the real values + c->setSessionActivityOverride(false); //make sure we get the real values QString n = QString::number(num); cg.writeEntry(QLatin1String("sessionId") + n, c->sessionId().constData()); cg.writeEntry(QLatin1String("windowRole") + n, c->windowRole().constData()); cg.writeEntry(QLatin1String("wmCommand") + n, c->wmCommand().constData()); cg.writeEntry(QLatin1String("resourceName") + n, c->resourceName().constData()); cg.writeEntry(QLatin1String("resourceClass") + n, c->resourceClass().constData()); cg.writeEntry(QLatin1String("geometry") + n, QRect(c->calculateGravitation(true), c->clientSize())); // FRAME cg.writeEntry(QLatin1String("restore") + n, c->geometryRestore()); cg.writeEntry(QLatin1String("fsrestore") + n, c->geometryFSRestore()); cg.writeEntry(QLatin1String("maximize") + n, (int) c->maximizeMode()); cg.writeEntry(QLatin1String("fullscreen") + n, (int) c->fullScreenMode()); cg.writeEntry(QLatin1String("desktop") + n, c->desktop()); // the config entry is called "iconified" for back. comp. reasons // (kconf_update script for updating session files would be too complicated) cg.writeEntry(QLatin1String("iconified") + n, c->isMinimized()); cg.writeEntry(QLatin1String("opacity") + n, c->opacity()); // the config entry is called "sticky" for back. comp. reasons cg.writeEntry(QLatin1String("sticky") + n, c->isOnAllDesktops()); cg.writeEntry(QLatin1String("shaded") + n, c->isShade()); // the config entry is called "staysOnTop" for back. comp. reasons cg.writeEntry(QLatin1String("staysOnTop") + n, c->keepAbove()); cg.writeEntry(QLatin1String("keepBelow") + n, c->keepBelow()); cg.writeEntry(QLatin1String("skipTaskbar") + n, c->originalSkipTaskbar()); cg.writeEntry(QLatin1String("skipPager") + n, c->skipPager()); cg.writeEntry(QLatin1String("skipSwitcher") + n, c->skipSwitcher()); // not really just set by user, but name kept for back. comp. reasons cg.writeEntry(QLatin1String("userNoBorder") + n, c->noBorder()); cg.writeEntry(QLatin1String("windowType") + n, windowTypeToTxt(c->windowType())); cg.writeEntry(QLatin1String("shortcut") + n, c->shortcut().toString()); cg.writeEntry(QLatin1String("stackingOrder") + n, unconstrained_stacking_order.indexOf(c)); // KConfig doesn't support long so we need to live with less precision on 64-bit systems cg.writeEntry(QLatin1String("tabGroup") + n, static_cast(reinterpret_cast(c->tabGroup()))); cg.writeEntry(QLatin1String("activities") + n, c->activities()); } void Workspace::storeSubSession(const QString &name, QSet sessionIds) { //TODO clear it first KConfigGroup cg(KSharedConfig::openConfig(), QLatin1String("SubSession: ") + name); int count = 0; int active_client = -1; for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) { Client* c = (*it); QByteArray sessionId = c->sessionId(); QByteArray wmCommand = c->wmCommand(); if (sessionId.isEmpty()) // remember also applications that are not XSMP capable // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF if (wmCommand.isEmpty()) continue; if (!sessionIds.contains(sessionId)) continue; qCDebug(KWIN_CORE) << "storing" << sessionId; count++; if (c->isActive()) active_client = count; storeClient(cg, count, c); } cg.writeEntry("count", count); cg.writeEntry("active", active_client); //cg.writeEntry( "desktop", currentDesktop()); } /*! Loads the session information from the config file. \sa storeSession() */ void Workspace::loadSessionInfo(const QString &key) { // NOTICE: qApp->sessionKey() is outdated when this gets invoked // the key parameter is cached from the application constructor. session.clear(); KConfigGroup cg(sessionConfig(qApp->sessionId(), key), "Session"); addSessionInfo(cg); } void Workspace::addSessionInfo(KConfigGroup &cg) { m_initialDesktop = cg.readEntry("desktop", 1); int count = cg.readEntry("count", 0); int active_client = cg.readEntry("active", 0); for (int i = 1; i <= count; i++) { QString n = QString::number(i); SessionInfo* info = new SessionInfo; session.append(info); info->sessionId = cg.readEntry(QLatin1String("sessionId") + n, QString()).toLatin1(); info->windowRole = cg.readEntry(QLatin1String("windowRole") + n, QString()).toLatin1(); info->wmCommand = cg.readEntry(QLatin1String("wmCommand") + n, QString()).toLatin1(); info->resourceName = cg.readEntry(QLatin1String("resourceName") + n, QString()).toLatin1(); info->resourceClass = cg.readEntry(QLatin1String("resourceClass") + n, QString()).toLower().toLatin1(); info->geometry = cg.readEntry(QLatin1String("geometry") + n, QRect()); info->restore = cg.readEntry(QLatin1String("restore") + n, QRect()); info->fsrestore = cg.readEntry(QLatin1String("fsrestore") + n, QRect()); info->maximized = cg.readEntry(QLatin1String("maximize") + n, 0); info->fullscreen = cg.readEntry(QLatin1String("fullscreen") + n, 0); info->desktop = cg.readEntry(QLatin1String("desktop") + n, 0); info->minimized = cg.readEntry(QLatin1String("iconified") + n, false); info->opacity = cg.readEntry(QLatin1String("opacity") + n, 1.0); info->onAllDesktops = cg.readEntry(QLatin1String("sticky") + n, false); info->shaded = cg.readEntry(QLatin1String("shaded") + n, false); info->keepAbove = cg.readEntry(QLatin1String("staysOnTop") + n, false); info->keepBelow = cg.readEntry(QLatin1String("keepBelow") + n, false); info->skipTaskbar = cg.readEntry(QLatin1String("skipTaskbar") + n, false); info->skipPager = cg.readEntry(QLatin1String("skipPager") + n, false); info->skipSwitcher = cg.readEntry(QLatin1String("skipSwitcher") + n, false); info->noBorder = cg.readEntry(QLatin1String("userNoBorder") + n, false); info->windowType = txtToWindowType(cg.readEntry(QLatin1String("windowType") + n, QString()).toLatin1().constData()); info->shortcut = cg.readEntry(QLatin1String("shortcut") + n, QString()); info->active = (active_client == i); info->stackingOrder = cg.readEntry(QLatin1String("stackingOrder") + n, -1); info->tabGroup = cg.readEntry(QLatin1String("tabGroup") + n, 0); info->tabGroupClient = NULL; info->activities = cg.readEntry(QLatin1String("activities") + n, QStringList()); } } void Workspace::loadSubSessionInfo(const QString &name) { KConfigGroup cg(KSharedConfig::openConfig(), QLatin1String("SubSession: ") + name); addSessionInfo(cg); } /*! Returns a SessionInfo for client \a c. The returned session info is removed from the storage. It's up to the caller to delete it. This function is called when a new window is mapped and must be managed. We try to find a matching entry in the session. May return 0 if there's no session info for the client. */ SessionInfo* Workspace::takeSessionInfo(Client* c) { SessionInfo *realInfo = 0; QByteArray sessionId = c->sessionId(); QByteArray windowRole = c->windowRole(); QByteArray wmCommand = c->wmCommand(); QByteArray resourceName = c->resourceName(); QByteArray resourceClass = c->resourceClass(); // First search ``session'' if (! sessionId.isEmpty()) { // look for a real session managed client (algorithm suggested by ICCCM) foreach (SessionInfo * info, session) { if (realInfo) break; if (info->sessionId == sessionId && sessionInfoWindowTypeMatch(c, info)) { if (! windowRole.isEmpty()) { if (info->windowRole == windowRole) { realInfo = info; session.removeAll(info); } } else { if (info->windowRole.isEmpty() && info->resourceName == resourceName && info->resourceClass == resourceClass) { realInfo = info; session.removeAll(info); } } } } } else { // look for a sessioninfo with matching features. foreach (SessionInfo * info, session) { if (realInfo) break; if (info->resourceName == resourceName && info->resourceClass == resourceClass && sessionInfoWindowTypeMatch(c, info)) { if (wmCommand.isEmpty() || info->wmCommand == wmCommand) { realInfo = info; session.removeAll(info); } } } } // Set tabGroupClient for other clients in the same group if (realInfo && realInfo->tabGroup) { foreach (SessionInfo * info, session) { if (!info->tabGroupClient && info->tabGroup == realInfo->tabGroup) info->tabGroupClient = c; } } return realInfo; } bool Workspace::sessionInfoWindowTypeMatch(Client* c, SessionInfo* info) { if (info->windowType == -2) { // undefined (not really part of NET::WindowType) return !c->isSpecialWindow(); } return info->windowType == c->windowType(); } static const char* const window_type_names[] = { "Unknown", "Normal" , "Desktop", "Dock", "Toolbar", "Menu", "Dialog", "Override", "TopMenu", "Utility", "Splash" }; // change also the two functions below when adding new entries const char* Workspace::windowTypeToTxt(NET::WindowType type) { if (type >= NET::Unknown && type <= NET::Splash) return window_type_names[ type + 1 ]; // +1 (unknown==-1) if (type == -2) // undefined (not really part of NET::WindowType) return "Undefined"; qFatal("Unknown Window Type"); return NULL; } NET::WindowType Workspace::txtToWindowType(const char* txt) { for (int i = NET::Unknown; i <= NET::Splash; ++i) if (qstrcmp(txt, window_type_names[ i + 1 ]) == 0) // +1 return static_cast< NET::WindowType >(i); return static_cast< NET::WindowType >(-2); // undefined } // KWin's focus stealing prevention causes problems with user interaction // during session save, as it prevents possible dialogs from getting focus. // Therefore it's temporarily disabled during session saving. Start of // session saving can be detected in SessionManager::saveState() above, // but Qt doesn't have API for saying when session saved finished (either // successfully, or was canceled). Therefore, create another connection // to session manager, that will provide this information. // Similarly the remember feature of window-specific settings should be disabled // during KDE shutdown when windows may move e.g. because of Kicker going away // (struts changing). When session saving starts, it can be cancelled, in which // case the shutdown_cancelled callback is invoked, or it's a checkpoint that // is immediatelly followed by save_complete, or finally it's a shutdown that // is immediatelly followed by die callback. So getting save_yourself with shutdown // set disables window-specific settings remembering, getting shutdown_cancelled // re-enables, otherwise KWin will go away after die. static void save_yourself(SmcConn conn_P, SmPointer ptr, int, Bool shutdown, int, Bool) { SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); if (conn_P != session->connection()) return; if (shutdown) RuleBook::self()->setUpdatesDisabled(true); SmcSaveYourselfDone(conn_P, True); } static void die(SmcConn conn_P, SmPointer ptr) { SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); if (conn_P != session->connection()) return; // session->saveDone(); we will quit anyway session->close(); } static void save_complete(SmcConn conn_P, SmPointer ptr) { SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); if (conn_P != session->connection()) return; session->saveDone(); } static void shutdown_cancelled(SmcConn conn_P, SmPointer ptr) { SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); if (conn_P != session->connection()) return; RuleBook::self()->setUpdatesDisabled(false); // re-enable // no need to differentiate between successful finish and cancel session->saveDone(); } void SessionSaveDoneHelper::saveDone() { if (Workspace::self()) Workspace::self()->sessionSaveDone(); } SessionSaveDoneHelper::SessionSaveDoneHelper() { SmcCallbacks calls; calls.save_yourself.callback = save_yourself; calls.save_yourself.client_data = reinterpret_cast< SmPointer >(this); calls.die.callback = die; calls.die.client_data = reinterpret_cast< SmPointer >(this); calls.save_complete.callback = save_complete; calls.save_complete.client_data = reinterpret_cast< SmPointer >(this); calls.shutdown_cancelled.callback = shutdown_cancelled; calls.shutdown_cancelled.client_data = reinterpret_cast< SmPointer >(this); char* id = NULL; char err[ 11 ]; conn = SmcOpenConnection(NULL, 0, 1, 0, SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask, &calls, NULL, &id, 10, err); if (id != NULL) free(id); if (conn == NULL) return; // no SM // detect ksmserver char* vendor = SmcVendor(conn); gs_sessionManagerIsKSMServer = qstrcmp(vendor, "KDE") == 0; free(vendor); // set the required properties, mostly dummy values SmPropValue propvalue[ 5 ]; SmProp props[ 5 ]; propvalue[ 0 ].length = sizeof(unsigned char); unsigned char value0 = SmRestartNever; // so that this extra SM connection doesn't interfere propvalue[ 0 ].value = &value0; props[ 0 ].name = const_cast< char* >(SmRestartStyleHint); props[ 0 ].type = const_cast< char* >(SmCARD8); props[ 0 ].num_vals = 1; props[ 0 ].vals = &propvalue[ 0 ]; struct passwd* entry = getpwuid(geteuid()); propvalue[ 1 ].length = entry != NULL ? strlen(entry->pw_name) : 0; propvalue[ 1 ].value = (SmPointer)(entry != NULL ? entry->pw_name : ""); props[ 1 ].name = const_cast< char* >(SmUserID); props[ 1 ].type = const_cast< char* >(SmARRAY8); props[ 1 ].num_vals = 1; props[ 1 ].vals = &propvalue[ 1 ]; propvalue[ 2 ].length = 0; propvalue[ 2 ].value = (SmPointer)(""); props[ 2 ].name = const_cast< char* >(SmRestartCommand); props[ 2 ].type = const_cast< char* >(SmLISTofARRAY8); props[ 2 ].num_vals = 1; props[ 2 ].vals = &propvalue[ 2 ]; propvalue[ 3 ].length = strlen("kwinsmhelper"); propvalue[ 3 ].value = (SmPointer)"kwinsmhelper"; props[ 3 ].name = const_cast< char* >(SmProgram); props[ 3 ].type = const_cast< char* >(SmARRAY8); props[ 3 ].num_vals = 1; props[ 3 ].vals = &propvalue[ 3 ]; propvalue[ 4 ].length = 0; propvalue[ 4 ].value = (SmPointer)(""); props[ 4 ].name = const_cast< char* >(SmCloneCommand); props[ 4 ].type = const_cast< char* >(SmLISTofARRAY8); props[ 4 ].num_vals = 1; props[ 4 ].vals = &propvalue[ 4 ]; SmProp* p[ 5 ] = { &props[ 0 ], &props[ 1 ], &props[ 2 ], &props[ 3 ], &props[ 4 ] }; SmcSetProperties(conn, 5, p); notifier = new QSocketNotifier(IceConnectionNumber(SmcGetIceConnection(conn)), QSocketNotifier::Read, this); connect(notifier, SIGNAL(activated(int)), SLOT(processData())); } SessionSaveDoneHelper::~SessionSaveDoneHelper() { close(); } void SessionSaveDoneHelper::close() { if (conn != NULL) { delete notifier; SmcCloseConnection(conn, 0, NULL); } conn = NULL; } void SessionSaveDoneHelper::processData() { if (conn != NULL) IceProcessMessages(SmcGetIceConnection(conn), 0, 0); } void Workspace::sessionSaveDone() { session_saving = false; - //remove sessionInteract flag from all clients foreach (Client * c, clients) { - c->setSessionInteract(false); + c->setSessionActivityOverride(false); } } } // namespace