/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2015 Martin Gräßlin SPDX-FileCopyrightText: 2019 Vlad Zahorodnii SPDX-License-Identifier: GPL-2.0-or-later */ #include "window.h" #include "output.h" #if KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "appmenu.h" #include "atoms.h" #include "client_machine.h" #include "composite.h" #include "decorations/decoratedclient.h" #include "decorations/decorationbridge.h" #include "decorations/decorationpalette.h" #include "effects.h" #include "focuschain.h" #include "outline.h" #include "platform.h" #include "screens.h" #if KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "screenedge.h" #include "shadow.h" #include "shadowitem.h" #include "surfaceitem_x11.h" #include "useractions.h" #include "virtualdesktops.h" #include "wayland/output_interface.h" #include "wayland/plasmawindowmanagement_interface.h" #include "wayland/surface_interface.h" #include "wayland_server.h" #include "windowitem.h" #include "workspace.h" #include #include #include #include #include #include #include namespace KWin { static inline int sign(int v) { return (v > 0) - (v < 0); } QHash> Window::s_palettes; std::shared_ptr Window::s_defaultPalette; Window::Window() : m_output(workspace()->activeOutput()) , m_visual(XCB_NONE) , bit_depth(24) , info(nullptr) , ready_for_painting(false) , m_internalId(QUuid::createUuid()) , m_client() , is_shape(false) , m_effectWindow(nullptr) , m_clientMachine(new ClientMachine(this)) , m_wmClientLeader(XCB_WINDOW_NONE) , m_skipCloseAnimation(false) #if KWIN_BUILD_TABBOX , m_tabBoxClient(QSharedPointer(new TabBox::TabBoxClientImpl(this))) #endif , m_colorScheme(QStringLiteral("kdeglobals")) { connect(screens(), &Screens::changed, this, &Window::screenChanged); connect(this, &Window::bufferGeometryChanged, this, &Window::inputTransformationChanged); // Only for compatibility reasons, drop in the next major release. connect(this, &Window::frameGeometryChanged, this, &Window::geometryChanged); connect(this, &Window::geometryShapeChanged, this, &Window::discardShapeRegion); connect(this, &Window::clientStartUserMovedResized, this, &Window::moveResizedChanged); connect(this, &Window::clientFinishUserMovedResized, this, &Window::moveResizedChanged); connect(this, &Window::windowShown, this, &Window::hiddenChanged); connect(this, &Window::windowHidden, this, &Window::hiddenChanged); connect(this, &Window::paletteChanged, this, &Window::triggerDecorationRepaint); // If the user manually moved the window, don't restore it after the keyboard closes connect(this, &Window::clientFinishUserMovedResized, this, [this]() { m_keyboardGeometryRestore = QRect(); }); connect(this, qOverload(&Window::clientMaximizedStateChanged), this, [this]() { m_keyboardGeometryRestore = QRect(); }); connect(this, &Window::fullScreenChanged, this, [this]() { m_keyboardGeometryRestore = QRect(); }); // replace on-screen-display on size changes connect(this, &Window::frameGeometryChanged, this, [this](Window *c, const QRect &old) { Q_UNUSED(c) if (isOnScreenDisplay() && !frameGeometry().isEmpty() && old.size() != frameGeometry().size() && isPlaceable()) { GeometryUpdatesBlocker blocker(this); Placement::self()->place(this, workspace()->clientArea(PlacementArea, this, workspace()->activeOutput())); } }); connect(ApplicationMenu::self(), &ApplicationMenu::applicationMenuEnabledChanged, this, [this] { Q_EMIT hasApplicationMenuChanged(hasApplicationMenu()); }); } Window::~Window() { Q_ASSERT(m_blockGeometryUpdates == 0); Q_ASSERT(m_decoration.decoration == nullptr); delete info; } QDebug operator<<(QDebug debug, const Window *toplevel) { QDebugStateSaver saver(debug); debug.nospace(); if (toplevel) { debug << toplevel->metaObject()->className() << '(' << static_cast(toplevel); if (toplevel->window()) { debug << ", windowId=0x" << Qt::hex << toplevel->window() << Qt::dec; } if (const KWaylandServer::SurfaceInterface *surface = toplevel->surface()) { debug << ", surface=" << surface; } if (toplevel->isClient()) { if (!toplevel->isPopupWindow()) { debug << ", caption=" << toplevel->caption(); } if (toplevel->transientFor()) { debug << ", transientFor=" << toplevel->transientFor(); } } if (debug.verbosity() > 2) { debug << ", frameGeometry=" << toplevel->frameGeometry(); debug << ", resourceName=" << toplevel->resourceName(); debug << ", resourceClass=" << toplevel->resourceClass(); } debug << ')'; } else { debug << "Window(0x0)"; } return debug; } void Window::detectShape(xcb_window_t id) { const bool wasShape = is_shape; is_shape = Xcb::Extensions::self()->hasShape(id); if (wasShape != is_shape) { Q_EMIT shapedChanged(); } } // used only by Deleted::copy() void Window::copyToDeleted(Window *c) { m_internalId = c->internalId(); m_bufferGeometry = c->m_bufferGeometry; m_frameGeometry = c->m_frameGeometry; m_clientGeometry = c->m_clientGeometry; m_visual = c->m_visual; bit_depth = c->bit_depth; info = c->info; m_client.reset(c->m_client, false); ready_for_painting = c->ready_for_painting; is_shape = c->is_shape; m_effectWindow = c->m_effectWindow; if (m_effectWindow != nullptr) { m_effectWindow->setWindow(this); } m_sceneWindow = c->m_sceneWindow; if (m_sceneWindow != nullptr) { m_sceneWindow->setToplevel(this); } m_shadow = c->m_shadow; if (m_shadow) { m_shadow->setToplevel(this); } resource_name = c->resourceName(); resource_class = c->resourceClass(); m_clientMachine = c->m_clientMachine; m_clientMachine->setParent(this); m_wmClientLeader = c->wmClientLeader(); opaque_region = c->opaqueRegion(); m_output = c->m_output; m_skipCloseAnimation = c->m_skipCloseAnimation; m_internalFBO = c->m_internalFBO; m_internalImage = c->m_internalImage; m_opacity = c->m_opacity; m_shapeRegionIsValid = c->m_shapeRegionIsValid; m_shapeRegion = c->m_shapeRegion; m_stackingOrder = c->m_stackingOrder; } // before being deleted, remove references to everything that's now // owner by Deleted void Window::disownDataPassedToDeleted() { info = nullptr; } QRect Window::visibleGeometry() const { if (const WindowItem *item = windowItem()) { return item->mapToGlobal(item->boundingRect()); } return QRect(); } Xcb::Property Window::fetchWmClientLeader() const { return Xcb::Property(false, window(), atoms->wm_client_leader, XCB_ATOM_WINDOW, 0, 10000); } void Window::readWmClientLeader(Xcb::Property &prop) { m_wmClientLeader = prop.value(window()); } void Window::getWmClientLeader() { auto prop = fetchWmClientLeader(); readWmClientLeader(prop); } /** * Returns sessionId for this window, * taken either from its window or from the leader window. */ QByteArray Window::sessionId() const { QByteArray result = Xcb::StringProperty(window(), atoms->sm_client_id); if (result.isEmpty() && m_wmClientLeader && m_wmClientLeader != window()) { result = Xcb::StringProperty(m_wmClientLeader, atoms->sm_client_id); } return result; } /** * Returns command property for this window, * taken either from its window or from the leader window. */ QByteArray Window::wmCommand() { QByteArray result = Xcb::StringProperty(window(), XCB_ATOM_WM_COMMAND); if (result.isEmpty() && m_wmClientLeader && m_wmClientLeader != window()) { result = Xcb::StringProperty(m_wmClientLeader, XCB_ATOM_WM_COMMAND); } result.replace(0, ' '); return result; } void Window::getWmClientMachine() { m_clientMachine->resolve(window(), wmClientLeader()); } /** * Returns client machine for this window, * taken either from its window or from the leader window. */ QByteArray Window::wmClientMachine(bool use_localhost) const { if (!m_clientMachine) { // this should never happen return QByteArray(); } if (use_localhost && m_clientMachine->isLocal()) { // special name for the local machine (localhost) return ClientMachine::localhost(); } return m_clientMachine->hostName(); } /** * Returns client leader window for this client. * Returns the client window itself if no leader window is defined. */ xcb_window_t Window::wmClientLeader() const { if (m_wmClientLeader != XCB_WINDOW_NONE) { return m_wmClientLeader; } return window(); } void Window::getResourceClass() { if (!info) { return; } setResourceClass(QByteArray(info->windowClassName()).toLower(), QByteArray(info->windowClassClass()).toLower()); } void Window::setResourceClass(const QByteArray &name, const QByteArray &className) { resource_name = name; resource_class = className; Q_EMIT windowClassChanged(); } bool Window::resourceMatch(const Window *c1, const Window *c2) { return c1->resourceClass() == c2->resourceClass(); } qreal Window::opacity() const { return m_opacity; } void Window::setOpacity(qreal opacity) { opacity = qBound(0.0, opacity, 1.0); if (m_opacity == opacity) { return; } const qreal oldOpacity = m_opacity; m_opacity = opacity; if (Compositor::compositing()) { addRepaintFull(); Q_EMIT opacityChanged(this, oldOpacity); } } bool Window::setupCompositing() { if (!Compositor::compositing()) { return false; } m_effectWindow = new EffectWindowImpl(this); updateShadow(); m_sceneWindow = Compositor::self()->scene()->createWindow(this); m_effectWindow->setSceneWindow(m_sceneWindow); connect(windowItem(), &WindowItem::positionChanged, this, &Window::visibleGeometryChanged); connect(windowItem(), &WindowItem::boundingRectChanged, this, &Window::visibleGeometryChanged); return true; } void Window::finishCompositing(ReleaseReason releaseReason) { // If the X11 window has been destroyed, avoid calling XDamageDestroy. if (releaseReason != ReleaseReason::Destroyed) { if (SurfaceItemX11 *item = qobject_cast(surfaceItem())) { item->destroyDamage(); } } if (m_shadow && m_shadow->toplevel() == this) { // otherwise it's already passed to Deleted, don't free data deleteShadow(); } if (m_effectWindow && m_effectWindow->window() == this) { // otherwise it's already passed to Deleted, don't free data deleteEffectWindow(); } if (m_sceneWindow && m_sceneWindow->window() == this) { // otherwise it's already passed to Deleted, don't free data deleteSceneWindow(); } } void Window::addRepaint(const QRect &rect) { addRepaint(QRegion(rect)); } void Window::addRepaint(int x, int y, int width, int height) { addRepaint(QRegion(x, y, width, height)); } void Window::addRepaint(const QRegion ®ion) { if (auto item = windowItem()) { item->scheduleRepaint(region); } } void Window::addLayerRepaint(const QRect &rect) { addLayerRepaint(QRegion(rect)); } void Window::addLayerRepaint(int x, int y, int width, int height) { addLayerRepaint(QRegion(x, y, width, height)); } void Window::addLayerRepaint(const QRegion ®ion) { addRepaint(region.translated(-pos())); } void Window::addRepaintFull() { addLayerRepaint(visibleGeometry()); } void Window::addWorkspaceRepaint(int x, int y, int w, int h) { addWorkspaceRepaint(QRect(x, y, w, h)); } void Window::addWorkspaceRepaint(const QRect &r2) { if (Compositor::compositing()) { Compositor::self()->scene()->addRepaint(r2); } } void Window::addWorkspaceRepaint(const QRegion ®ion) { if (Compositor::compositing()) { Compositor::self()->scene()->addRepaint(region); } } void Window::setReadyForPainting() { if (!ready_for_painting) { ready_for_painting = true; if (Compositor::compositing()) { addRepaintFull(); Q_EMIT windowShown(this); } } } void Window::deleteShadow() { delete m_shadow; m_shadow = nullptr; } void Window::deleteEffectWindow() { delete m_effectWindow; m_effectWindow = nullptr; } void Window::deleteSceneWindow() { delete m_sceneWindow; m_sceneWindow = nullptr; } int Window::screen() const { return kwinApp()->platform()->enabledOutputs().indexOf(m_output); } Output *Window::output() const { return m_output; } void Window::setOutput(Output *output) { if (m_output != output) { m_output = output; Q_EMIT screenChanged(); } } bool Window::isOnActiveOutput() const { return isOnOutput(workspace()->activeOutput()); } bool Window::isOnOutput(Output *output) const { return output->geometry().intersects(frameGeometry()); } Shadow *Window::shadow() const { return m_shadow; } void Window::updateShadow() { if (!Compositor::compositing()) { return; } if (m_shadow) { if (!m_shadow->updateShadow()) { deleteShadow(); } Q_EMIT shadowChanged(); } else { m_shadow = Shadow::createShadow(this); if (m_shadow) { Q_EMIT shadowChanged(); } } } SurfaceItem *Window::surfaceItem() const { if (m_sceneWindow) { return m_sceneWindow->surfaceItem(); } return nullptr; } WindowItem *Window::windowItem() const { if (m_sceneWindow) { return m_sceneWindow->windowItem(); } return nullptr; } bool Window::wantsShadowToBeRendered() const { return !isFullScreen() && maximizeMode() != MaximizeFull; } void Window::getWmOpaqueRegion() { if (!info) { return; } const auto rects = info->opaqueRegion(); QRegion new_opaque_region; for (const auto &r : rects) { new_opaque_region += QRect(r.pos.x, r.pos.y, r.size.width, r.size.height); } opaque_region = new_opaque_region; } QRegion Window::shapeRegion() const { if (m_shapeRegionIsValid) { return m_shapeRegion; } const QRect bufferGeometry = this->bufferGeometry(); if (shape()) { auto cookie = xcb_shape_get_rectangles_unchecked(kwinApp()->x11Connection(), frameId(), XCB_SHAPE_SK_BOUNDING); ScopedCPointer reply(xcb_shape_get_rectangles_reply(kwinApp()->x11Connection(), cookie, nullptr)); if (!reply.isNull()) { m_shapeRegion = QRegion(); const xcb_rectangle_t *rects = xcb_shape_get_rectangles_rectangles(reply.data()); const int rectCount = xcb_shape_get_rectangles_rectangles_length(reply.data()); for (int i = 0; i < rectCount; ++i) { m_shapeRegion += QRegion(rects[i].x, rects[i].y, rects[i].width, rects[i].height); } // make sure the shape is sane (X is async, maybe even XShape is broken) m_shapeRegion &= QRegion(0, 0, bufferGeometry.width(), bufferGeometry.height()); } else { m_shapeRegion = QRegion(); } } else { m_shapeRegion = QRegion(0, 0, bufferGeometry.width(), bufferGeometry.height()); } m_shapeRegionIsValid = true; return m_shapeRegion; } void Window::discardShapeRegion() { m_shapeRegionIsValid = false; m_shapeRegion = QRegion(); } bool Window::isClient() const { return false; } bool Window::isDeleted() const { return false; } bool Window::isOnCurrentActivity() const { #if KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return true; } return isOnActivity(Activities::self()->current()); #else return true; #endif } void Window::elevate(bool elevate) { if (!effectWindow()) { return; } effectWindow()->elevate(elevate); addWorkspaceRepaint(visibleGeometry()); } pid_t Window::pid() const { if (!info) { return -1; } return info->pid(); } xcb_window_t Window::frameId() const { return m_client; } Xcb::Property Window::fetchSkipCloseAnimation() const { return Xcb::Property(false, window(), atoms->kde_skip_close_animation, XCB_ATOM_CARDINAL, 0, 1); } void Window::readSkipCloseAnimation(Xcb::Property &property) { setSkipCloseAnimation(property.toBool()); } void Window::getSkipCloseAnimation() { Xcb::Property property = fetchSkipCloseAnimation(); readSkipCloseAnimation(property); } bool Window::skipsCloseAnimation() const { return m_skipCloseAnimation; } void Window::setSkipCloseAnimation(bool set) { if (set == m_skipCloseAnimation) { return; } m_skipCloseAnimation = set; Q_EMIT skipCloseAnimationChanged(); } KWaylandServer::SurfaceInterface *Window::surface() const { return m_surface; } void Window::setSurface(KWaylandServer::SurfaceInterface *surface) { if (m_surface == surface) { return; } m_surface = surface; m_pendingSurfaceId = 0; Q_EMIT surfaceChanged(); } int Window::stackingOrder() const { return m_stackingOrder; } void Window::setStackingOrder(int order) { if (m_stackingOrder != order) { m_stackingOrder = order; Q_EMIT stackingOrderChanged(); } } QByteArray Window::windowRole() const { if (!info) { return {}; } return QByteArray(info->windowRole()); } void Window::setDepth(int depth) { if (bit_depth == depth) { return; } const bool oldAlpha = hasAlpha(); bit_depth = depth; if (oldAlpha != hasAlpha()) { Q_EMIT hasAlphaChanged(); } } QRegion Window::inputShape() const { if (m_surface) { return m_surface->input(); } else { // TODO: maybe also for X11? return QRegion(); } } QMatrix4x4 Window::inputTransformation() const { QMatrix4x4 m; m.translate(-x(), -y()); return m; } bool Window::hitTest(const QPoint &point) const { if (isDecorated()) { if (m_decoration.inputRegion.contains(mapToFrame(point))) { return true; } } if (m_surface && m_surface->isMapped()) { return m_surface->inputSurfaceAt(mapToLocal(point)); } return inputGeometry().contains(point); } QPoint Window::mapToFrame(const QPoint &point) const { return point - frameGeometry().topLeft(); } QPoint Window::mapToLocal(const QPoint &point) const { return point - bufferGeometry().topLeft(); } QPointF Window::mapToLocal(const QPointF &point) const { return point - bufferGeometry().topLeft(); } QPointF Window::mapFromLocal(const QPointF &point) const { return point + bufferGeometry().topLeft(); } QRect Window::inputGeometry() const { if (isDecorated()) { return frameGeometry() + decoration()->resizeOnlyBorders(); } return frameGeometry(); } bool Window::isLocalhost() const { if (!m_clientMachine) { return true; } return m_clientMachine->isLocal(); } QMargins Window::frameMargins() const { return QMargins(borderLeft(), borderTop(), borderRight(), borderBottom()); } bool Window::isOnDesktop(VirtualDesktop *desktop) const { return isOnAllDesktops() || desktops().contains(desktop); } bool Window::isOnDesktop(int d) const { return isOnDesktop(VirtualDesktopManager::self()->desktopForX11Id(d)); } bool Window::isOnCurrentDesktop() const { return isOnDesktop(VirtualDesktopManager::self()->currentDesktop()); } void Window::updateMouseGrab() { } bool Window::belongToSameApplication(const Window *c1, const Window *c2, SameApplicationChecks checks) { return c1->belongsToSameApplication(c2, checks); } bool Window::isTransient() const { return false; } void Window::setClientShown(bool shown) { Q_UNUSED(shown) } xcb_timestamp_t Window::userTime() const { return XCB_TIME_CURRENT_TIME; } void Window::setSkipSwitcher(bool set) { set = rules()->checkSkipSwitcher(set); if (set == skipSwitcher()) { return; } m_skipSwitcher = set; doSetSkipSwitcher(); updateWindowRules(Rules::SkipSwitcher); Q_EMIT skipSwitcherChanged(); } void Window::setSkipPager(bool b) { b = rules()->checkSkipPager(b); if (b == skipPager()) { return; } m_skipPager = b; doSetSkipPager(); updateWindowRules(Rules::SkipPager); Q_EMIT skipPagerChanged(); } void Window::doSetSkipPager() { } void Window::setSkipTaskbar(bool b) { int was_wants_tab_focus = wantsTabFocus(); if (b == skipTaskbar()) { return; } m_skipTaskbar = b; doSetSkipTaskbar(); updateWindowRules(Rules::SkipTaskbar); if (was_wants_tab_focus != wantsTabFocus()) { FocusChain::self()->update(this, isActive() ? FocusChain::MakeFirst : FocusChain::Update); } Q_EMIT skipTaskbarChanged(); } void Window::setOriginalSkipTaskbar(bool b) { m_originalSkipTaskbar = rules()->checkSkipTaskbar(b); setSkipTaskbar(m_originalSkipTaskbar); } void Window::doSetSkipTaskbar() { } void Window::doSetSkipSwitcher() { } void Window::setIcon(const QIcon &icon) { m_icon = icon; Q_EMIT iconChanged(); } void Window::setActive(bool act) { if (isZombie()) { return; } if (m_active == act) { return; } m_active = act; const int ruledOpacity = m_active ? rules()->checkOpacityActive(qRound(opacity() * 100.0)) : rules()->checkOpacityInactive(qRound(opacity() * 100.0)); setOpacity(ruledOpacity / 100.0); workspace()->setActiveWindow(act ? this : nullptr); if (!m_active) { cancelAutoRaise(); } if (!m_active && shadeMode() == ShadeActivated) { setShade(ShadeNormal); } StackingUpdatesBlocker blocker(workspace()); updateLayer(); // active windows may get different layer auto mainwindows = mainWindows(); for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) { if ((*it)->isFullScreen()) { // fullscreens go high even if their transient is active (*it)->updateLayer(); } } doSetActive(); Q_EMIT activeChanged(); updateMouseGrab(); } void Window::doSetActive() { } bool Window::isZombie() const { return m_zombie; } void Window::markAsZombie() { Q_ASSERT(!m_zombie); m_zombie = true; } Layer Window::layer() const { if (m_layer == UnknownLayer) { const_cast(this)->m_layer = belongsToLayer(); } return m_layer; } void Window::updateLayer() { if (layer() == belongsToLayer()) { return; } StackingUpdatesBlocker blocker(workspace()); invalidateLayer(); // invalidate, will be updated when doing restacking for (auto it = transients().constBegin(), end = transients().constEnd(); it != end; ++it) { (*it)->updateLayer(); } } void Window::invalidateLayer() { m_layer = UnknownLayer; } Layer Window::belongsToLayer() const { // NOTICE while showingDesktop, desktops move to the AboveLayer // (interchangeable w/ eg. yakuake etc. which will at first remain visible) // and the docks move into the NotificationLayer (which is between Above- and // ActiveLayer, so that active fullscreen windows will still cover everything) // Since the desktop is also activated, nothing should be in the ActiveLayer, though if (isInternal()) { return UnmanagedLayer; } if (isLockScreen()) { return UnmanagedLayer; } if (isInputMethod()) { return UnmanagedLayer; } if (isDesktop()) { return workspace()->showingDesktop() ? AboveLayer : DesktopLayer; } if (isSplash()) { // no damn annoying splashscreens return NormalLayer; // getting in the way of everything else } if (isDock()) { if (workspace()->showingDesktop()) { return NotificationLayer; } return layerForDock(); } if (isPopupWindow()) { return PopupLayer; } if (isOnScreenDisplay()) { return OnScreenDisplayLayer; } if (isNotification()) { return NotificationLayer; } if (isCriticalNotification()) { return CriticalNotificationLayer; } if (workspace()->showingDesktop() && belongsToDesktop()) { return AboveLayer; } if (keepBelow()) { return BelowLayer; } if (isActiveFullScreen()) { return ActiveLayer; } if (keepAbove()) { return AboveLayer; } return NormalLayer; } bool Window::belongsToDesktop() const { return false; } Layer Window::layerForDock() const { // slight hack for the 'allow window to cover panel' Kicker setting // don't move keepbelow docks below normal window, but only to the same // layer, so that both may be raised to cover the other if (keepBelow()) { return NormalLayer; } if (keepAbove()) { // slight hack for the autohiding panels return AboveLayer; } return DockLayer; } void Window::setKeepAbove(bool b) { b = rules()->checkKeepAbove(b); if (b && !rules()->checkKeepBelow(false)) { setKeepBelow(false); } if (b == keepAbove()) { return; } m_keepAbove = b; doSetKeepAbove(); updateLayer(); updateWindowRules(Rules::Above); Q_EMIT keepAboveChanged(m_keepAbove); } void Window::doSetKeepAbove() { } void Window::setKeepBelow(bool b) { b = rules()->checkKeepBelow(b); if (b && !rules()->checkKeepAbove(false)) { setKeepAbove(false); } if (b == keepBelow()) { return; } m_keepBelow = b; doSetKeepBelow(); updateLayer(); updateWindowRules(Rules::Below); Q_EMIT keepBelowChanged(m_keepBelow); } void Window::doSetKeepBelow() { } void Window::startAutoRaise() { delete m_autoRaiseTimer; m_autoRaiseTimer = new QTimer(this); connect(m_autoRaiseTimer, &QTimer::timeout, this, &Window::autoRaise); m_autoRaiseTimer->setSingleShot(true); m_autoRaiseTimer->start(options->autoRaiseInterval()); } void Window::cancelAutoRaise() { delete m_autoRaiseTimer; m_autoRaiseTimer = nullptr; } void Window::autoRaise() { workspace()->raiseWindow(this); cancelAutoRaise(); } bool Window::isMostRecentlyRaised() const { // The last toplevel in the unconstrained stacking order is the most recently raised one. return workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop(), nullptr, true, false) == this; } bool Window::wantsTabFocus() const { return (isNormalWindow() || isDialog()) && wantsInput(); } bool Window::isSpecialWindow() const { // TODO return isDesktop() || isDock() || isSplash() || isToolbar() || isNotification() || isOnScreenDisplay() || isCriticalNotification(); } void Window::demandAttention(bool set) { if (isActive()) { set = false; } if (m_demandsAttention == set) { return; } m_demandsAttention = set; doSetDemandsAttention(); workspace()->windowAttentionChanged(this, set); Q_EMIT demandsAttentionChanged(); } void Window::doSetDemandsAttention() { } void Window::setDesktop(int desktop) { const int numberOfDesktops = VirtualDesktopManager::self()->count(); if (desktop != NET::OnAllDesktops) { // Do range check desktop = qMax(1, qMin(numberOfDesktops, desktop)); } QVector desktops; if (desktop != NET::OnAllDesktops) { desktops << VirtualDesktopManager::self()->desktopForX11Id(desktop); } setDesktops(desktops); } void Window::setDesktops(QVector desktops) { // on x11 we can have only one desktop at a time if (kwinApp()->operationMode() == Application::OperationModeX11 && desktops.size() > 1) { desktops = QVector({desktops.last()}); } desktops = rules()->checkDesktops(desktops); if (desktops == m_desktops) { return; } int was_desk = Window::desktop(); const bool wasOnCurrentDesktop = isOnCurrentDesktop() && was_desk >= 0; m_desktops = desktops; if (windowManagementInterface()) { if (m_desktops.isEmpty()) { windowManagementInterface()->setOnAllDesktops(true); } else { windowManagementInterface()->setOnAllDesktops(false); auto currentDesktops = windowManagementInterface()->plasmaVirtualDesktops(); for (auto desktop : qAsConst(m_desktops)) { if (!currentDesktops.contains(desktop->id())) { windowManagementInterface()->addPlasmaVirtualDesktop(desktop->id()); } else { currentDesktops.removeOne(desktop->id()); } } for (const auto &desktopId : qAsConst(currentDesktops)) { windowManagementInterface()->removePlasmaVirtualDesktop(desktopId); } } } if (info) { info->setDesktop(desktop()); } if ((was_desk == NET::OnAllDesktops) != (desktop() == NET::OnAllDesktops)) { // onAllDesktops changed workspace()->updateOnAllDesktopsOfTransients(this); } auto transients_stacking_order = workspace()->ensureStackingOrder(transients()); for (auto it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) { (*it)->setDesktops(desktops); } if (isModal()) // if a modal dialog is moved, move the mainwindow with it as otherwise // the (just moved) modal dialog will confusingly return to the mainwindow with // the next desktop change { const auto windows = mainWindows(); for (Window *other : windows) { other->setDesktops(desktops); } } doSetDesktop(); FocusChain::self()->update(this, FocusChain::MakeFirst); updateWindowRules(Rules::Desktops); Q_EMIT desktopChanged(); if (wasOnCurrentDesktop != isOnCurrentDesktop()) { addWorkspaceRepaint(visibleGeometry()); Q_EMIT desktopPresenceChanged(this, was_desk); } Q_EMIT x11DesktopIdsChanged(); } void Window::doSetDesktop() { } void Window::doSetOnActivities(const QStringList &activityList) { Q_UNUSED(activityList); } void Window::enterDesktop(VirtualDesktop *virtualDesktop) { if (m_desktops.contains(virtualDesktop)) { return; } auto desktops = m_desktops; desktops.append(virtualDesktop); setDesktops(desktops); } void Window::leaveDesktop(VirtualDesktop *virtualDesktop) { QVector currentDesktops; if (m_desktops.isEmpty()) { currentDesktops = VirtualDesktopManager::self()->desktops(); } else { currentDesktops = m_desktops; } if (!currentDesktops.contains(virtualDesktop)) { return; } auto desktops = currentDesktops; desktops.removeOne(virtualDesktop); setDesktops(desktops); } void Window::setOnAllDesktops(bool b) { if (b == isOnAllDesktops()) { return; } if (b) { setDesktops({}); } else { setDesktops({VirtualDesktopManager::self()->currentDesktop()}); } } int Window::desktop() const { return m_desktops.isEmpty() ? (int)NET::OnAllDesktops : m_desktops.last()->x11DesktopNumber(); } QVector Window::desktops() const { return m_desktops; } QVector Window::x11DesktopIds() const { const auto desks = desktops(); QVector x11Ids; x11Ids.reserve(desks.count()); std::transform(desks.constBegin(), desks.constEnd(), std::back_inserter(x11Ids), [](const VirtualDesktop *vd) { return vd->x11DesktopNumber(); }); return x11Ids; } QStringList Window::desktopIds() const { const auto desks = desktops(); QStringList ids; ids.reserve(desks.count()); std::transform(desks.constBegin(), desks.constEnd(), std::back_inserter(ids), [](const VirtualDesktop *vd) { return vd->id(); }); return ids; }; ShadeMode Window::shadeMode() const { return m_shadeMode; } bool Window::isShadeable() const { return false; } void Window::setShade(bool set) { set ? setShade(ShadeNormal) : setShade(ShadeNone); } void Window::setShade(ShadeMode mode) { if (!isShadeable()) { return; } if (mode == ShadeHover && isInteractiveMove()) { return; // causes geometry breaks and is probably nasty } if (isSpecialWindow() || !isDecorated()) { mode = ShadeNone; } mode = rules()->checkShade(mode); if (m_shadeMode == mode) { return; } const bool wasShade = isShade(); const ShadeMode previousShadeMode = shadeMode(); m_shadeMode = mode; if (wasShade == isShade()) { // Decoration may want to update after e.g. hover-shade changes Q_EMIT shadeChanged(); return; // No real change in shaded state } Q_ASSERT(isDecorated()); GeometryUpdatesBlocker blocker(this); doSetShade(previousShadeMode); updateWindowRules(Rules::Shade); Q_EMIT shadeChanged(); } void Window::doSetShade(ShadeMode previousShadeMode) { Q_UNUSED(previousShadeMode) } void Window::shadeHover() { setShade(ShadeHover); cancelShadeHoverTimer(); } void Window::shadeUnhover() { setShade(ShadeNormal); cancelShadeHoverTimer(); } void Window::startShadeHoverTimer() { if (!isShade()) { return; } m_shadeHoverTimer = new QTimer(this); connect(m_shadeHoverTimer, &QTimer::timeout, this, &Window::shadeHover); m_shadeHoverTimer->setSingleShot(true); m_shadeHoverTimer->start(options->shadeHoverInterval()); } void Window::startShadeUnhoverTimer() { if (m_shadeMode == ShadeHover && !isInteractiveMoveResize() && !isInteractiveMoveResizePointerButtonDown()) { m_shadeHoverTimer = new QTimer(this); connect(m_shadeHoverTimer, &QTimer::timeout, this, &Window::shadeUnhover); m_shadeHoverTimer->setSingleShot(true); m_shadeHoverTimer->start(options->shadeHoverInterval()); } } void Window::cancelShadeHoverTimer() { delete m_shadeHoverTimer; m_shadeHoverTimer = nullptr; } void Window::toggleShade() { // If the mode is ShadeHover or ShadeActive, cancel shade too. setShade(shadeMode() == ShadeNone ? ShadeNormal : ShadeNone); } Qt::Edge Window::titlebarPosition() const { // TODO: still needed, remove? return Qt::TopEdge; } bool Window::titlebarPositionUnderMouse() const { if (!isDecorated()) { return false; } const auto sectionUnderMouse = decoration()->sectionUnderMouse(); if (sectionUnderMouse == Qt::TitleBarArea) { return true; } // check other sections based on titlebarPosition switch (titlebarPosition()) { case Qt::TopEdge: return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::TopSection || sectionUnderMouse == Qt::TopRightSection); case Qt::LeftEdge: return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::LeftSection || sectionUnderMouse == Qt::BottomLeftSection); case Qt::RightEdge: return (sectionUnderMouse == Qt::BottomRightSection || sectionUnderMouse == Qt::RightSection || sectionUnderMouse == Qt::TopRightSection); case Qt::BottomEdge: return (sectionUnderMouse == Qt::BottomLeftSection || sectionUnderMouse == Qt::BottomSection || sectionUnderMouse == Qt::BottomRightSection); default: // nothing return false; } } void Window::setMinimized(bool set) { set ? minimize() : unminimize(); } void Window::minimize(bool avoid_animation) { if (!isMinimizable() || isMinimized()) { return; } m_minimized = true; doMinimize(); updateWindowRules(Rules::Minimize); if (options->moveMinimizedWindowsToEndOfTabBoxFocusChain()) { FocusChain::self()->update(this, FocusChain::MakeFirstMinimized); } // TODO: merge signal with s_minimized addWorkspaceRepaint(visibleGeometry()); Q_EMIT clientMinimized(this, !avoid_animation); Q_EMIT minimizedChanged(); } void Window::unminimize(bool avoid_animation) { if (!isMinimized()) { return; } if (rules()->checkMinimize(false)) { return; } m_minimized = false; doMinimize(); updateWindowRules(Rules::Minimize); Q_EMIT clientUnminimized(this, !avoid_animation); Q_EMIT minimizedChanged(); } void Window::doMinimize() { } QPalette Window::palette() const { if (!m_palette) { return QPalette(); } return m_palette->palette(); } const Decoration::DecorationPalette *Window::decorationPalette() const { return m_palette.get(); } QString Window::preferredColorScheme() const { return rules()->checkDecoColor(QString()); } QString Window::colorScheme() const { return m_colorScheme; } void Window::setColorScheme(const QString &colorScheme) { QString requestedColorScheme = colorScheme; if (requestedColorScheme.isEmpty()) { requestedColorScheme = QStringLiteral("kdeglobals"); } if (!m_palette || m_colorScheme != requestedColorScheme) { m_colorScheme = requestedColorScheme; if (m_palette) { disconnect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &Window::handlePaletteChange); } auto it = s_palettes.find(m_colorScheme); if (it == s_palettes.end() || it->expired()) { m_palette = std::make_shared(m_colorScheme); if (m_palette->isValid()) { s_palettes[m_colorScheme] = m_palette; } else { if (!s_defaultPalette) { s_defaultPalette = std::make_shared(QStringLiteral("kdeglobals")); s_palettes[QStringLiteral("kdeglobals")] = s_defaultPalette; } m_palette = s_defaultPalette; } if (m_colorScheme == QStringLiteral("kdeglobals")) { s_defaultPalette = m_palette; } } else { m_palette = it->lock(); } connect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &Window::handlePaletteChange); Q_EMIT paletteChanged(palette()); Q_EMIT colorSchemeChanged(); } } void Window::updateColorScheme() { setColorScheme(preferredColorScheme()); } void Window::handlePaletteChange() { Q_EMIT paletteChanged(palette()); } void Window::keepInArea(QRect area, bool partial) { if (partial) { // increase the area so that can have only 100 pixels in the area const QRect geometry = moveResizeGeometry(); area.setLeft(std::min(area.left() - geometry.width() + 100, area.left())); area.setTop(std::min(area.top() - geometry.height() + 100, area.top())); area.setRight(std::max(area.right() + geometry.width() - 100, area.right())); area.setBottom(std::max(area.bottom() + geometry.height() - 100, area.bottom())); } if (!partial) { // resize to fit into area const QRect geometry = moveResizeGeometry(); if (area.width() < geometry.width() || area.height() < geometry.height()) { resizeWithChecks(geometry.size().boundedTo(area.size())); } } QRect geometry = moveResizeGeometry(); if (geometry.right() > area.right() && geometry.width() <= area.width()) { geometry.moveRight(area.right()); } if (geometry.bottom() > area.bottom() && geometry.height() <= area.height()) { geometry.moveBottom(area.bottom()); } if (!area.contains(geometry.topLeft())) { if (geometry.left() < area.left()) { geometry.moveLeft(area.left()); } if (geometry.top() < area.top()) { geometry.moveTop(area.top()); } } if (moveResizeGeometry().topLeft() != geometry.topLeft()) { move(geometry.topLeft()); } } /** * Returns the maximum client size, not the maximum frame size. */ QSize Window::maxSize() const { return rules()->checkMaxSize(QSize(INT_MAX, INT_MAX)); } /** * Returns the minimum client size, not the minimum frame size. */ QSize Window::minSize() const { return rules()->checkMinSize(QSize(0, 0)); } void Window::blockGeometryUpdates(bool block) { if (block) { if (m_blockGeometryUpdates == 0) { m_pendingMoveResizeMode = MoveResizeMode::None; } ++m_blockGeometryUpdates; } else { if (--m_blockGeometryUpdates == 0) { if (m_pendingMoveResizeMode != MoveResizeMode::None) { moveResizeInternal(moveResizeGeometry(), m_pendingMoveResizeMode); m_pendingMoveResizeMode = MoveResizeMode::None; } } } } void Window::maximize(MaximizeMode m) { setMaximize(m & MaximizeVertical, m & MaximizeHorizontal); } void Window::setMaximize(bool vertically, bool horizontally) { // changeMaximize() flips the state, so change from set->flip const MaximizeMode oldMode = requestedMaximizeMode(); changeMaximize( oldMode & MaximizeHorizontal ? !horizontally : horizontally, oldMode & MaximizeVertical ? !vertically : vertically, false); const MaximizeMode newMode = maximizeMode(); if (oldMode != newMode) { Q_EMIT clientMaximizedStateChanged(this, newMode); Q_EMIT clientMaximizedStateChanged(this, vertically, horizontally); } } bool Window::startInteractiveMoveResize() { Q_ASSERT(!isInteractiveMoveResize()); Q_ASSERT(QWidget::keyboardGrabber() == nullptr); Q_ASSERT(QWidget::mouseGrabber() == nullptr); stopDelayedInteractiveMoveResize(); if (QApplication::activePopupWidget() != nullptr) { return false; // popups have grab } if (isFullScreen() && (screens()->count() < 2 || !isMovableAcrossScreens())) { return false; } if (!doStartInteractiveMoveResize()) { return false; } invalidateDecorationDoubleClickTimer(); setInteractiveMoveResize(true); workspace()->setMoveResizeWindow(this); if (maximizeMode() != MaximizeRestore) { switch (interactiveMoveResizeGravity()) { case Gravity::Left: case Gravity::Right: // Quit maximized horizontally state if the window is resized horizontally. if (maximizeMode() & MaximizeHorizontal) { QRect originalGeometry = geometryRestore(); originalGeometry.setX(moveResizeGeometry().x()); originalGeometry.setWidth(moveResizeGeometry().width()); setGeometryRestore(originalGeometry); maximize(maximizeMode() ^ MaximizeHorizontal); } break; case Gravity::Top: case Gravity::Bottom: // Quit maximized vertically state if the window is resized vertically. if (maximizeMode() & MaximizeVertical) { QRect originalGeometry = geometryRestore(); originalGeometry.setY(moveResizeGeometry().y()); originalGeometry.setHeight(moveResizeGeometry().height()); setGeometryRestore(originalGeometry); maximize(maximizeMode() ^ MaximizeVertical); } break; case Gravity::TopLeft: case Gravity::BottomLeft: case Gravity::TopRight: case Gravity::BottomRight: // Quit the maximized mode if the window is resized by dragging one of its corners. setGeometryRestore(moveResizeGeometry()); maximize(MaximizeRestore); break; default: break; } } if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { if (interactiveMoveResizeGravity() != Gravity::None) { // Cannot use isResize() yet // Exit quick tile mode when the user attempts to resize a tiled window updateQuickTileMode(QuickTileFlag::None); // Do so without restoring original geometry setGeometryRestore(moveResizeGeometry()); doSetQuickTileMode(); Q_EMIT quickTileModeChanged(); } } updateInitialMoveResizeGeometry(); updateElectricGeometryRestore(); checkUnrestrictedInteractiveMoveResize(); Q_EMIT clientStartUserMovedResized(this); if (ScreenEdges::self()->isDesktopSwitchingMovingClients()) { ScreenEdges::self()->reserveDesktopSwitching(true, Qt::Vertical | Qt::Horizontal); } return true; } void Window::finishInteractiveMoveResize(bool cancel) { GeometryUpdatesBlocker blocker(this); leaveInteractiveMoveResize(); doFinishInteractiveMoveResize(); if (cancel) { moveResize(initialInteractiveMoveResizeGeometry()); } if (output() != interactiveMoveResizeStartOutput()) { workspace()->sendWindowToOutput(this, output()); // checks rule validity if (isFullScreen() || maximizeMode() != MaximizeRestore) { checkWorkspacePosition(); } } if (isElectricBorderMaximizing()) { setQuickTileMode(electricBorderMode()); setElectricBorderMaximizing(false); } setElectricBorderMode(QuickTileMode(QuickTileFlag::None)); Q_EMIT clientFinishUserMovedResized(this); } // This function checks if it actually makes sense to perform a restricted move/resize. // If e.g. the titlebar is already outside of the workarea, there's no point in performing // a restricted move resize, because then e.g. resize would also move the window (#74555). // NOTE: Most of it is duplicated from handleMoveResize(). void Window::checkUnrestrictedInteractiveMoveResize() { if (isUnrestrictedInteractiveMoveResize()) { return; } const QRect &moveResizeGeom = moveResizeGeometry(); QRect desktopArea = workspace()->clientArea(WorkArea, this, moveResizeGeom.center()); int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge; // restricted move/resize - keep at least part of the titlebar always visible // how much must remain visible when moved away in that direction left_marge = qMin(100 + borderRight(), moveResizeGeom.width()); right_marge = qMin(100 + borderLeft(), moveResizeGeom.width()); // width/height change with opaque resizing, use the initial ones titlebar_marge = initialInteractiveMoveResizeGeometry().height(); top_marge = borderBottom(); bottom_marge = borderTop(); if (isInteractiveResize()) { if (moveResizeGeom.bottom() < desktopArea.top() + top_marge) { setUnrestrictedInteractiveMoveResize(true); } if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge) { setUnrestrictedInteractiveMoveResize(true); } if (moveResizeGeom.right() < desktopArea.left() + left_marge) { setUnrestrictedInteractiveMoveResize(true); } if (moveResizeGeom.left() > desktopArea.right() - right_marge) { setUnrestrictedInteractiveMoveResize(true); } if (!isUnrestrictedInteractiveMoveResize() && moveResizeGeom.top() < desktopArea.top()) { // titlebar mustn't go out setUnrestrictedInteractiveMoveResize(true); } } if (isInteractiveMove()) { if (moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1) { setUnrestrictedInteractiveMoveResize(true); } // no need to check top_marge, titlebar_marge already handles it if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge + 1) { // titlebar mustn't go out setUnrestrictedInteractiveMoveResize(true); } if (moveResizeGeom.right() < desktopArea.left() + left_marge) { setUnrestrictedInteractiveMoveResize(true); } if (moveResizeGeom.left() > desktopArea.right() - right_marge) { setUnrestrictedInteractiveMoveResize(true); } } } // When the user pressed mouse on the titlebar, don't activate move immediately, // since it may be just a click. Activate instead after a delay. Move used to be // activated only after moving by several pixels, but that looks bad. void Window::startDelayedInteractiveMoveResize() { Q_ASSERT(!m_interactiveMoveResize.delayedTimer); m_interactiveMoveResize.delayedTimer = new QTimer(this); m_interactiveMoveResize.delayedTimer->setSingleShot(true); connect(m_interactiveMoveResize.delayedTimer, &QTimer::timeout, this, [this]() { Q_ASSERT(isInteractiveMoveResizePointerButtonDown()); if (!startInteractiveMoveResize()) { setInteractiveMoveResizePointerButtonDown(false); } updateCursor(); stopDelayedInteractiveMoveResize(); }); m_interactiveMoveResize.delayedTimer->start(QApplication::startDragTime()); } void Window::stopDelayedInteractiveMoveResize() { delete m_interactiveMoveResize.delayedTimer; m_interactiveMoveResize.delayedTimer = nullptr; } void Window::updateInteractiveMoveResize(const QPointF ¤tGlobalCursor) { handleInteractiveMoveResize(pos(), currentGlobalCursor.toPoint()); } void Window::handleInteractiveMoveResize(const QPoint &local, const QPoint &global) { const QRect oldGeo = moveResizeGeometry(); handleInteractiveMoveResize(local.x(), local.y(), global.x(), global.y()); if (!isFullScreen() && isInteractiveMove()) { if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && oldGeo != moveResizeGeometry()) { GeometryUpdatesBlocker blocker(this); setQuickTileMode(QuickTileFlag::None); const QRect &geom_restore = geometryRestore(); setInteractiveMoveOffset(QPoint(double(interactiveMoveOffset().x()) / double(oldGeo.width()) * double(geom_restore.width()), double(interactiveMoveOffset().y()) / double(oldGeo.height()) * double(geom_restore.height()))); if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore) { setMoveResizeGeometry(geom_restore); } handleInteractiveMoveResize(local.x(), local.y(), global.x(), global.y()); // fix position } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None) && isResizable()) { checkQuickTilingMaximizationZones(global.x(), global.y()); } } } void Window::handleInteractiveMoveResize(int x, int y, int x_root, int y_root) { if (isWaitingForInteractiveMoveResizeSync()) { return; // we're still waiting for the client or the timeout } const Gravity gravity = interactiveMoveResizeGravity(); if ((gravity == Gravity::None && !isMovableAcrossScreens()) || (gravity != Gravity::None && (isShade() || !isResizable()))) { return; } if (!isInteractiveMoveResize()) { QPoint p(QPoint(x /* - padding_left*/, y /* - padding_top*/) - interactiveMoveOffset()); if (p.manhattanLength() >= QApplication::startDragDistance()) { if (!startInteractiveMoveResize()) { setInteractiveMoveResizePointerButtonDown(false); updateCursor(); return; } updateCursor(); } else { return; } } // ShadeHover or ShadeActive, ShadeNormal was already avoided above if (gravity != Gravity::None && shadeMode() != ShadeNone) { setShade(ShadeNone); } QPoint globalPos(x_root, y_root); // these two points limit the geometry rectangle, i.e. if bottomleft resizing is done, // the bottomleft corner should be at is at (topleft.x(), bottomright().y()) QPoint topleft = globalPos - interactiveMoveOffset(); QPoint bottomright = globalPos + invertedInteractiveMoveOffset(); QRect previousMoveResizeGeom = moveResizeGeometry(); // TODO move whole group when moving its leader or when the leader is not mapped? auto titleBarRect = [this](bool &transposed, int &requiredPixels) -> QRect { const QRect &moveResizeGeom = moveResizeGeometry(); QRect r(moveResizeGeom); r.moveTopLeft(QPoint(0, 0)); switch (titlebarPosition()) { default: case Qt::TopEdge: r.setHeight(borderTop()); break; case Qt::LeftEdge: r.setWidth(borderLeft()); transposed = true; break; case Qt::BottomEdge: r.setTop(r.bottom() - borderBottom()); break; case Qt::RightEdge: r.setLeft(r.right() - borderRight()); transposed = true; break; } // When doing a restricted move we must always keep 100px of the titlebar // visible to allow the user to be able to move it again. requiredPixels = qMin(100 * (transposed ? r.width() : r.height()), moveResizeGeom.width() * moveResizeGeom.height()); return r; }; bool update = false; if (isInteractiveResize()) { QRect orig = initialInteractiveMoveResizeGeometry(); SizeMode sizeMode = SizeModeAny; auto calculateMoveResizeGeom = [this, &topleft, &bottomright, &orig, &sizeMode, &gravity]() { switch (gravity) { case Gravity::TopLeft: setMoveResizeGeometry(QRect(topleft, orig.bottomRight())); break; case Gravity::BottomRight: setMoveResizeGeometry(QRect(orig.topLeft(), bottomright)); break; case Gravity::BottomLeft: setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.y()), QPoint(orig.right(), bottomright.y()))); break; case Gravity::TopRight: setMoveResizeGeometry(QRect(QPoint(orig.x(), topleft.y()), QPoint(bottomright.x(), orig.bottom()))); break; case Gravity::Top: setMoveResizeGeometry(QRect(QPoint(orig.left(), topleft.y()), orig.bottomRight())); sizeMode = SizeModeFixedH; // try not to affect height break; case Gravity::Bottom: setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(orig.right(), bottomright.y()))); sizeMode = SizeModeFixedH; break; case Gravity::Left: setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.top()), orig.bottomRight())); sizeMode = SizeModeFixedW; break; case Gravity::Right: setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(bottomright.x(), orig.bottom()))); sizeMode = SizeModeFixedW; break; case Gravity::None: Q_UNREACHABLE(); break; } }; // first resize (without checking constrains), then snap, then check bounds, then check constrains calculateMoveResizeGeom(); // adjust new size to snap to other windows/borders setMoveResizeGeometry(workspace()->adjustWindowSize(this, moveResizeGeometry(), gravity)); if (!isUnrestrictedInteractiveMoveResize()) { // Make sure the titlebar isn't behind a restricted area. We don't need to restrict // the other directions. If not visible enough, move the window to the closest valid // point. We bruteforce this by slowly moving the window back to its previous position QRegion availableArea(workspace()->clientArea(FullArea, this, workspace()->activeOutput())); availableArea -= workspace()->restrictedMoveArea(VirtualDesktopManager::self()->currentDesktop()); bool transposed = false; int requiredPixels; QRect bTitleRect = titleBarRect(transposed, requiredPixels); int lastVisiblePixels = -1; QRect lastTry = moveResizeGeometry(); bool titleFailed = false; for (;;) { const QRect titleRect(bTitleRect.translated(moveResizeGeometry().topLeft())); int visiblePixels = 0; int realVisiblePixels = 0; for (const QRect &rect : availableArea) { const QRect r = rect & titleRect; realVisiblePixels += r.width() * r.height(); if ((transposed && r.width() == titleRect.width()) || // Only the full size regions... (!transposed && r.height() == titleRect.height())) { // ...prevents long slim areas visiblePixels += r.width() * r.height(); } } if (visiblePixels >= requiredPixels) { break; // We have reached a valid position } if (realVisiblePixels <= lastVisiblePixels) { if (titleFailed && realVisiblePixels < lastVisiblePixels) { break; // we won't become better } else { if (!titleFailed) { setMoveResizeGeometry(lastTry); } titleFailed = true; } } lastVisiblePixels = realVisiblePixels; QRect moveResizeGeom = moveResizeGeometry(); lastTry = moveResizeGeom; // Not visible enough, move the window to the closest valid point. We bruteforce // this by slowly moving the window back to its previous position. // The geometry changes at up to two edges, the one with the title (if) shall take // precedence. The opposing edge has no impact on visiblePixels and only one of // the adjacent can alter at a time, ie. it's enough to ignore adjacent edges // if the title edge altered bool leftChanged = previousMoveResizeGeom.left() != moveResizeGeom.left(); bool rightChanged = previousMoveResizeGeom.right() != moveResizeGeom.right(); bool topChanged = previousMoveResizeGeom.top() != moveResizeGeom.top(); bool btmChanged = previousMoveResizeGeom.bottom() != moveResizeGeom.bottom(); auto fixChangedState = [titleFailed](bool &major, bool &counter, bool &ad1, bool &ad2) { counter = false; if (titleFailed) { major = false; } if (major) { ad1 = ad2 = false; } }; switch (titlebarPosition()) { default: case Qt::TopEdge: fixChangedState(topChanged, btmChanged, leftChanged, rightChanged); break; case Qt::LeftEdge: fixChangedState(leftChanged, rightChanged, topChanged, btmChanged); break; case Qt::BottomEdge: fixChangedState(btmChanged, topChanged, leftChanged, rightChanged); break; case Qt::RightEdge: fixChangedState(rightChanged, leftChanged, topChanged, btmChanged); break; } if (topChanged) { moveResizeGeom.setTop(moveResizeGeom.y() + sign(previousMoveResizeGeom.y() - moveResizeGeom.y())); } else if (leftChanged) { moveResizeGeom.setLeft(moveResizeGeom.x() + sign(previousMoveResizeGeom.x() - moveResizeGeom.x())); } else if (btmChanged) { moveResizeGeom.setBottom(moveResizeGeom.bottom() + sign(previousMoveResizeGeom.bottom() - moveResizeGeom.bottom())); } else if (rightChanged) { moveResizeGeom.setRight(moveResizeGeom.right() + sign(previousMoveResizeGeom.right() - moveResizeGeom.right())); } else { break; // no position changed - that's certainly not good } setMoveResizeGeometry(moveResizeGeom); } } // Always obey size hints, even when in "unrestricted" mode QSize size = constrainFrameSize(moveResizeGeometry().size(), sizeMode); // the new topleft and bottomright corners (after checking size constrains), if they'll be needed topleft = QPoint(moveResizeGeometry().right() - size.width() + 1, moveResizeGeometry().bottom() - size.height() + 1); bottomright = QPoint(moveResizeGeometry().left() + size.width() - 1, moveResizeGeometry().top() + size.height() - 1); orig = moveResizeGeometry(); // if aspect ratios are specified, both dimensions may change. // Therefore grow to the right/bottom if needed. // TODO it should probably obey gravity rather than always using right/bottom ? if (sizeMode == SizeModeFixedH) { orig.setRight(bottomright.x()); } else if (sizeMode == SizeModeFixedW) { orig.setBottom(bottomright.y()); } calculateMoveResizeGeom(); if (moveResizeGeometry().size() != previousMoveResizeGeom.size()) { update = true; } } else if (isInteractiveMove()) { Q_ASSERT(gravity == Gravity::None); if (!isMovable()) { // isMovableAcrossScreens() must have been true to get here // Special moving of maximized windows on Xinerama screens Output *output = kwinApp()->platform()->outputAt(globalPos); if (isFullScreen()) { setMoveResizeGeometry(workspace()->clientArea(FullScreenArea, this, output)); } else { QRect moveResizeGeom = workspace()->clientArea(MaximizeArea, this, output); QSize adjSize = constrainFrameSize(moveResizeGeom.size(), SizeModeMax); if (adjSize != moveResizeGeom.size()) { QRect r(moveResizeGeom); moveResizeGeom.setSize(adjSize); moveResizeGeom.moveCenter(r.center()); } setMoveResizeGeometry(moveResizeGeom); } } else { // first move, then snap, then check bounds QRect moveResizeGeom = moveResizeGeometry(); moveResizeGeom.moveTopLeft(topleft); moveResizeGeom.moveTopLeft(workspace()->adjustWindowPosition(this, moveResizeGeom.topLeft(), isUnrestrictedInteractiveMoveResize())); setMoveResizeGeometry(moveResizeGeom); if (!isUnrestrictedInteractiveMoveResize()) { const QRegion strut = workspace()->restrictedMoveArea(VirtualDesktopManager::self()->currentDesktop()); QRegion availableArea(workspace()->clientArea(FullArea, this, workspace()->activeOutput())); availableArea -= strut; // Strut areas bool transposed = false; int requiredPixels; QRect bTitleRect = titleBarRect(transposed, requiredPixels); for (;;) { QRect moveResizeGeom = moveResizeGeometry(); const QRect titleRect(bTitleRect.translated(moveResizeGeom.topLeft())); int visiblePixels = 0; for (const QRect &rect : availableArea) { const QRect r = rect & titleRect; if ((transposed && r.width() == titleRect.width()) || // Only the full size regions... (!transposed && r.height() == titleRect.height())) { // ...prevents long slim areas visiblePixels += r.width() * r.height(); } } if (visiblePixels >= requiredPixels) { break; // We have reached a valid position } // (esp.) if there're more screens with different struts (panels) it the titlebar // will be movable outside the movearea (covering one of the panels) until it // crosses the panel "too much" (not enough visiblePixels) and then stucks because // it's usually only pushed by 1px to either direction // so we first check whether we intersect suc strut and move the window below it // immediately (it's still possible to hit the visiblePixels >= titlebarArea break // by moving the window slightly downwards, but it won't stuck) // see bug #274466 // and bug #301805 for why we can't just match the titlearea against the screen if (screens()->count() > 1) { // optimization // TODO: could be useful on partial screen struts (half-width panels etc.) int newTitleTop = -1; for (const QRect &r : strut) { if (r.top() == 0 && r.width() > r.height() && // "top panel" r.intersects(moveResizeGeom) && moveResizeGeom.top() < r.bottom()) { newTitleTop = r.bottom() + 1; break; } } if (newTitleTop > -1) { moveResizeGeom.moveTop(newTitleTop); // invalid position, possibly on screen change setMoveResizeGeometry(moveResizeGeom); break; } } int dx = sign(previousMoveResizeGeom.x() - moveResizeGeom.x()), dy = sign(previousMoveResizeGeom.y() - moveResizeGeom.y()); if (visiblePixels && dx) { // means there's no full width cap -> favor horizontally dy = 0; } else if (dy) { dx = 0; } // Move it back moveResizeGeom.translate(dx, dy); setMoveResizeGeometry(moveResizeGeom); if (moveResizeGeom == previousMoveResizeGeom) { break; // Prevent lockup } } } } if (moveResizeGeometry().topLeft() != previousMoveResizeGeom.topLeft()) { update = true; } } else { Q_UNREACHABLE(); } if (!update) { return; } if (isInteractiveMove()) { move(moveResizeGeometry().topLeft()); } else { doInteractiveResizeSync(); } Q_EMIT clientStepUserMovedResized(this, moveResizeGeometry()); } StrutRect Window::strutRect(StrutArea area) const { Q_UNUSED(area) return StrutRect(); } StrutRects Window::strutRects() const { StrutRects region; region += strutRect(StrutAreaTop); region += strutRect(StrutAreaRight); region += strutRect(StrutAreaBottom); region += strutRect(StrutAreaLeft); return region; } bool Window::hasStrut() const { return false; } void Window::setupWindowManagementInterface() { if (m_windowManagementInterface) { // already setup return; } if (!waylandServer() || !surface()) { return; } if (!waylandServer()->windowManagement()) { return; } using namespace KWaylandServer; auto w = waylandServer()->windowManagement()->createWindow(this, internalId()); w->setTitle(caption()); w->setActive(isActive()); w->setFullscreen(isFullScreen()); w->setKeepAbove(keepAbove()); w->setKeepBelow(keepBelow()); w->setMaximized(maximizeMode() == KWin::MaximizeFull); w->setMinimized(isMinimized()); w->setDemandsAttention(isDemandingAttention()); w->setCloseable(isCloseable()); w->setMaximizeable(isMaximizable()); w->setMinimizeable(isMinimizable()); w->setFullscreenable(isFullScreenable()); w->setApplicationMenuPaths(applicationMenuServiceName(), applicationMenuObjectPath()); w->setIcon(icon()); auto updateAppId = [this, w] { w->setResourceName(resourceName()); w->setAppId(QString::fromUtf8(m_desktopFileName.isEmpty() ? resourceClass() : m_desktopFileName)); }; updateAppId(); w->setSkipTaskbar(skipTaskbar()); w->setSkipSwitcher(skipSwitcher()); w->setPid(pid()); w->setShadeable(isShadeable()); w->setShaded(isShade()); w->setResizable(isResizable()); w->setMovable(isMovable()); w->setVirtualDesktopChangeable(true); // FIXME Matches X11Window::actionSupported(), but both should be implemented. w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr); w->setGeometry(frameGeometry()); connect(this, &Window::skipTaskbarChanged, w, [w, this]() { w->setSkipTaskbar(skipTaskbar()); }); connect(this, &Window::skipSwitcherChanged, w, [w, this]() { w->setSkipSwitcher(skipSwitcher()); }); connect(this, &Window::captionChanged, w, [w, this] { w->setTitle(caption()); }); connect(this, &Window::activeChanged, w, [w, this] { w->setActive(isActive()); }); connect(this, &Window::fullScreenChanged, w, [w, this] { w->setFullscreen(isFullScreen()); }); connect(this, &Window::keepAboveChanged, w, &PlasmaWindowInterface::setKeepAbove); connect(this, &Window::keepBelowChanged, w, &PlasmaWindowInterface::setKeepBelow); connect(this, &Window::minimizedChanged, w, [w, this] { w->setMinimized(isMinimized()); }); connect(this, static_cast(&Window::clientMaximizedStateChanged), w, [w](KWin::Window *c, MaximizeMode mode) { Q_UNUSED(c); w->setMaximized(mode == KWin::MaximizeFull); }); connect(this, &Window::demandsAttentionChanged, w, [w, this] { w->setDemandsAttention(isDemandingAttention()); }); connect(this, &Window::iconChanged, w, [w, this]() { w->setIcon(icon()); }); connect(this, &Window::windowClassChanged, w, updateAppId); connect(this, &Window::desktopFileNameChanged, w, updateAppId); connect(this, &Window::shadeChanged, w, [w, this] { w->setShaded(isShade()); }); connect(this, &Window::transientChanged, w, [w, this]() { w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr); }); connect(this, &Window::frameGeometryChanged, w, [w, this]() { w->setGeometry(frameGeometry()); }); connect(this, &Window::applicationMenuChanged, w, [w, this]() { w->setApplicationMenuPaths(applicationMenuServiceName(), applicationMenuObjectPath()); }); connect(w, &PlasmaWindowInterface::closeRequested, this, [this] { closeWindow(); }); connect(w, &PlasmaWindowInterface::moveRequested, this, [this]() { Cursors::self()->mouse()->setPos(frameGeometry().center()); performMouseCommand(Options::MouseMove, Cursors::self()->mouse()->pos()); }); connect(w, &PlasmaWindowInterface::resizeRequested, this, [this]() { Cursors::self()->mouse()->setPos(frameGeometry().bottomRight()); performMouseCommand(Options::MouseResize, Cursors::self()->mouse()->pos()); }); connect(w, &PlasmaWindowInterface::fullscreenRequested, this, [this](bool set) { setFullScreen(set, false); }); connect(w, &PlasmaWindowInterface::minimizedRequested, this, [this](bool set) { if (set) { minimize(); } else { unminimize(); } }); connect(w, &PlasmaWindowInterface::maximizedRequested, this, [this](bool set) { maximize(set ? MaximizeFull : MaximizeRestore); }); connect(w, &PlasmaWindowInterface::keepAboveRequested, this, [this](bool set) { setKeepAbove(set); }); connect(w, &PlasmaWindowInterface::keepBelowRequested, this, [this](bool set) { setKeepBelow(set); }); connect(w, &PlasmaWindowInterface::demandsAttentionRequested, this, [this](bool set) { demandAttention(set); }); connect(w, &PlasmaWindowInterface::activeRequested, this, [this](bool set) { if (set) { workspace()->activateWindow(this, true); } }); connect(w, &PlasmaWindowInterface::shadedRequested, this, [this](bool set) { setShade(set); }); for (const auto vd : qAsConst(m_desktops)) { w->addPlasmaVirtualDesktop(vd->id()); } // We need to set `OnAllDesktops` after the actual VD list has been added. // Otherwise it will unconditionally add the current desktop to the interface // which may not be the case, for example, when using rules w->setOnAllDesktops(isOnAllDesktops()); // Plasma Virtual desktop management // show/hide when the window enters/exits from desktop connect(w, &PlasmaWindowInterface::enterPlasmaVirtualDesktopRequested, this, [this](const QString &desktopId) { VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId); if (vd) { enterDesktop(vd); } }); connect(w, &PlasmaWindowInterface::enterNewPlasmaVirtualDesktopRequested, this, [this]() { VirtualDesktopManager::self()->setCount(VirtualDesktopManager::self()->count() + 1); enterDesktop(VirtualDesktopManager::self()->desktops().last()); }); connect(w, &PlasmaWindowInterface::leavePlasmaVirtualDesktopRequested, this, [this](const QString &desktopId) { VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId); if (vd) { leaveDesktop(vd); } }); for (const auto &activity : qAsConst(m_activityList)) { w->addPlasmaActivity(activity); } connect(this, &Window::activitiesChanged, w, [w, this] { const auto newActivities = QSet(m_activityList.begin(), m_activityList.end()); const auto oldActivitiesList = w->plasmaActivities(); const auto oldActivities = QSet(oldActivitiesList.begin(), oldActivitiesList.end()); const auto activitiesToAdd = newActivities - oldActivities; for (const auto &activity : activitiesToAdd) { w->addPlasmaActivity(activity); } const auto activitiesToRemove = oldActivities - newActivities; for (const auto &activity : activitiesToRemove) { w->removePlasmaActivity(activity); } }); // Plasma Activities management // show/hide when the window enters/exits activity connect(w, &PlasmaWindowInterface::enterPlasmaActivityRequested, this, [this](const QString &activityId) { setOnActivity(activityId, true); }); connect(w, &PlasmaWindowInterface::leavePlasmaActivityRequested, this, [this](const QString &activityId) { setOnActivity(activityId, false); }); connect(w, &PlasmaWindowInterface::sendToOutput, this, [this](KWaylandServer::OutputInterface *output) { sendToOutput(waylandServer()->findOutput(output)); }); m_windowManagementInterface = w; } Options::MouseCommand Window::getMouseCommand(Qt::MouseButton button, bool *handled) const { *handled = false; if (button == Qt::NoButton) { return Options::MouseNothing; } if (isActive()) { if (options->isClickRaise() && !isMostRecentlyRaised()) { *handled = true; return Options::MouseActivateRaiseAndPassClick; } } else { *handled = true; switch (button) { case Qt::LeftButton: return options->commandWindow1(); case Qt::MiddleButton: return options->commandWindow2(); case Qt::RightButton: return options->commandWindow3(); default: // all other buttons pass Activate & Pass Client return Options::MouseActivateAndPassClick; } } return Options::MouseNothing; } Options::MouseCommand Window::getWheelCommand(Qt::Orientation orientation, bool *handled) const { *handled = false; if (orientation != Qt::Vertical) { return Options::MouseNothing; } if (!isActive()) { *handled = true; return options->commandWindowWheel(); } return Options::MouseNothing; } bool Window::performMouseCommand(Options::MouseCommand cmd, const QPoint &globalPos) { bool replay = false; switch (cmd) { case Options::MouseRaise: workspace()->raiseWindow(this); break; case Options::MouseLower: { workspace()->lowerWindow(this); // used to be activateNextWindow(this), then topWindowOnDesktop // since this is a mouseOp it's however safe to use the window under the mouse instead if (isActive() && options->focusPolicyIsReasonable()) { Window *next = workspace()->windowUnderMouse(output()); if (next && next != this) { workspace()->requestFocus(next, false); } } break; } case Options::MouseOperationsMenu: if (isActive() && options->isClickRaise()) { autoRaise(); } workspace()->showWindowMenu(QRect(globalPos, globalPos), this); break; case Options::MouseToggleRaiseAndLower: workspace()->raiseOrLowerWindow(this); break; case Options::MouseActivateAndRaise: { replay = isActive(); // for clickraise mode bool mustReplay = !rules()->checkAcceptFocus(acceptsFocus()); if (mustReplay) { auto it = workspace()->stackingOrder().constEnd(), begin = workspace()->stackingOrder().constBegin(); while (mustReplay && --it != begin && *it != this) { auto c = *it; if (!c->isClient() || (c->keepAbove() && !keepAbove()) || (keepBelow() && !c->keepBelow())) { continue; // can never raise above "it" } mustReplay = !(c->isOnCurrentDesktop() && c->isOnCurrentActivity() && c->frameGeometry().intersects(frameGeometry())); } } workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise); workspace()->setActiveOutput(globalPos); replay = replay || mustReplay; break; } case Options::MouseActivateAndLower: workspace()->requestFocus(this); workspace()->lowerWindow(this); workspace()->setActiveOutput(globalPos); replay = replay || !rules()->checkAcceptFocus(acceptsFocus()); break; case Options::MouseActivate: replay = isActive(); // for clickraise mode workspace()->takeActivity(this, Workspace::ActivityFocus); workspace()->setActiveOutput(globalPos); replay = replay || !rules()->checkAcceptFocus(acceptsFocus()); break; case Options::MouseActivateRaiseAndPassClick: workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise); workspace()->setActiveOutput(globalPos); replay = true; break; case Options::MouseActivateAndPassClick: workspace()->takeActivity(this, Workspace::ActivityFocus); workspace()->setActiveOutput(globalPos); replay = true; break; case Options::MouseMaximize: maximize(MaximizeFull); break; case Options::MouseRestore: maximize(MaximizeRestore); break; case Options::MouseMinimize: minimize(); break; case Options::MouseAbove: { StackingUpdatesBlocker blocker(workspace()); if (keepBelow()) { setKeepBelow(false); } else { setKeepAbove(true); } break; } case Options::MouseBelow: { StackingUpdatesBlocker blocker(workspace()); if (keepAbove()) { setKeepAbove(false); } else { setKeepBelow(true); } break; } case Options::MousePreviousDesktop: workspace()->windowToPreviousDesktop(this); break; case Options::MouseNextDesktop: workspace()->windowToNextDesktop(this); break; case Options::MouseOpacityMore: if (!isDesktop()) { // No point in changing the opacity of the desktop setOpacity(qMin(opacity() + 0.1, 1.0)); } break; case Options::MouseOpacityLess: if (!isDesktop()) { // No point in changing the opacity of the desktop setOpacity(qMax(opacity() - 0.1, 0.1)); } break; case Options::MouseClose: closeWindow(); break; case Options::MouseActivateRaiseAndMove: case Options::MouseActivateRaiseAndUnrestrictedMove: workspace()->raiseWindow(this); workspace()->requestFocus(this); workspace()->setActiveOutput(globalPos); // fallthrough case Options::MouseMove: case Options::MouseUnrestrictedMove: { if (!isMovableAcrossScreens()) { break; } if (isInteractiveMoveResize()) { finishInteractiveMoveResize(false); } setInteractiveMoveResizeGravity(Gravity::None); setInteractiveMoveResizePointerButtonDown(true); setInteractiveMoveOffset(QPoint(globalPos.x() - x(), globalPos.y() - y())); // map from global setInvertedInteractiveMoveOffset(rect().bottomRight() - interactiveMoveOffset()); setUnrestrictedInteractiveMoveResize((cmd == Options::MouseActivateRaiseAndUnrestrictedMove || cmd == Options::MouseUnrestrictedMove)); if (!startInteractiveMoveResize()) { setInteractiveMoveResizePointerButtonDown(false); } updateCursor(); break; } case Options::MouseResize: case Options::MouseUnrestrictedResize: { if (!isResizable() || isShade()) { break; } if (isInteractiveMoveResize()) { finishInteractiveMoveResize(false); } setInteractiveMoveResizePointerButtonDown(true); const QPoint moveOffset = QPoint(globalPos.x() - x(), globalPos.y() - y()); // map from global setInteractiveMoveOffset(moveOffset); int x = moveOffset.x(), y = moveOffset.y(); bool left = x < width() / 3; bool right = x >= 2 * width() / 3; bool top = y < height() / 3; bool bot = y >= 2 * height() / 3; Gravity gravity; if (top) { gravity = left ? Gravity::TopLeft : (right ? Gravity::TopRight : Gravity::Top); } else if (bot) { gravity = left ? Gravity::BottomLeft : (right ? Gravity::BottomRight : Gravity::Bottom); } else { gravity = (x < width() / 2) ? Gravity::Left : Gravity::Right; } setInteractiveMoveResizeGravity(gravity); setInvertedInteractiveMoveOffset(rect().bottomRight() - moveOffset); setUnrestrictedInteractiveMoveResize((cmd == Options::MouseUnrestrictedResize)); if (!startInteractiveMoveResize()) { setInteractiveMoveResizePointerButtonDown(false); } updateCursor(); break; } case Options::MouseShade: toggleShade(); cancelShadeHoverTimer(); break; case Options::MouseSetShade: setShade(ShadeNormal); cancelShadeHoverTimer(); break; case Options::MouseUnsetShade: setShade(ShadeNone); cancelShadeHoverTimer(); break; case Options::MouseNothing: default: replay = true; break; } return replay; } void Window::setTransientFor(Window *transientFor) { if (transientFor == this) { // cannot be transient for one self return; } if (m_transientFor == transientFor) { return; } m_transientFor = transientFor; Q_EMIT transientChanged(); } const Window *Window::transientFor() const { return m_transientFor; } Window *Window::transientFor() { return m_transientFor; } bool Window::hasTransientPlacementHint() const { return false; } QRect Window::transientPlacement(const QRect &bounds) const { Q_UNUSED(bounds); Q_UNREACHABLE(); return QRect(); } bool Window::hasTransient(const Window *c, bool indirect) const { Q_UNUSED(indirect); return c->transientFor() == this; } QList Window::mainWindows() const { if (const Window *t = transientFor()) { return QList{const_cast(t)}; } return QList(); } QList Window::allMainWindows() const { auto result = mainWindows(); for (const auto *window : result) { result += window->allMainWindows(); } return result; } void Window::setModal(bool m) { // Qt-3.2 can have even modal normal windows :( if (m_modal == m) { return; } m_modal = m; Q_EMIT modalChanged(); // Changing modality for a mapped window is weird (?) // _NET_WM_STATE_MODAL should possibly rather be _NET_WM_WINDOW_TYPE_MODAL_DIALOG } bool Window::isModal() const { return m_modal; } // check whether a transient should be actually kept above its mainwindow // there may be some special cases where this rule shouldn't be enfored static bool shouldKeepTransientAbove(const Window *parent, const Window *transient) { // #93832 - don't keep splashscreens above dialogs if (transient->isSplash() && parent->isDialog()) { return false; } // This is rather a hack for #76026. Don't keep non-modal dialogs above // the mainwindow, but only if they're group transient (since only such dialogs // have taskbar entry in Kicker). A proper way of doing this (both kwin and kicker) // needs to be found. if (transient->isDialog() && !transient->isModal() && transient->groupTransient()) { return false; } // #63223 - don't keep transients above docks, because the dock is kept high, // and e.g. dialogs for them would be too high too // ignore this if the transient has a placement hint which indicates it should go above it's parent if (parent->isDock() && !transient->hasTransientPlacementHint()) { return false; } return true; } void Window::addTransient(Window *cl) { Q_ASSERT(!m_transients.contains(cl)); Q_ASSERT(cl != this); m_transients.append(cl); if (shouldKeepTransientAbove(this, cl)) { workspace()->constrain(this, cl); } } void Window::removeTransient(Window *cl) { m_transients.removeAll(cl); if (cl->transientFor() == this) { cl->setTransientFor(nullptr); } workspace()->unconstrain(this, cl); } void Window::removeTransientFromList(Window *cl) { m_transients.removeAll(cl); } bool Window::isActiveFullScreen() const { if (!isFullScreen()) { return false; } const auto ac = workspace()->mostRecentlyActivatedWindow(); // instead of activeWindow() - avoids flicker // according to NETWM spec implementation notes suggests // "focused windows having state _NET_WM_STATE_FULLSCREEN" to be on the highest layer. // we'll also take the screen into account return ac && (ac == this || !ac->isOnOutput(output()) || ac->allMainWindows().contains(const_cast(this))); } #define BORDER(which) \ int Window::border##which() const \ { \ return isDecorated() ? decoration()->border##which() : 0; \ } BORDER(Bottom) BORDER(Left) BORDER(Right) BORDER(Top) #undef BORDER void Window::updateInitialMoveResizeGeometry() { m_interactiveMoveResize.initialGeometry = frameGeometry(); m_interactiveMoveResize.startOutput = output(); } void Window::updateCursor() { Gravity gravity = interactiveMoveResizeGravity(); if (!isResizable() || isShade()) { gravity = Gravity::None; } CursorShape c = Qt::ArrowCursor; switch (gravity) { case Gravity::TopLeft: c = KWin::ExtendedCursor::SizeNorthWest; break; case Gravity::BottomRight: c = KWin::ExtendedCursor::SizeSouthEast; break; case Gravity::BottomLeft: c = KWin::ExtendedCursor::SizeSouthWest; break; case Gravity::TopRight: c = KWin::ExtendedCursor::SizeNorthEast; break; case Gravity::Top: c = KWin::ExtendedCursor::SizeNorth; break; case Gravity::Bottom: c = KWin::ExtendedCursor::SizeSouth; break; case Gravity::Left: c = KWin::ExtendedCursor::SizeWest; break; case Gravity::Right: c = KWin::ExtendedCursor::SizeEast; break; default: if (isInteractiveMoveResize()) { c = Qt::SizeAllCursor; } else { c = Qt::ArrowCursor; } break; } if (c == m_interactiveMoveResize.cursor) { return; } m_interactiveMoveResize.cursor = c; Q_EMIT moveResizeCursorChanged(c); } void Window::leaveInteractiveMoveResize() { workspace()->setMoveResizeWindow(nullptr); setInteractiveMoveResize(false); if (ScreenEdges::self()->isDesktopSwitchingMovingClients()) { ScreenEdges::self()->reserveDesktopSwitching(false, Qt::Vertical | Qt::Horizontal); } if (isElectricBorderMaximizing()) { outline()->hide(); elevate(false); } } bool Window::doStartInteractiveMoveResize() { return true; } void Window::doFinishInteractiveMoveResize() { } bool Window::isWaitingForInteractiveMoveResizeSync() const { return false; } void Window::doInteractiveResizeSync() { } void Window::checkQuickTilingMaximizationZones(int xroot, int yroot) { QuickTileMode mode = QuickTileFlag::None; bool innerBorder = false; const auto outputs = kwinApp()->platform()->enabledOutputs(); for (const Output *output : outputs) { if (!output->geometry().contains(QPoint(xroot, yroot))) { continue; } auto isInScreen = [&output, &outputs](const QPoint &pt) { for (const Output *other : outputs) { if (other == output) { continue; } if (other->geometry().contains(pt)) { return true; } } return false; }; QRect area = workspace()->clientArea(MaximizeArea, this, QPoint(xroot, yroot)); if (options->electricBorderTiling()) { if (xroot <= area.x() + 20) { mode |= QuickTileFlag::Left; innerBorder = isInScreen(QPoint(area.x() - 1, yroot)); } else if (xroot >= area.x() + area.width() - 20) { mode |= QuickTileFlag::Right; innerBorder = isInScreen(QPoint(area.right() + 1, yroot)); } } if (mode != QuickTileMode(QuickTileFlag::None)) { if (yroot <= area.y() + area.height() * options->electricBorderCornerRatio()) { mode |= QuickTileFlag::Top; } else if (yroot >= area.y() + area.height() - area.height() * options->electricBorderCornerRatio()) { mode |= QuickTileFlag::Bottom; } } else if (options->electricBorderMaximize() && yroot <= area.y() + 5 && isMaximizable()) { mode = QuickTileFlag::Maximize; innerBorder = isInScreen(QPoint(xroot, area.y() - 1)); } break; // no point in checking other screens to contain this... "point"... } if (mode != electricBorderMode()) { setElectricBorderMode(mode); if (innerBorder) { if (!m_electricMaximizingDelay) { m_electricMaximizingDelay = new QTimer(this); m_electricMaximizingDelay->setInterval(250); m_electricMaximizingDelay->setSingleShot(true); connect(m_electricMaximizingDelay, &QTimer::timeout, this, [this]() { if (isInteractiveMove()) { setElectricBorderMaximizing(electricBorderMode() != QuickTileMode(QuickTileFlag::None)); } }); } m_electricMaximizingDelay->start(); } else { setElectricBorderMaximizing(mode != QuickTileMode(QuickTileFlag::None)); } } } void Window::keyPressEvent(uint key_code) { if (!isInteractiveMove() && !isInteractiveResize()) { return; } bool is_control = key_code & Qt::CTRL; bool is_alt = key_code & Qt::ALT; key_code = key_code & ~Qt::KeyboardModifierMask; int delta = is_control ? 1 : is_alt ? 32 : 8; QPoint pos = Cursors::self()->mouse()->pos(); switch (key_code) { case Qt::Key_Left: pos.rx() -= delta; break; case Qt::Key_Right: pos.rx() += delta; break; case Qt::Key_Up: pos.ry() -= delta; break; case Qt::Key_Down: pos.ry() += delta; break; case Qt::Key_Space: case Qt::Key_Return: case Qt::Key_Enter: setInteractiveMoveResizePointerButtonDown(false); finishInteractiveMoveResize(false); updateCursor(); break; case Qt::Key_Escape: setInteractiveMoveResizePointerButtonDown(false); finishInteractiveMoveResize(true); updateCursor(); break; default: return; } Cursors::self()->mouse()->setPos(pos); } QSize Window::resizeIncrements() const { return QSize(1, 1); } void Window::dontInteractiveMoveResize() { setInteractiveMoveResizePointerButtonDown(false); stopDelayedInteractiveMoveResize(); if (isInteractiveMoveResize()) { finishInteractiveMoveResize(false); } } Gravity Window::mouseGravity() const { if (isDecorated()) { switch (decoration()->sectionUnderMouse()) { case Qt::BottomLeftSection: return Gravity::BottomLeft; case Qt::BottomRightSection: return Gravity::BottomRight; case Qt::BottomSection: return Gravity::Bottom; case Qt::LeftSection: return Gravity::Left; case Qt::RightSection: return Gravity::Right; case Qt::TopSection: return Gravity::Top; case Qt::TopLeftSection: return Gravity::TopLeft; case Qt::TopRightSection: return Gravity::TopRight; default: return Gravity::None; } } return Gravity::None; } void Window::endInteractiveMoveResize() { setInteractiveMoveResizePointerButtonDown(false); stopDelayedInteractiveMoveResize(); if (isInteractiveMoveResize()) { finishInteractiveMoveResize(false); setInteractiveMoveResizeGravity(mouseGravity()); } updateCursor(); } void Window::setDecoration(QSharedPointer decoration) { if (m_decoration.decoration.data() == decoration) { return; } if (decoration) { QMetaObject::invokeMethod(decoration.data(), QOverload<>::of(&KDecoration2::Decoration::update), Qt::QueuedConnection); connect(decoration.data(), &KDecoration2::Decoration::shadowChanged, this, &Window::updateShadow); connect(decoration.data(), &KDecoration2::Decoration::bordersChanged, this, &Window::updateDecorationInputShape); connect(decoration.data(), &KDecoration2::Decoration::resizeOnlyBordersChanged, this, &Window::updateDecorationInputShape); connect(decoration.data(), &KDecoration2::Decoration::bordersChanged, this, [this]() { GeometryUpdatesBlocker blocker(this); const QRect oldGeometry = moveResizeGeometry(); if (!isShade()) { checkWorkspacePosition(oldGeometry); } Q_EMIT geometryShapeChanged(this, oldGeometry); }); connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::sizeChanged, this, &Window::updateDecorationInputShape); } m_decoration.decoration = decoration; updateDecorationInputShape(); Q_EMIT decorationChanged(); } void Window::updateDecorationInputShape() { if (!isDecorated()) { m_decoration.inputRegion = QRegion(); return; } const QMargins borders = decoration()->borders(); const QMargins resizeBorders = decoration()->resizeOnlyBorders(); const QRect innerRect = QRect(QPoint(borderLeft(), borderTop()), decoratedClient()->size()); const QRect outerRect = innerRect + borders + resizeBorders; m_decoration.inputRegion = QRegion(outerRect) - innerRect; } bool Window::decorationHasAlpha() const { if (!isDecorated() || decoration()->isOpaque()) { // either no decoration or decoration has alpha disabled return false; } return true; } void Window::triggerDecorationRepaint() { if (isDecorated()) { decoration()->update(); } } void Window::layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const { if (!isDecorated()) { return; } QRect r = decoration()->rect(); top = QRect(r.x(), r.y(), r.width(), borderTop()); bottom = QRect(r.x(), r.y() + r.height() - borderBottom(), r.width(), borderBottom()); left = QRect(r.x(), r.y() + top.height(), borderLeft(), r.height() - top.height() - bottom.height()); right = QRect(r.x() + r.width() - borderRight(), r.y() + top.height(), borderRight(), r.height() - top.height() - bottom.height()); } void Window::processDecorationMove(const QPoint &localPos, const QPoint &globalPos) { if (isInteractiveMoveResizePointerButtonDown()) { handleInteractiveMoveResize(localPos.x(), localPos.y(), globalPos.x(), globalPos.y()); return; } // TODO: handle modifiers Gravity newGravity = mouseGravity(); if (newGravity != interactiveMoveResizeGravity()) { setInteractiveMoveResizeGravity(newGravity); updateCursor(); } } bool Window::processDecorationButtonPress(QMouseEvent *event, bool ignoreMenu) { Options::MouseCommand com = Options::MouseNothing; bool active = isActive(); if (!wantsInput()) { // we cannot be active, use it anyway active = true; } // check whether it is a double click if (event->button() == Qt::LeftButton && titlebarPositionUnderMouse()) { if (m_decoration.doubleClickTimer.isValid()) { const qint64 interval = m_decoration.doubleClickTimer.elapsed(); m_decoration.doubleClickTimer.invalidate(); if (interval > QGuiApplication::styleHints()->mouseDoubleClickInterval()) { m_decoration.doubleClickTimer.start(); // expired -> new first click and pot. init } else { Workspace::self()->performWindowOperation(this, options->operationTitlebarDblClick()); dontInteractiveMoveResize(); return false; } } else { m_decoration.doubleClickTimer.start(); // new first click and pot. init, could be invalidated by release - see below } } if (event->button() == Qt::LeftButton) { com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1(); } else if (event->button() == Qt::MiddleButton) { com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2(); } else if (event->button() == Qt::RightButton) { com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3(); } if (event->button() == Qt::LeftButton && com != Options::MouseOperationsMenu // actions where it's not possible to get the matching && com != Options::MouseMinimize) // mouse release event { setInteractiveMoveResizeGravity(mouseGravity()); setInteractiveMoveResizePointerButtonDown(true); setInteractiveMoveOffset(event->pos()); setInvertedInteractiveMoveOffset(rect().bottomRight() - interactiveMoveOffset()); setUnrestrictedInteractiveMoveResize(false); startDelayedInteractiveMoveResize(); updateCursor(); } // In the new API the decoration may process the menu action to display an inactive tab's menu. // If the event is unhandled then the core will create one for the active window in the group. if (!ignoreMenu || com != Options::MouseOperationsMenu) { performMouseCommand(com, event->globalPos()); } return !( // Return events that should be passed to the decoration in the new API com == Options::MouseRaise || com == Options::MouseOperationsMenu || com == Options::MouseActivateAndRaise || com == Options::MouseActivate || com == Options::MouseActivateRaiseAndPassClick || com == Options::MouseActivateAndPassClick || com == Options::MouseNothing); } void Window::processDecorationButtonRelease(QMouseEvent *event) { if (isDecorated()) { if (event->isAccepted() || !titlebarPositionUnderMouse()) { invalidateDecorationDoubleClickTimer(); // click was for the deco and shall not init a doubleclick } } if (event->buttons() == Qt::NoButton) { setInteractiveMoveResizePointerButtonDown(false); stopDelayedInteractiveMoveResize(); if (isInteractiveMoveResize()) { finishInteractiveMoveResize(false); setInteractiveMoveResizeGravity(mouseGravity()); } updateCursor(); } } void Window::startDecorationDoubleClickTimer() { m_decoration.doubleClickTimer.start(); } void Window::invalidateDecorationDoubleClickTimer() { m_decoration.doubleClickTimer.invalidate(); } bool Window::providesContextHelp() const { return false; } void Window::showContextHelp() { } QPointer Window::decoratedClient() const { return m_decoration.client; } void Window::setDecoratedClient(QPointer client) { m_decoration.client = client; } void Window::pointerEnterEvent(const QPoint &globalPos) { if (options->isShadeHover()) { cancelShadeHoverTimer(); startShadeHoverTimer(); } if (options->focusPolicy() == Options::ClickToFocus || workspace()->userActionsMenu()->isShown()) { return; } if (options->isAutoRaise() && !isDesktop() && !isDock() && workspace()->focusChangeEnabled() && globalPos != workspace()->focusMousePosition() && workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop(), options->isSeparateScreenFocus() ? output() : nullptr) != this) { startAutoRaise(); } if (isDesktop() || isDock()) { return; } // for FocusFollowsMouse, change focus only if the mouse has actually been moved, not if the focus // change came because of window changes (e.g. closing a window) - #92290 if (options->focusPolicy() != Options::FocusFollowsMouse || globalPos != workspace()->focusMousePosition()) { workspace()->requestDelayFocus(this); } } void Window::pointerLeaveEvent() { cancelAutoRaise(); workspace()->cancelDelayFocus(); cancelShadeHoverTimer(); startShadeUnhoverTimer(); // TODO: send hover leave to deco // TODO: handle Options::FocusStrictlyUnderMouse } QRect Window::iconGeometry() const { if (!windowManagementInterface() || !waylandServer()) { // window management interface is only available if the surface is mapped return QRect(); } int minDistance = INT_MAX; Window *candidatePanel = nullptr; QRect candidateGeom; const auto minGeometries = windowManagementInterface()->minimizedGeometries(); for (auto i = minGeometries.constBegin(), end = minGeometries.constEnd(); i != end; ++i) { Window *panel = waylandServer()->findWindow(i.key()); if (!panel) { continue; } const int distance = QPoint(panel->pos() - pos()).manhattanLength(); if (distance < minDistance) { minDistance = distance; candidatePanel = panel; candidateGeom = i.value(); } } if (!candidatePanel) { return QRect(); } return candidateGeom.translated(candidatePanel->pos()); } QRect Window::virtualKeyboardGeometry() const { return m_virtualKeyboardGeometry; } void Window::setVirtualKeyboardGeometry(const QRect &geo) { // No keyboard anymore if (geo.isEmpty() && !m_keyboardGeometryRestore.isEmpty()) { const QRect availableArea = workspace()->clientArea(MaximizeArea, this); QRect newWindowGeometry = (maximizeMode() & MaximizeHorizontal) ? availableArea : m_keyboardGeometryRestore; moveResize(newWindowGeometry); m_keyboardGeometryRestore = QRect(); } else if (geo.isEmpty()) { return; // The keyboard has just been opened (rather than resized) save window geometry for a restore } else if (m_keyboardGeometryRestore.isEmpty()) { m_keyboardGeometryRestore = moveResizeGeometry(); } m_virtualKeyboardGeometry = geo; // Don't resize Desktop and fullscreen windows if (isFullScreen() || isDesktop()) { return; } if (!geo.intersects(m_keyboardGeometryRestore)) { return; } const QRect availableArea = workspace()->clientArea(MaximizeArea, this); QRect newWindowGeometry = (maximizeMode() & MaximizeHorizontal) ? availableArea : m_keyboardGeometryRestore; newWindowGeometry.moveBottom(geo.top()); newWindowGeometry.setTop(qMax(newWindowGeometry.top(), availableArea.top())); moveResize(newWindowGeometry); } QRect Window::keyboardGeometryRestore() const { return m_keyboardGeometryRestore; } void Window::setKeyboardGeometryRestore(const QRect &geom) { m_keyboardGeometryRestore = geom; } bool Window::dockWantsInput() const { return false; } void Window::setDesktopFileName(QByteArray name) { name = rules()->checkDesktopFile(name).toUtf8(); if (name == m_desktopFileName) { return; } m_desktopFileName = name; updateWindowRules(Rules::DesktopFile); Q_EMIT desktopFileNameChanged(); } QString Window::iconFromDesktopFile() const { return iconFromDesktopFile(QFile::decodeName(m_desktopFileName)); } QString Window::iconFromDesktopFile(const QString &desktopFileName) { if (desktopFileName.isEmpty()) { return {}; } const QString desktopFileNameWithPrefix = desktopFileName + QLatin1String(".desktop"); QString desktopFilePath; if (QDir::isAbsolutePath(desktopFileName)) { if (QFile::exists(desktopFileNameWithPrefix)) { desktopFilePath = desktopFileNameWithPrefix; } else { desktopFilePath = desktopFileName; } } if (desktopFilePath.isEmpty()) { desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, desktopFileNameWithPrefix); } if (desktopFilePath.isEmpty()) { desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, desktopFileName); } if (desktopFilePath.isEmpty()) { return {}; } KDesktopFile df(desktopFilePath); return df.readIcon(); } bool Window::hasApplicationMenu() const { return ApplicationMenu::self()->applicationMenuEnabled() && !m_applicationMenuServiceName.isEmpty() && !m_applicationMenuObjectPath.isEmpty(); } void Window::updateApplicationMenuServiceName(const QString &serviceName) { const bool old_hasApplicationMenu = hasApplicationMenu(); m_applicationMenuServiceName = serviceName; const bool new_hasApplicationMenu = hasApplicationMenu(); Q_EMIT applicationMenuChanged(); if (old_hasApplicationMenu != new_hasApplicationMenu) { Q_EMIT hasApplicationMenuChanged(new_hasApplicationMenu); } } void Window::updateApplicationMenuObjectPath(const QString &objectPath) { const bool old_hasApplicationMenu = hasApplicationMenu(); m_applicationMenuObjectPath = objectPath; const bool new_hasApplicationMenu = hasApplicationMenu(); Q_EMIT applicationMenuChanged(); if (old_hasApplicationMenu != new_hasApplicationMenu) { Q_EMIT hasApplicationMenuChanged(new_hasApplicationMenu); } } void Window::setApplicationMenuActive(bool applicationMenuActive) { if (m_applicationMenuActive != applicationMenuActive) { m_applicationMenuActive = applicationMenuActive; Q_EMIT applicationMenuActiveChanged(applicationMenuActive); } } void Window::showApplicationMenu(int actionId) { if (isDecorated()) { decoration()->showApplicationMenu(actionId); } else { // we don't know where the application menu button will be, show it in the top left corner instead Workspace::self()->showApplicationMenu(QRect(), this, actionId); } } bool Window::unresponsive() const { return m_unresponsive; } void Window::setUnresponsive(bool unresponsive) { if (m_unresponsive != unresponsive) { m_unresponsive = unresponsive; Q_EMIT unresponsiveChanged(m_unresponsive); Q_EMIT captionChanged(); } } QString Window::shortcutCaptionSuffix() const { if (shortcut().isEmpty()) { return QString(); } return QLatin1String(" {") + shortcut().toString() + QLatin1Char('}'); } Window *Window::findWindowWithSameCaption() const { auto fetchNameInternalPredicate = [this](const Window *cl) { return (!cl->isSpecialWindow() || cl->isToolbar()) && cl != this && cl->captionNormal() == captionNormal() && cl->captionSuffix() == captionSuffix(); }; return workspace()->findAbstractClient(fetchNameInternalPredicate); } QString Window::caption() const { QString cap = captionNormal() + captionSuffix(); if (unresponsive()) { cap += QLatin1String(" "); cap += i18nc("Application is not responding, appended to window title", "(Not Responding)"); } return cap; } void Window::removeRule(Rules *rule) { m_rules.remove(rule); } void Window::discardTemporaryRules() { m_rules.discardTemporary(); } void Window::evaluateWindowRules() { setupWindowRules(true); applyWindowRules(); } /** * Returns the list of activities the window window is on. * if it's on all activities, the list will be empty. * Don't use this, use isOnActivity() and friends (from class Window) */ QStringList Window::activities() const { return m_activityList; } /** * Sets whether the window 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 Window::setOnActivity(const QString &activity, bool enable) { #if 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 window is on */ void Window::setOnActivities(const QStringList &newActivitiesList) { #if KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } const auto allActivities = Activities::self()->all(); const auto activityList = [&] { auto result = rules()->checkActivity(newActivitiesList); const auto it = std::remove_if(result.begin(), result.end(), [=](const QString &activity) { return !allActivities.contains(activity); }); result.erase(it, result.end()); return result; }(); const auto allActivityExplicitlyRequested = activityList.isEmpty() || activityList.contains(Activities::nullUuid()); const auto allActivitiesCovered = activityList.size() > 1 && activityList.size() == allActivities.size(); if (allActivityExplicitlyRequested || allActivitiesCovered) { if (!m_activityList.isEmpty()) { m_activityList.clear(); doSetOnActivities(m_activityList); } } else { if (m_activityList != activityList) { m_activityList = activityList; doSetOnActivities(m_activityList); } } updateActivities(false); #else Q_UNUSED(newActivitiesList) #endif } /** * if @p all is true, sets on all activities. * if it's false, sets it to only be on the current activity */ void Window::setOnAllActivities(bool all) { #if KWIN_BUILD_ACTIVITIES if (all == isOnAllActivities()) { return; } if (all) { setOnActivities(QStringList()); } else { setOnActivity(Activities::self()->current(), true); } #else Q_UNUSED(all) #endif } /** * update after activities changed */ void Window::updateActivities(bool includeTransients) { if (m_activityUpdatesBlocked) { m_blockedActivityUpdatesRequireTransients |= includeTransients; return; } Q_EMIT activitiesChanged(this); m_blockedActivityUpdatesRequireTransients = false; // reset FocusChain::self()->update(this, FocusChain::MakeFirst); updateWindowRules(Rules::Activity); } void Window::blockActivityUpdates(bool b) { if (b) { ++m_activityUpdatesBlocked; } else { Q_ASSERT(m_activityUpdatesBlocked); --m_activityUpdatesBlocked; if (!m_activityUpdatesBlocked) { updateActivities(m_blockedActivityUpdatesRequireTransients); } } } void Window::checkNoBorder() { setNoBorder(false); } bool Window::groupTransient() const { return false; } const Group *Window::group() const { return nullptr; } Group *Window::group() { return nullptr; } bool Window::supportsWindowRules() const { return false; } QPoint Window::framePosToClientPos(const QPoint &point) const { return point + QPoint(borderLeft(), borderTop()); } QPoint Window::clientPosToFramePos(const QPoint &point) const { return point - QPoint(borderLeft(), borderTop()); } QSize Window::frameSizeToClientSize(const QSize &size) const { const int width = size.width() - borderLeft() - borderRight(); const int height = size.height() - borderTop() - borderBottom(); return QSize(width, height); } QSize Window::clientSizeToFrameSize(const QSize &size) const { const int width = size.width() + borderLeft() + borderRight(); const int height = size.height() + borderTop() + borderBottom(); return QSize(width, height); } QRect Window::frameRectToClientRect(const QRect &rect) const { const QPoint position = framePosToClientPos(rect.topLeft()); const QSize size = frameSizeToClientSize(rect.size()); return QRect(position, size); } QRect Window::clientRectToFrameRect(const QRect &rect) const { const QPoint position = clientPosToFramePos(rect.topLeft()); const QSize size = clientSizeToFrameSize(rect.size()); return QRect(position, size); } QRect Window::moveResizeGeometry() const { return m_moveResizeGeometry; } void Window::setMoveResizeGeometry(const QRect &geo) { m_moveResizeGeometry = geo; } void Window::move(const QPoint &point) { m_moveResizeGeometry.moveTopLeft(point); moveResizeInternal(m_moveResizeGeometry, MoveResizeMode::Move); } void Window::resize(const QSize &size) { m_moveResizeGeometry.setSize(size); moveResizeInternal(m_moveResizeGeometry, MoveResizeMode::Resize); } void Window::moveResize(const QRect &rect) { m_moveResizeGeometry = rect; moveResizeInternal(m_moveResizeGeometry, MoveResizeMode::MoveResize); } void Window::setElectricBorderMode(QuickTileMode mode) { if (mode != QuickTileMode(QuickTileFlag::Maximize)) { // sanitize the mode, ie. simplify "invalid" combinations if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal)) { mode &= ~QuickTileMode(QuickTileFlag::Horizontal); } if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical)) { mode &= ~QuickTileMode(QuickTileFlag::Vertical); } } m_electricMode = mode; } void Window::setElectricBorderMaximizing(bool maximizing) { m_electricMaximizing = maximizing; if (maximizing) { outline()->show(quickTileGeometry(electricBorderMode(), Cursors::self()->mouse()->pos()), moveResizeGeometry()); } else { outline()->hide(); } elevate(maximizing); } QRect Window::quickTileGeometry(QuickTileMode mode, const QPoint &pos) const { if (mode == QuickTileMode(QuickTileFlag::Maximize)) { if (maximizeMode() == MaximizeFull) { return geometryRestore(); } else { return workspace()->clientArea(MaximizeArea, this, pos); } } QRect ret = workspace()->clientArea(MaximizeArea, this, pos); if (mode & QuickTileFlag::Left) { ret.setRight(ret.left() + ret.width() / 2 - 1); } else if (mode & QuickTileFlag::Right) { ret.setLeft(ret.right() - (ret.width() - ret.width() / 2) + 1); } if (mode & QuickTileFlag::Top) { ret.setBottom(ret.top() + ret.height() / 2 - 1); } else if (mode & QuickTileFlag::Bottom) { ret.setTop(ret.bottom() - (ret.height() - ret.height() / 2) + 1); } return ret; } void Window::updateElectricGeometryRestore() { m_electricGeometryRestore = geometryRestore(); if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) { if (!(maximizeMode() & MaximizeHorizontal)) { m_electricGeometryRestore.setX(x()); m_electricGeometryRestore.setWidth(width()); } if (!(maximizeMode() & MaximizeVertical)) { m_electricGeometryRestore.setY(y()); m_electricGeometryRestore.setHeight(height()); } } } QRect Window::quickTileGeometryRestore() const { if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { // If the window is tiled, geometryRestore() already has a good value. return geometryRestore(); } if (isElectricBorderMaximizing()) { return m_electricGeometryRestore; } else { return moveResizeGeometry(); } } void Window::setQuickTileMode(QuickTileMode mode, bool keyboard) { // Only allow quick tile on a regular window. if (!isResizable()) { return; } workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event GeometryUpdatesBlocker blocker(this); if (mode == QuickTileMode(QuickTileFlag::Maximize)) { if (requestedMaximizeMode() == MaximizeFull) { m_quickTileMode = int(QuickTileFlag::None); setMaximize(false, false); } else { QRect effectiveGeometryRestore = quickTileGeometryRestore(); m_quickTileMode = int(QuickTileFlag::Maximize); setMaximize(true, true); setGeometryRestore(effectiveGeometryRestore); } doSetQuickTileMode(); Q_EMIT quickTileModeChanged(); return; } // sanitize the mode, ie. simplify "invalid" combinations if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal)) { mode &= ~QuickTileMode(QuickTileFlag::Horizontal); } if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical)) { mode &= ~QuickTileMode(QuickTileFlag::Vertical); } // restore from maximized so that it is possible to tile maximized windows with one hit or by dragging if (requestedMaximizeMode() != MaximizeRestore) { if (mode != QuickTileMode(QuickTileFlag::None)) { m_quickTileMode = int(QuickTileFlag::None); // Temporary, so the maximize code doesn't get all confused setMaximize(false, false); moveResize(quickTileGeometry(mode, keyboard ? moveResizeGeometry().center() : Cursors::self()->mouse()->pos())); // Store the mode change m_quickTileMode = mode; } else { m_quickTileMode = mode; setMaximize(false, false); } doSetQuickTileMode(); Q_EMIT quickTileModeChanged(); return; } if (mode != QuickTileMode(QuickTileFlag::None)) { QPoint whichScreen = keyboard ? moveResizeGeometry().center() : Cursors::self()->mouse()->pos(); // If trying to tile to the side that the window is already tiled to move the window to the next // screen if it exists, otherwise toggle the mode (set QuickTileFlag::None) if (quickTileMode() == mode) { const QVector outputs = kwinApp()->platform()->enabledOutputs(); const Output *currentOutput = output(); const Output *nextOutput = currentOutput; for (const Output *output : outputs) { if (output == currentOutput) { continue; } if (output->geometry().bottom() <= currentOutput->geometry().top() || output->geometry().top() >= currentOutput->geometry().bottom()) { continue; // not in horizontal line } const int x = output->geometry().center().x(); if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Left)) { if (x >= currentOutput->geometry().center().x() || (currentOutput != nextOutput && x <= nextOutput->geometry().center().x())) { continue; // not left of current or more left then found next } } else if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Right)) { if (x <= currentOutput->geometry().center().x() || (currentOutput != nextOutput && x >= nextOutput->geometry().center().x())) { continue; // not right of current or more right then found next } } nextOutput = output; } if (nextOutput == currentOutput) { mode = QuickTileFlag::None; // No other screens, toggle tiling } else { // Move to other screen moveResize(geometryRestore().translated(nextOutput->geometry().topLeft() - currentOutput->geometry().topLeft())); whichScreen = nextOutput->geometry().center(); // Swap sides if (mode & QuickTileFlag::Horizontal) { mode = (~mode & QuickTileFlag::Horizontal) | (mode & QuickTileFlag::Vertical); } } } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) { // Not coming out of an existing tile, not shifting monitors, we're setting a brand new tile. // Store geometry first, so we can go out of this tile later. setGeometryRestore(quickTileGeometryRestore()); } m_quickTileMode = mode; if (mode != QuickTileMode(QuickTileFlag::None)) { moveResize(quickTileGeometry(mode, whichScreen)); } } if (mode == QuickTileMode(QuickTileFlag::None)) { m_quickTileMode = int(QuickTileFlag::None); // Untiling, so just restore geometry, and we're done. if (geometryRestore().isValid()) { // invalid if we started maximized and wait for placement moveResize(geometryRestore()); } checkWorkspacePosition(); // Just in case it's a different screen } doSetQuickTileMode(); Q_EMIT quickTileModeChanged(); } void Window::doSetQuickTileMode() { } void Window::sendToOutput(Output *newOutput) { newOutput = rules()->checkOutput(newOutput); if (isActive()) { workspace()->setActiveOutput(newOutput); // might impact the layer of a fullscreen window const auto windows = workspace()->allClientList(); for (Window *other : windows) { if (other->isFullScreen() && other->output() == newOutput) { other->updateLayer(); } } } if (output() == newOutput) { // Don't use isOnScreen(), that's true even when only partially return; } GeometryUpdatesBlocker blocker(this); // operating on the maximized / quicktiled window would leave the old geom_restore behind, // so we clear the state first MaximizeMode maxMode = maximizeMode(); QuickTileMode qtMode = quickTileMode(); if (maxMode != MaximizeRestore) { maximize(MaximizeRestore); } if (qtMode != QuickTileMode(QuickTileFlag::None)) { setQuickTileMode(QuickTileFlag::None, true); } QRect oldScreenArea = workspace()->clientArea(MaximizeArea, this); QRect screenArea = workspace()->clientArea(MaximizeArea, this, newOutput); // the window can have its center so that the position correction moves the new center onto // the old screen, what will tile it where it is. Ie. the screen is not changed // this happens esp. with electric border quicktiling if (qtMode != QuickTileMode(QuickTileFlag::None)) { keepInArea(oldScreenArea); } QRect oldGeom = moveResizeGeometry(); QRect newGeom = oldGeom; // move the window to have the same relative position to the center of the screen // (i.e. one near the middle of the right edge will also end up near the middle of the right edge) QPoint center = newGeom.center() - oldScreenArea.center(); center.setX(center.x() * screenArea.width() / oldScreenArea.width()); center.setY(center.y() * screenArea.height() / oldScreenArea.height()); center += screenArea.center(); newGeom.moveCenter(center); moveResize(newGeom); // If the window was inside the old screen area, explicitly make sure its inside also the new screen area. // Calling checkWorkspacePosition() should ensure that, but when moving to a small screen the window could // be big enough to overlap outside of the new screen area, making struts from other screens come into effect, // which could alter the resulting geometry. if (oldScreenArea.contains(oldGeom)) { keepInArea(screenArea); } if (isFullScreen()) { updateGeometryRestoresForFullscreen(newOutput); checkWorkspacePosition(oldGeom); } else { // align geom_restore - checkWorkspacePosition operates on it setGeometryRestore(moveResizeGeometry()); checkWorkspacePosition(oldGeom); // re-align geom_restore to constrained geometry setGeometryRestore(moveResizeGeometry()); } // finally reset special states // NOTICE that MaximizeRestore/QuickTileFlag::None checks are required. // eg. setting QuickTileFlag::None would break maximization if (maxMode != MaximizeRestore) { maximize(maxMode); } if (qtMode != QuickTileMode(QuickTileFlag::None) && qtMode != quickTileMode()) { setQuickTileMode(qtMode, true); } auto tso = workspace()->ensureStackingOrder(transients()); for (auto it = tso.constBegin(), end = tso.constEnd(); it != end; ++it) { (*it)->sendToOutput(newOutput); } } void Window::updateGeometryRestoresForFullscreen(Output *output) { QRect screenArea = workspace()->clientArea(MaximizeArea, this, output); QRect newFullScreenGeometryRestore = screenArea; if (!(maximizeMode() & MaximizeVertical)) { newFullScreenGeometryRestore.setHeight(geometryRestore().height()); } if (!(maximizeMode() & MaximizeHorizontal)) { newFullScreenGeometryRestore.setWidth(geometryRestore().width()); } newFullScreenGeometryRestore.setSize(newFullScreenGeometryRestore.size().boundedTo(screenArea.size())); QSize move = (screenArea.size() - newFullScreenGeometryRestore.size()) / 2; newFullScreenGeometryRestore.translate(move.width(), move.height()); QRect newGeometryRestore = QRect(screenArea.topLeft(), geometryRestore().size().boundedTo(screenArea.size())); move = (screenArea.size() - newGeometryRestore.size()) / 2; newGeometryRestore.translate(move.width(), move.height()); setFullscreenGeometryRestore(newFullScreenGeometryRestore); setGeometryRestore(newGeometryRestore); } void Window::checkWorkspacePosition(QRect oldGeometry, const VirtualDesktop *oldDesktop) { if (isDock() || isDesktop() || !isPlaceable()) { return; } QRect newGeom = moveResizeGeometry(); if (!oldGeometry.isValid()) { oldGeometry = newGeom; } if (isRequestedFullScreen()) { moveResize(workspace()->clientArea(FullScreenArea, this, newGeom.center())); updateGeometryRestoresForFullscreen(kwinApp()->platform()->outputAt(newGeom.center())); return; } if (requestedMaximizeMode() != MaximizeRestore) { changeMaximize(false, false, true); // adjust size QRect geom = moveResizeGeometry(); const QRect screenArea = workspace()->clientArea(ScreenArea, this, geom.center()); checkOffscreenPosition(&geom, screenArea); moveResize(geom); return; } if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { moveResize(quickTileGeometry(quickTileMode(), moveResizeGeometry().center())); return; } // this can be true only if this window was mapped before KWin // was started - in such case, don't adjust position to workarea, // because the window already had its position, and if a window // with a strut altering the workarea would be managed in initialization // after this one, this window would be moved if (!workspace() || workspace()->initializing()) { return; } VirtualDesktop *desktop = !isOnCurrentDesktop() ? desktops().constLast() : VirtualDesktopManager::self()->currentDesktop(); if (!oldDesktop) { oldDesktop = desktop; } // If the window was touching an edge before but not now move it so it is again. // Old and new maximums have different starting values so windows on the screen // edge will move when a new strut is placed on the edge. QRect oldScreenArea; QRect screenArea; if (workspace()->inUpdateClientArea()) { // check if the window is on an about to be destroyed output Output *newOutput = output(); if (!kwinApp()->platform()->enabledOutputs().contains(newOutput)) { newOutput = kwinApp()->platform()->outputAt(newGeom.center()); } // we need to find the screen area as it was before the change oldScreenArea = workspace()->previousScreenSizes().value(output()); if (oldScreenArea.isNull()) { oldScreenArea = newOutput->geometry(); } screenArea = newOutput->geometry(); newGeom.translate(screenArea.topLeft() - oldScreenArea.topLeft()); } else { oldScreenArea = workspace()->clientArea(ScreenArea, kwinApp()->platform()->outputAt(oldGeometry.center()), oldDesktop); screenArea = workspace()->clientArea(ScreenArea, this, newGeom.center()); } const QRect oldGeomTall = QRect(oldGeometry.x(), oldScreenArea.y(), oldGeometry.width(), oldScreenArea.height()); // Full screen height const QRect oldGeomWide = QRect(oldScreenArea.x(), oldGeometry.y(), oldScreenArea.width(), oldGeometry.height()); // Full screen width int oldTopMax = oldScreenArea.y(); int oldRightMax = oldScreenArea.x() + oldScreenArea.width(); int oldBottomMax = oldScreenArea.y() + oldScreenArea.height(); int oldLeftMax = oldScreenArea.x(); int topMax = screenArea.y(); int rightMax = screenArea.x() + screenArea.width(); int bottomMax = screenArea.y() + screenArea.height(); int leftMax = screenArea.x(); const QRect newGeomTall = QRect(newGeom.x(), screenArea.y(), newGeom.width(), screenArea.height()); // Full screen height const QRect newGeomWide = QRect(screenArea.x(), newGeom.y(), screenArea.width(), newGeom.height()); // Full screen width // Get the max strut point for each side where the window is (E.g. Highest point for // the bottom struts bounded by the window's left and right sides). // These 4 compute old bounds ... auto moveAreaFunc = workspace()->inUpdateClientArea() ? &Workspace::previousRestrictedMoveArea : //... the restricted areas changed &Workspace::restrictedMoveArea; //... when e.g. active desktop or screen changes for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaTop)) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) { oldTopMax = qMax(oldTopMax, rect.y() + rect.height()); } } for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaRight)) { QRect rect = r & oldGeomWide; if (!rect.isEmpty()) { oldRightMax = qMin(oldRightMax, rect.x()); } } for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaBottom)) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) { oldBottomMax = qMin(oldBottomMax, rect.y()); } } for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaLeft)) { QRect rect = r & oldGeomWide; if (!rect.isEmpty()) { oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width()); } } // These 4 compute new bounds for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaTop)) { QRect rect = r & newGeomTall; if (!rect.isEmpty()) { topMax = qMax(topMax, rect.y() + rect.height()); } } for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaRight)) { QRect rect = r & newGeomWide; if (!rect.isEmpty()) { rightMax = qMin(rightMax, rect.x()); } } for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaBottom)) { QRect rect = r & newGeomTall; if (!rect.isEmpty()) { bottomMax = qMin(bottomMax, rect.y()); } } for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaLeft)) { QRect rect = r & newGeomWide; if (!rect.isEmpty()) { leftMax = qMax(leftMax, rect.x() + rect.width()); } } // Check if the sides were inside or touching but are no longer enum { Left = 0, Top, Right, Bottom, }; bool keep[4] = {false, false, false, false}; bool save[4] = {false, false, false, false}; if (oldGeometry.x() >= oldLeftMax) { save[Left] = newGeom.x() < leftMax; } if (oldGeometry.x() == oldLeftMax) { keep[Left] = newGeom.x() != leftMax; } if (oldGeometry.y() >= oldTopMax) { save[Top] = newGeom.y() < topMax; } if (oldGeometry.y() == oldTopMax) { keep[Top] = newGeom.y() != topMax; } if (oldGeometry.right() <= oldRightMax - 1) { save[Right] = newGeom.right() > rightMax - 1; } if (oldGeometry.right() == oldRightMax - 1) { keep[Right] = newGeom.right() != rightMax - 1; } if (oldGeometry.bottom() <= oldBottomMax - 1) { save[Bottom] = newGeom.bottom() > bottomMax - 1; } if (oldGeometry.bottom() == oldBottomMax - 1) { keep[Bottom] = newGeom.bottom() != bottomMax - 1; } // if randomly touches opposing edges, do not favor either if (keep[Left] && keep[Right]) { keep[Left] = keep[Right] = false; } if (keep[Top] && keep[Bottom]) { keep[Top] = keep[Bottom] = false; } if (save[Left] || keep[Left]) { newGeom.moveLeft(qMax(leftMax, screenArea.x())); } if (save[Top] || keep[Top]) { newGeom.moveTop(qMax(topMax, screenArea.y())); } if (save[Right] || keep[Right]) { newGeom.moveRight(qMin(rightMax - 1, screenArea.right())); } if (save[Bottom] || keep[Bottom]) { newGeom.moveBottom(qMin(bottomMax - 1, screenArea.bottom())); } if (oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax) { newGeom.setLeft(qMax(leftMax, screenArea.x())); } if (oldGeometry.y() >= oldTopMax && newGeom.y() < topMax) { newGeom.setTop(qMax(topMax, screenArea.y())); } checkOffscreenPosition(&newGeom, screenArea); // Obey size hints. TODO: We really should make sure it stays in the right place if (!isShade()) { newGeom.setSize(constrainFrameSize(newGeom.size())); } moveResize(newGeom); } void Window::checkOffscreenPosition(QRect *geom, const QRect &screenArea) { if (geom->left() > screenArea.right()) { geom->moveLeft(screenArea.right() - screenArea.width() / 4); } else if (geom->right() < screenArea.left()) { geom->moveRight(screenArea.left() + screenArea.width() / 4); } if (geom->top() > screenArea.bottom()) { geom->moveTop(screenArea.bottom() - screenArea.height() / 4); } else if (geom->bottom() < screenArea.top()) { geom->moveBottom(screenArea.top() + screenArea.width() / 4); } } /** * Returns the natural size of the window, if the window is not shaded it's the same * as size(). */ QSize Window::implicitSize() const { return clientSizeToFrameSize(clientSize()); } /** * Constrains the client size @p size according to a set of the window's size hints. * * Default implementation applies only minimum and maximum size constraints. */ QSize Window::constrainClientSize(const QSize &size, SizeMode mode) const { Q_UNUSED(mode) int width = size.width(); int height = size.height(); // When user is resizing the window, the move resize geometry may have negative width or // height. In which case, we need to set negative dimensions to reasonable values. if (width < 1) { width = 1; } if (height < 1) { height = 1; } const QSize minimumSize = minSize(); const QSize maximumSize = maxSize(); width = qBound(minimumSize.width(), width, maximumSize.width()); height = qBound(minimumSize.height(), height, maximumSize.height()); return QSize(width, height); } /** * Constrains the frame size @p size according to a set of the window's size hints. */ QSize Window::constrainFrameSize(const QSize &size, SizeMode mode) const { const QSize unconstrainedClientSize = frameSizeToClientSize(size); const QSize constrainedClientSize = constrainClientSize(unconstrainedClientSize, mode); return clientSizeToFrameSize(constrainedClientSize); } /** * Returns @c true if the Window can be shown in full screen mode; otherwise @c false. * * Default implementation returns @c false. */ bool Window::isFullScreenable() const { return false; } /** * Returns @c true if the Window is currently being shown in full screen mode; otherwise @c false. * * A window in full screen mode occupies the entire screen with no window frame around it. * * Default implementation returns @c false. */ bool Window::isFullScreen() const { return false; } bool Window::isRequestedFullScreen() const { return isFullScreen(); } /** * Returns whether requests initiated by the user to enter or leave full screen mode are honored. * * Default implementation returns @c false. */ bool Window::userCanSetFullScreen() const { return false; } /** * Asks the Window to enter or leave full screen mode. * * Default implementation does nothing. * * @param set @c true if the Window has to be shown in full screen mode, otherwise @c false * @param user @c true if the request is initiated by the user, otherwise @c false */ void Window::setFullScreen(bool set, bool user) { Q_UNUSED(set) Q_UNUSED(user) qCWarning(KWIN_CORE, "%s doesn't support setting fullscreen state", metaObject()->className()); } /** * Returns @c true if the Window can be minimized; otherwise @c false. * * Default implementation returns @c false. */ bool Window::isMinimizable() const { return false; } /** * Returns @c true if the Window can be maximized; otherwise @c false. * * Default implementation returns @c false. */ bool Window::isMaximizable() const { return false; } /** * Returns the currently applied maximize mode. * * Default implementation returns MaximizeRestore. */ MaximizeMode Window::maximizeMode() const { return MaximizeRestore; } /** * Returns the last requested maximize mode. * * On X11, this method always matches maximizeMode(). On Wayland, it is asynchronous. * * Default implementation matches maximizeMode(). */ MaximizeMode Window::requestedMaximizeMode() const { return maximizeMode(); } /** * Returns the geometry of the Window before it was maximized or quick tiled. */ QRect Window::geometryRestore() const { return m_maximizeGeometryRestore; } /** * Sets the geometry of the Window before it was maximized or quick tiled to @p rect. */ void Window::setGeometryRestore(const QRect &rect) { m_maximizeGeometryRestore = rect; } /** * Toggles the maximized state along specified dimensions @p horizontal and @p vertical. * * If @p adjust is @c true, only frame geometry will be updated to match requestedMaximizeMode(). * * Default implementation does nothing. */ void Window::changeMaximize(bool horizontal, bool vertical, bool adjust) { Q_UNUSED(horizontal) Q_UNUSED(vertical) Q_UNUSED(adjust) qCWarning(KWIN_CORE, "%s doesn't support setting maximized state", metaObject()->className()); } void Window::invalidateDecoration() { } bool Window::noBorder() const { return true; } bool Window::userCanSetNoBorder() const { return false; } void Window::setNoBorder(bool set) { Q_UNUSED(set) qCWarning(KWIN_CORE, "%s doesn't support setting decorations", metaObject()->className()); } void Window::showOnScreenEdge() { qCWarning(KWIN_CORE, "%s doesn't support screen edge activation", metaObject()->className()); } bool Window::isPlaceable() const { return true; } QRect Window::fullscreenGeometryRestore() const { return m_fullscreenGeometryRestore; } void Window::setFullscreenGeometryRestore(const QRect &geom) { m_fullscreenGeometryRestore = geom; } void Window::cleanTabBox() { #if KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); if (tabBox && tabBox->isDisplayed() && tabBox->currentClient() == this) { tabBox->nextPrev(true); } #endif } void Window::setupWindowRules(bool ignore_temporary) { disconnect(this, &Window::captionChanged, this, &Window::evaluateWindowRules); m_rules = RuleBook::self()->find(this, ignore_temporary); // check only after getting the rules, because there may be a rule forcing window type } void Window::updateWindowRules(Rules::Types selection) { if (RuleBook::self()->areUpdatesDisabled()) { return; } m_rules.update(this, selection); } void Window::finishWindowRules() { updateWindowRules(Rules::All); m_rules = WindowRules(); } // Applies Force, ForceTemporarily and ApplyNow rules // Used e.g. after the rules have been modified using the kcm. void Window::applyWindowRules() { // apply force rules // Placement - does need explicit update, just like some others below // Geometry : setGeometry() doesn't check rules auto client_rules = rules(); const QRect oldGeometry = moveResizeGeometry(); const QRect geometry = client_rules->checkGeometry(oldGeometry); if (geometry != oldGeometry) { moveResize(geometry); } // MinSize, MaxSize handled by Geometry // IgnoreGeometry setDesktops(desktops()); workspace()->sendWindowToOutput(this, output()); setOnActivities(activities()); // Type maximize(maximizeMode()); // Minimize : functions don't check, and there are two functions if (client_rules->checkMinimize(isMinimized())) { minimize(); } else { unminimize(); } setShade(shadeMode()); setOriginalSkipTaskbar(skipTaskbar()); setSkipPager(skipPager()); setSkipSwitcher(skipSwitcher()); setKeepAbove(keepAbove()); setKeepBelow(keepBelow()); setFullScreen(isFullScreen(), true); setNoBorder(noBorder()); updateColorScheme(); // FSP // AcceptFocus : if (workspace()->mostRecentlyActivatedWindow() == this && !client_rules->checkAcceptFocus(true)) { workspace()->activateNextWindow(this); } // Autogrouping : Only checked on window manage // AutogroupInForeground : Only checked on window manage // AutogroupById : Only checked on window manage // StrictGeometry setShortcut(rules()->checkShortcut(shortcut().toString())); // see also X11Window::setActive() if (isActive()) { setOpacity(rules()->checkOpacityActive(qRound(opacity() * 100.0)) / 100.0); workspace()->disableGlobalShortcutsForClient(rules()->checkDisableGlobalShortcuts(false)); } else { setOpacity(rules()->checkOpacityInactive(qRound(opacity() * 100.0)) / 100.0); } setDesktopFileName(rules()->checkDesktopFile(desktopFileName()).toUtf8()); } } // namespace KWin #include "moc_window.cpp"