File indexing completed on 2024-11-10 04:58:01
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org> 0006 SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 #include "window.h" 0011 0012 #if KWIN_BUILD_ACTIVITIES 0013 #include "activities.h" 0014 #endif 0015 #include "appmenu.h" 0016 #include "client_machine.h" 0017 #include "compositor.h" 0018 #include "core/output.h" 0019 #include "decorations/decoratedclient.h" 0020 #include "decorations/decorationpalette.h" 0021 #include "focuschain.h" 0022 #include "input.h" 0023 #include "outline.h" 0024 #include "placement.h" 0025 #include "scene/windowitem.h" 0026 #include "scene/workspacescene.h" 0027 #include "screenedge.h" 0028 #include "shadow.h" 0029 #if KWIN_BUILD_TABBOX 0030 #include "tabbox/tabbox.h" 0031 #endif 0032 #include "tiles/tilemanager.h" 0033 #include "useractions.h" 0034 #include "virtualdesktops.h" 0035 #include "wayland/output.h" 0036 #include "wayland/plasmawindowmanagement.h" 0037 #include "wayland/surface.h" 0038 #include "wayland_server.h" 0039 #include "workspace.h" 0040 0041 #include <KDecoration2/DecoratedClient> 0042 #include <KDecoration2/Decoration> 0043 #include <KDesktopFile> 0044 0045 #include <QDebug> 0046 #include <QDir> 0047 #include <QMouseEvent> 0048 #include <QStyleHints> 0049 0050 namespace KWin 0051 { 0052 0053 static inline int sign(int v) 0054 { 0055 return (v > 0) - (v < 0); 0056 } 0057 0058 QHash<QString, std::weak_ptr<Decoration::DecorationPalette>> Window::s_palettes; 0059 std::shared_ptr<Decoration::DecorationPalette> Window::s_defaultPalette; 0060 0061 Window::Window() 0062 : m_output(workspace()->activeOutput()) 0063 , ready_for_painting(false) 0064 , m_internalId(QUuid::createUuid()) 0065 , m_clientMachine(new ClientMachine(this)) 0066 , m_skipCloseAnimation(false) 0067 , m_colorScheme(QStringLiteral("kdeglobals")) 0068 , m_moveResizeOutput(workspace()->activeOutput()) 0069 { 0070 connect(this, &Window::bufferGeometryChanged, this, &Window::inputTransformationChanged); 0071 0072 connect(this, &Window::interactiveMoveResizeStarted, this, &Window::moveResizedChanged); 0073 connect(this, &Window::interactiveMoveResizeFinished, this, &Window::moveResizedChanged); 0074 0075 connect(this, &Window::windowShown, this, &Window::hiddenChanged); 0076 connect(this, &Window::windowHidden, this, &Window::hiddenChanged); 0077 0078 connect(this, &Window::paletteChanged, this, &Window::triggerDecorationRepaint); 0079 0080 // If the user manually moved the window, don't restore it after the keyboard closes 0081 connect(this, &Window::interactiveMoveResizeFinished, this, [this]() { 0082 m_keyboardGeometryRestore = QRectF(); 0083 }); 0084 connect(this, &Window::maximizedChanged, this, [this]() { 0085 m_keyboardGeometryRestore = QRectF(); 0086 }); 0087 connect(this, &Window::fullScreenChanged, this, [this]() { 0088 m_keyboardGeometryRestore = QRectF(); 0089 }); 0090 0091 // replace on-screen-display on size changes 0092 connect(this, &Window::frameGeometryChanged, this, [this](const QRectF &old) { 0093 if (isOnScreenDisplay() && !frameGeometry().isEmpty() && old.size() != frameGeometry().size() && isPlaceable()) { 0094 GeometryUpdatesBlocker blocker(this); 0095 workspace()->placement()->place(this, workspace()->clientArea(PlacementArea, this, workspace()->activeOutput())); 0096 } 0097 }); 0098 0099 connect(Workspace::self()->applicationMenu(), &ApplicationMenu::applicationMenuEnabledChanged, this, [this] { 0100 Q_EMIT hasApplicationMenuChanged(hasApplicationMenu()); 0101 }); 0102 connect(&m_offscreenFramecallbackTimer, &QTimer::timeout, this, &Window::maybeSendFrameCallback); 0103 } 0104 0105 Window::~Window() 0106 { 0107 if (m_tile) { 0108 m_tile->removeWindow(this); 0109 } 0110 Q_ASSERT(m_blockGeometryUpdates == 0); 0111 } 0112 0113 void Window::ref() 0114 { 0115 ++m_refCount; 0116 } 0117 0118 void Window::unref() 0119 { 0120 --m_refCount; 0121 if (m_refCount) { 0122 return; 0123 } 0124 if (m_deleted) { 0125 workspace()->removeDeleted(this); 0126 } 0127 delete this; 0128 } 0129 0130 QDebug operator<<(QDebug debug, const Window *window) 0131 { 0132 QDebugStateSaver saver(debug); 0133 debug.nospace(); 0134 if (window) { 0135 debug << window->metaObject()->className() << '(' << static_cast<const void *>(window); 0136 if (const SurfaceInterface *surface = window->surface()) { 0137 debug << ", surface=" << surface; 0138 } 0139 if (window->isClient()) { 0140 if (!window->isPopupWindow()) { 0141 debug << ", caption=" << window->caption(); 0142 } 0143 if (window->transientFor()) { 0144 debug << ", transientFor=" << window->transientFor(); 0145 } 0146 } 0147 if (debug.verbosity() > 2) { 0148 debug << ", frameGeometry=" << window->frameGeometry(); 0149 debug << ", resourceName=" << window->resourceName(); 0150 debug << ", resourceClass=" << window->resourceClass(); 0151 } 0152 debug << ')'; 0153 } else { 0154 debug << "Window(0x0)"; 0155 } 0156 return debug; 0157 } 0158 0159 QRectF Window::visibleGeometry() const 0160 { 0161 if (const WindowItem *item = windowItem()) { 0162 return item->mapToGlobal(item->boundingRect()); 0163 } 0164 return QRectF(); 0165 } 0166 0167 /** 0168 * Returns client machine for this window, 0169 * taken either from its window or from the leader window. 0170 */ 0171 QString Window::wmClientMachine(bool use_localhost) const 0172 { 0173 if (!m_clientMachine) { 0174 // this should never happen 0175 return QString(); 0176 } 0177 if (use_localhost && m_clientMachine->isLocal()) { 0178 // special name for the local machine (localhost) 0179 return ClientMachine::localhost(); 0180 } 0181 return m_clientMachine->hostName(); 0182 } 0183 0184 void Window::setResourceClass(const QString &name, const QString &className) 0185 { 0186 resource_name = name; 0187 resource_class = className; 0188 Q_EMIT windowClassChanged(); 0189 } 0190 0191 qreal Window::opacity() const 0192 { 0193 return m_opacity; 0194 } 0195 0196 void Window::setOpacity(qreal opacity) 0197 { 0198 opacity = std::clamp(opacity, 0.0, 1.0); 0199 if (m_opacity == opacity) { 0200 return; 0201 } 0202 const qreal oldOpacity = m_opacity; 0203 m_opacity = opacity; 0204 Q_EMIT opacityChanged(this, oldOpacity); 0205 } 0206 0207 bool Window::setupCompositing() 0208 { 0209 WorkspaceScene *scene = Compositor::self()->scene(); 0210 if (!scene) { 0211 return false; 0212 } 0213 0214 m_windowItem = createItem(scene); 0215 m_windowItem->setParentItem(scene->containerItem()); 0216 0217 connect(windowItem(), &WindowItem::positionChanged, this, &Window::visibleGeometryChanged); 0218 connect(windowItem(), &WindowItem::boundingRectChanged, this, &Window::visibleGeometryChanged); 0219 0220 return true; 0221 } 0222 0223 void Window::finishCompositing() 0224 { 0225 m_windowItem.reset(); 0226 } 0227 0228 void Window::setReadyForPainting() 0229 { 0230 if (!ready_for_painting) { 0231 ready_for_painting = true; 0232 Q_EMIT readyForPaintingChanged(); 0233 } 0234 } 0235 0236 Output *Window::output() const 0237 { 0238 return m_output; 0239 } 0240 0241 void Window::setOutput(Output *output) 0242 { 0243 if (m_output != output) { 0244 m_output = output; 0245 Q_EMIT outputChanged(); 0246 } 0247 } 0248 0249 bool Window::isOnActiveOutput() const 0250 { 0251 return isOnOutput(workspace()->activeOutput()); 0252 } 0253 0254 bool Window::isOnOutput(Output *output) const 0255 { 0256 return output->geometry().intersects(frameGeometry().toRect()); 0257 } 0258 0259 Shadow *Window::shadow() const 0260 { 0261 return m_shadow.get(); 0262 } 0263 0264 void Window::updateShadow() 0265 { 0266 if (m_shadow) { 0267 if (!m_shadow->updateShadow()) { 0268 m_shadow.reset(); 0269 } 0270 Q_EMIT shadowChanged(); 0271 } else { 0272 m_shadow = Shadow::createShadow(this); 0273 if (m_shadow) { 0274 Q_EMIT shadowChanged(); 0275 } 0276 } 0277 } 0278 0279 EffectWindow *Window::effectWindow() 0280 { 0281 return m_windowItem ? m_windowItem->effectWindow() : nullptr; 0282 } 0283 0284 const EffectWindow *Window::effectWindow() const 0285 { 0286 return m_windowItem ? m_windowItem->effectWindow() : nullptr; 0287 } 0288 0289 SurfaceItem *Window::surfaceItem() const 0290 { 0291 if (m_windowItem) { 0292 return m_windowItem->surfaceItem(); 0293 } 0294 return nullptr; 0295 } 0296 0297 bool Window::wantsShadowToBeRendered() const 0298 { 0299 return !isFullScreen() && maximizeMode() != MaximizeFull; 0300 } 0301 0302 bool Window::isClient() const 0303 { 0304 return false; 0305 } 0306 0307 bool Window::isUnmanaged() const 0308 { 0309 return false; 0310 } 0311 0312 void Window::elevate(bool elevate) 0313 { 0314 if (m_windowItem) { 0315 if (elevate) { 0316 m_windowItem->elevate(); 0317 } else { 0318 m_windowItem->deelevate(); 0319 } 0320 } 0321 } 0322 0323 pid_t Window::pid() const 0324 { 0325 return -1; 0326 } 0327 0328 bool Window::skipsCloseAnimation() const 0329 { 0330 return m_skipCloseAnimation; 0331 } 0332 0333 void Window::setSkipCloseAnimation(bool set) 0334 { 0335 if (set == m_skipCloseAnimation) { 0336 return; 0337 } 0338 m_skipCloseAnimation = set; 0339 Q_EMIT skipCloseAnimationChanged(); 0340 } 0341 0342 SurfaceInterface *Window::surface() const 0343 { 0344 return m_surface; 0345 } 0346 0347 void Window::setSurface(SurfaceInterface *surface) 0348 { 0349 if (m_surface == surface) { 0350 return; 0351 } 0352 m_surface = surface; 0353 Q_EMIT surfaceChanged(); 0354 } 0355 0356 int Window::stackingOrder() const 0357 { 0358 return m_stackingOrder; 0359 } 0360 0361 void Window::setStackingOrder(int order) 0362 { 0363 if (m_stackingOrder != order) { 0364 m_stackingOrder = order; 0365 Q_EMIT stackingOrderChanged(); 0366 } 0367 } 0368 0369 QString Window::windowRole() const 0370 { 0371 return QString(); 0372 } 0373 0374 QMatrix4x4 Window::inputTransformation() const 0375 { 0376 QMatrix4x4 m; 0377 m.translate(-m_bufferGeometry.x(), -m_bufferGeometry.y()); 0378 return m; 0379 } 0380 0381 bool Window::hitTest(const QPointF &point) const 0382 { 0383 if (isDecorated()) { 0384 if (m_decoration.inputRegion.contains(flooredPoint(mapToFrame(point)))) { 0385 return true; 0386 } 0387 } 0388 if (m_surface && m_surface->isMapped()) { 0389 return m_surface->inputSurfaceAt(mapToLocal(point)); 0390 } 0391 return exclusiveContains(m_bufferGeometry, point); 0392 } 0393 0394 QPointF Window::mapToFrame(const QPointF &point) const 0395 { 0396 return point - frameGeometry().topLeft(); 0397 } 0398 0399 QPointF Window::mapToLocal(const QPointF &point) const 0400 { 0401 return point - bufferGeometry().topLeft(); 0402 } 0403 0404 QPointF Window::mapFromLocal(const QPointF &point) const 0405 { 0406 return point + bufferGeometry().topLeft(); 0407 } 0408 0409 bool Window::isLocalhost() const 0410 { 0411 if (!m_clientMachine) { 0412 return true; 0413 } 0414 return m_clientMachine->isLocal(); 0415 } 0416 0417 QMargins Window::frameMargins() const 0418 { 0419 return QMargins(borderLeft(), borderTop(), borderRight(), borderBottom()); 0420 } 0421 0422 void Window::updateMouseGrab() 0423 { 0424 } 0425 0426 bool Window::belongToSameApplication(const Window *c1, const Window *c2, SameApplicationChecks checks) 0427 { 0428 return c1->belongsToSameApplication(c2, checks); 0429 } 0430 0431 xcb_timestamp_t Window::userTime() const 0432 { 0433 return XCB_TIME_CURRENT_TIME; 0434 } 0435 0436 void Window::setSkipSwitcher(bool set) 0437 { 0438 set = rules()->checkSkipSwitcher(set); 0439 if (set == skipSwitcher()) { 0440 return; 0441 } 0442 m_skipSwitcher = set; 0443 doSetSkipSwitcher(); 0444 updateWindowRules(Rules::SkipSwitcher); 0445 Q_EMIT skipSwitcherChanged(); 0446 } 0447 0448 void Window::setSkipPager(bool b) 0449 { 0450 b = rules()->checkSkipPager(b); 0451 if (b == skipPager()) { 0452 return; 0453 } 0454 m_skipPager = b; 0455 doSetSkipPager(); 0456 updateWindowRules(Rules::SkipPager); 0457 Q_EMIT skipPagerChanged(); 0458 } 0459 0460 void Window::doSetSkipPager() 0461 { 0462 } 0463 0464 void Window::setSkipTaskbar(bool b) 0465 { 0466 int was_wants_tab_focus = wantsTabFocus(); 0467 if (b == skipTaskbar()) { 0468 return; 0469 } 0470 m_skipTaskbar = b; 0471 doSetSkipTaskbar(); 0472 updateWindowRules(Rules::SkipTaskbar); 0473 if (was_wants_tab_focus != wantsTabFocus()) { 0474 Workspace::self()->focusChain()->update(this, isActive() ? FocusChain::MakeFirst : FocusChain::Update); 0475 } 0476 Q_EMIT skipTaskbarChanged(); 0477 } 0478 0479 void Window::setOriginalSkipTaskbar(bool b) 0480 { 0481 m_originalSkipTaskbar = rules()->checkSkipTaskbar(b); 0482 setSkipTaskbar(m_originalSkipTaskbar); 0483 } 0484 0485 void Window::doSetSkipTaskbar() 0486 { 0487 } 0488 0489 void Window::doSetSkipSwitcher() 0490 { 0491 } 0492 0493 void Window::setIcon(const QIcon &icon) 0494 { 0495 m_icon = icon; 0496 Q_EMIT iconChanged(); 0497 } 0498 0499 void Window::setActive(bool act) 0500 { 0501 if (isDeleted()) { 0502 return; 0503 } 0504 if (m_active == act) { 0505 return; 0506 } 0507 m_active = act; 0508 const int ruledOpacity = m_active 0509 ? rules()->checkOpacityActive(qRound(opacity() * 100.0)) 0510 : rules()->checkOpacityInactive(qRound(opacity() * 100.0)); 0511 setOpacity(ruledOpacity / 100.0); 0512 workspace()->setActiveWindow(act ? this : nullptr); 0513 0514 if (!m_active) { 0515 cancelAutoRaise(); 0516 } 0517 0518 if (!m_active && shadeMode() == ShadeActivated) { 0519 setShade(ShadeNormal); 0520 } 0521 0522 StackingUpdatesBlocker blocker(workspace()); 0523 updateLayer(); // active windows may get different layer 0524 auto mainwindows = mainWindows(); 0525 for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) { 0526 if ((*it)->isFullScreen()) { // fullscreens go high even if their transient is active 0527 (*it)->updateLayer(); 0528 } 0529 } 0530 0531 doSetActive(); 0532 Q_EMIT activeChanged(); 0533 updateMouseGrab(); 0534 } 0535 0536 void Window::doSetActive() 0537 { 0538 } 0539 0540 bool Window::isDeleted() const 0541 { 0542 return m_deleted; 0543 } 0544 0545 void Window::markAsDeleted() 0546 { 0547 Q_ASSERT(!m_deleted); 0548 m_deleted = true; 0549 workspace()->addDeleted(this); 0550 } 0551 0552 Layer Window::layer() const 0553 { 0554 if (m_layer == UnknownLayer) { 0555 const_cast<Window *>(this)->m_layer = rules()->checkLayer(belongsToLayer()); 0556 } 0557 return m_layer; 0558 } 0559 0560 void Window::updateLayer() 0561 { 0562 if (isDeleted()) { 0563 return; 0564 } 0565 if (layer() == rules()->checkLayer(belongsToLayer())) { 0566 return; 0567 } 0568 StackingUpdatesBlocker blocker(workspace()); 0569 m_layer = UnknownLayer; // invalidate, will be updated when doing restacking 0570 for (auto it = transients().constBegin(), end = transients().constEnd(); it != end; ++it) { 0571 (*it)->updateLayer(); 0572 } 0573 } 0574 0575 Layer Window::belongsToLayer() const 0576 { 0577 if (isUnmanaged() || isInternal()) { 0578 return OverlayLayer; 0579 } 0580 if (isLockScreen() && !waylandServer()) { 0581 return OverlayLayer; 0582 } 0583 if (isInputMethod()) { 0584 return OverlayLayer; 0585 } 0586 if (isLockScreenOverlay() && waylandServer() && waylandServer()->isScreenLocked()) { 0587 return OverlayLayer; 0588 } 0589 if (isDesktop()) { 0590 return DesktopLayer; 0591 } 0592 if (isSplash()) { // no damn annoying splashscreens 0593 return NormalLayer; // getting in the way of everything else 0594 } 0595 if (isDock() || isAppletPopup()) { 0596 return AboveLayer; 0597 } 0598 if (isPopupWindow()) { 0599 return PopupLayer; 0600 } 0601 if (isOnScreenDisplay()) { 0602 return OnScreenDisplayLayer; 0603 } 0604 if (isNotification()) { 0605 return NotificationLayer; 0606 } 0607 if (isCriticalNotification()) { 0608 return CriticalNotificationLayer; 0609 } 0610 if (keepBelow()) { 0611 return BelowLayer; 0612 } 0613 if (isActiveFullScreen()) { 0614 return ActiveLayer; 0615 } 0616 if (keepAbove()) { 0617 return AboveLayer; 0618 } 0619 0620 return NormalLayer; 0621 } 0622 0623 bool Window::belongsToDesktop() const 0624 { 0625 return false; 0626 } 0627 0628 void Window::setKeepAbove(bool b) 0629 { 0630 b = rules()->checkKeepAbove(b); 0631 if (b && !rules()->checkKeepBelow(false)) { 0632 setKeepBelow(false); 0633 } 0634 if (b == keepAbove()) { 0635 return; 0636 } 0637 m_keepAbove = b; 0638 doSetKeepAbove(); 0639 updateLayer(); 0640 updateWindowRules(Rules::Above); 0641 0642 Q_EMIT keepAboveChanged(m_keepAbove); 0643 } 0644 0645 void Window::doSetKeepAbove() 0646 { 0647 } 0648 0649 void Window::setKeepBelow(bool b) 0650 { 0651 b = rules()->checkKeepBelow(b); 0652 if (b && !rules()->checkKeepAbove(false)) { 0653 setKeepAbove(false); 0654 } 0655 if (b == keepBelow()) { 0656 return; 0657 } 0658 m_keepBelow = b; 0659 doSetKeepBelow(); 0660 updateLayer(); 0661 updateWindowRules(Rules::Below); 0662 0663 Q_EMIT keepBelowChanged(m_keepBelow); 0664 } 0665 0666 void Window::doSetKeepBelow() 0667 { 0668 } 0669 0670 void Window::startAutoRaise() 0671 { 0672 delete m_autoRaiseTimer; 0673 m_autoRaiseTimer = new QTimer(this); 0674 connect(m_autoRaiseTimer, &QTimer::timeout, this, &Window::autoRaise); 0675 m_autoRaiseTimer->setSingleShot(true); 0676 m_autoRaiseTimer->start(options->autoRaiseInterval()); 0677 } 0678 0679 void Window::cancelAutoRaise() 0680 { 0681 delete m_autoRaiseTimer; 0682 m_autoRaiseTimer = nullptr; 0683 } 0684 0685 void Window::autoRaise() 0686 { 0687 workspace()->raiseWindow(this); 0688 cancelAutoRaise(); 0689 } 0690 0691 bool Window::isMostRecentlyRaised() const 0692 { 0693 // The last window in the unconstrained stacking order is the most recently raised one. 0694 return workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop(), nullptr, true, false) == this; 0695 } 0696 0697 bool Window::wantsTabFocus() const 0698 { 0699 return (isNormalWindow() || isDialog() || isAppletPopup()) && wantsInput(); 0700 } 0701 0702 bool Window::isSpecialWindow() const 0703 { 0704 // TODO 0705 return isDesktop() || isDock() || isSplash() || isToolbar() || isNotification() || isOnScreenDisplay() || isCriticalNotification(); 0706 } 0707 0708 void Window::demandAttention(bool set) 0709 { 0710 if (isActive()) { 0711 set = false; 0712 } 0713 if (m_demandsAttention == set) { 0714 return; 0715 } 0716 m_demandsAttention = set; 0717 doSetDemandsAttention(); 0718 workspace()->windowAttentionChanged(this, set); 0719 Q_EMIT demandsAttentionChanged(); 0720 } 0721 0722 void Window::doSetDemandsAttention() 0723 { 0724 } 0725 0726 void Window::setDesktops(QList<VirtualDesktop *> desktops) 0727 { 0728 // on x11 we can have only one desktop at a time 0729 if (kwinApp()->operationMode() == Application::OperationModeX11 && desktops.size() > 1) { 0730 desktops = QList<VirtualDesktop *>({desktops.last()}); 0731 } 0732 0733 desktops = rules()->checkDesktops(desktops); 0734 if (desktops == m_desktops) { 0735 return; 0736 } 0737 0738 m_desktops = desktops; 0739 0740 if (windowManagementInterface()) { 0741 if (m_desktops.isEmpty()) { 0742 windowManagementInterface()->setOnAllDesktops(true); 0743 } else { 0744 windowManagementInterface()->setOnAllDesktops(false); 0745 auto currentDesktops = windowManagementInterface()->plasmaVirtualDesktops(); 0746 for (auto desktop : std::as_const(m_desktops)) { 0747 if (!currentDesktops.contains(desktop->id())) { 0748 windowManagementInterface()->addPlasmaVirtualDesktop(desktop->id()); 0749 } else { 0750 currentDesktops.removeOne(desktop->id()); 0751 } 0752 } 0753 for (const auto &desktopId : std::as_const(currentDesktops)) { 0754 windowManagementInterface()->removePlasmaVirtualDesktop(desktopId); 0755 } 0756 } 0757 } 0758 0759 auto transients_stacking_order = workspace()->ensureStackingOrder(transients()); 0760 for (auto it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) { 0761 (*it)->setDesktops(desktops); 0762 } 0763 0764 if (isModal()) // if a modal dialog is moved, move the mainwindow with it as otherwise 0765 // the (just moved) modal dialog will confusingly return to the mainwindow with 0766 // the next desktop change 0767 { 0768 const auto windows = mainWindows(); 0769 for (Window *other : windows) { 0770 other->setDesktops(desktops); 0771 } 0772 } 0773 0774 doSetDesktop(); 0775 0776 Workspace::self()->focusChain()->update(this, FocusChain::MakeFirst); 0777 updateWindowRules(Rules::Desktops); 0778 0779 Q_EMIT desktopsChanged(); 0780 } 0781 0782 void Window::doSetDesktop() 0783 { 0784 } 0785 0786 void Window::enterDesktop(VirtualDesktop *virtualDesktop) 0787 { 0788 if (m_desktops.contains(virtualDesktop)) { 0789 return; 0790 } 0791 auto desktops = m_desktops; 0792 desktops.append(virtualDesktop); 0793 setDesktops(desktops); 0794 } 0795 0796 void Window::leaveDesktop(VirtualDesktop *virtualDesktop) 0797 { 0798 QList<VirtualDesktop *> currentDesktops; 0799 if (m_desktops.isEmpty()) { 0800 currentDesktops = VirtualDesktopManager::self()->desktops(); 0801 } else { 0802 currentDesktops = m_desktops; 0803 } 0804 0805 if (!currentDesktops.contains(virtualDesktop)) { 0806 return; 0807 } 0808 auto desktops = currentDesktops; 0809 desktops.removeOne(virtualDesktop); 0810 setDesktops(desktops); 0811 } 0812 0813 void Window::setOnAllDesktops(bool b) 0814 { 0815 if (b == isOnAllDesktops()) { 0816 return; 0817 } 0818 if (b) { 0819 setDesktops({}); 0820 } else { 0821 setDesktops({VirtualDesktopManager::self()->currentDesktop()}); 0822 } 0823 } 0824 0825 QList<VirtualDesktop *> Window::desktops() const 0826 { 0827 return m_desktops; 0828 } 0829 0830 QStringList Window::desktopIds() const 0831 { 0832 const auto desks = desktops(); 0833 QStringList ids; 0834 ids.reserve(desks.count()); 0835 std::transform(desks.constBegin(), desks.constEnd(), 0836 std::back_inserter(ids), 0837 [](const VirtualDesktop *vd) { 0838 return vd->id(); 0839 }); 0840 return ids; 0841 } 0842 0843 bool Window::isOnDesktop(VirtualDesktop *desktop) const 0844 { 0845 return isOnAllDesktops() || desktops().contains(desktop); 0846 } 0847 0848 bool Window::isOnCurrentDesktop() const 0849 { 0850 return isOnDesktop(VirtualDesktopManager::self()->currentDesktop()); 0851 } 0852 0853 ShadeMode Window::shadeMode() const 0854 { 0855 return m_shadeMode; 0856 } 0857 0858 bool Window::isShadeable() const 0859 { 0860 return false; 0861 } 0862 0863 void Window::setShade(bool set) 0864 { 0865 set ? setShade(ShadeNormal) : setShade(ShadeNone); 0866 } 0867 0868 void Window::setShade(ShadeMode mode) 0869 { 0870 if (!isShadeable()) { 0871 return; 0872 } 0873 if (mode == ShadeHover && isInteractiveMove()) { 0874 return; // causes geometry breaks and is probably nasty 0875 } 0876 if (isSpecialWindow() || !isDecorated()) { 0877 mode = ShadeNone; 0878 } 0879 0880 mode = rules()->checkShade(mode); 0881 if (m_shadeMode == mode) { 0882 return; 0883 } 0884 0885 const bool wasShade = isShade(); 0886 const ShadeMode previousShadeMode = shadeMode(); 0887 m_shadeMode = mode; 0888 0889 if (wasShade == isShade()) { 0890 // Decoration may want to update after e.g. hover-shade changes 0891 Q_EMIT shadeChanged(); 0892 return; // No real change in shaded state 0893 } 0894 0895 Q_ASSERT(isDecorated()); 0896 GeometryUpdatesBlocker blocker(this); 0897 0898 doSetShade(previousShadeMode); 0899 updateWindowRules(Rules::Shade); 0900 0901 Q_EMIT shadeChanged(); 0902 } 0903 0904 void Window::doSetShade(ShadeMode previousShadeMode) 0905 { 0906 } 0907 0908 void Window::shadeHover() 0909 { 0910 setShade(ShadeHover); 0911 cancelShadeHoverTimer(); 0912 } 0913 0914 void Window::shadeUnhover() 0915 { 0916 setShade(ShadeNormal); 0917 cancelShadeHoverTimer(); 0918 } 0919 0920 void Window::startShadeHoverTimer() 0921 { 0922 if (!isShade()) { 0923 return; 0924 } 0925 m_shadeHoverTimer = new QTimer(this); 0926 connect(m_shadeHoverTimer, &QTimer::timeout, this, &Window::shadeHover); 0927 m_shadeHoverTimer->setSingleShot(true); 0928 m_shadeHoverTimer->start(options->shadeHoverInterval()); 0929 } 0930 0931 void Window::startShadeUnhoverTimer() 0932 { 0933 if (m_shadeMode == ShadeHover && !isInteractiveMoveResize() && !isInteractiveMoveResizePointerButtonDown()) { 0934 m_shadeHoverTimer = new QTimer(this); 0935 connect(m_shadeHoverTimer, &QTimer::timeout, this, &Window::shadeUnhover); 0936 m_shadeHoverTimer->setSingleShot(true); 0937 m_shadeHoverTimer->start(options->shadeHoverInterval()); 0938 } 0939 } 0940 0941 void Window::cancelShadeHoverTimer() 0942 { 0943 delete m_shadeHoverTimer; 0944 m_shadeHoverTimer = nullptr; 0945 } 0946 0947 void Window::toggleShade() 0948 { 0949 // If the mode is ShadeHover or ShadeActive, cancel shade too. 0950 setShade(shadeMode() == ShadeNone ? ShadeNormal : ShadeNone); 0951 } 0952 0953 Qt::Edge Window::titlebarPosition() const 0954 { 0955 // TODO: still needed, remove? 0956 return Qt::TopEdge; 0957 } 0958 0959 bool Window::titlebarPositionUnderMouse() const 0960 { 0961 if (!isDecorated()) { 0962 return false; 0963 } 0964 const auto sectionUnderMouse = decoration()->sectionUnderMouse(); 0965 if (sectionUnderMouse == Qt::TitleBarArea) { 0966 return true; 0967 } 0968 // check other sections based on titlebarPosition 0969 switch (titlebarPosition()) { 0970 case Qt::TopEdge: 0971 return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::TopSection || sectionUnderMouse == Qt::TopRightSection); 0972 case Qt::LeftEdge: 0973 return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::LeftSection || sectionUnderMouse == Qt::BottomLeftSection); 0974 case Qt::RightEdge: 0975 return (sectionUnderMouse == Qt::BottomRightSection || sectionUnderMouse == Qt::RightSection || sectionUnderMouse == Qt::TopRightSection); 0976 case Qt::BottomEdge: 0977 return (sectionUnderMouse == Qt::BottomLeftSection || sectionUnderMouse == Qt::BottomSection || sectionUnderMouse == Qt::BottomRightSection); 0978 default: 0979 // nothing 0980 return false; 0981 } 0982 } 0983 0984 void Window::setMinimized(bool set) 0985 { 0986 const bool effectiveSet = rules()->checkMinimize(set); 0987 if (m_minimized == effectiveSet) { 0988 return; 0989 } 0990 0991 if (effectiveSet && !isMinimizable()) { 0992 return; 0993 } 0994 0995 m_minimized = effectiveSet; 0996 doMinimize(); 0997 0998 updateWindowRules(Rules::Minimize); 0999 Q_EMIT minimizedChanged(); 1000 } 1001 1002 void Window::doMinimize() 1003 { 1004 } 1005 1006 QPalette Window::palette() 1007 { 1008 ensurePalette(); 1009 return m_palette->palette(); 1010 } 1011 1012 const Decoration::DecorationPalette *Window::decorationPalette() 1013 { 1014 ensurePalette(); 1015 return m_palette.get(); 1016 } 1017 1018 QString Window::preferredColorScheme() const 1019 { 1020 return rules()->checkDecoColor(QString()); 1021 } 1022 1023 QString Window::colorScheme() const 1024 { 1025 return m_colorScheme; 1026 } 1027 1028 void Window::setColorScheme(const QString &colorScheme) 1029 { 1030 QString requestedColorScheme = colorScheme; 1031 if (requestedColorScheme.isEmpty()) { 1032 requestedColorScheme = QStringLiteral("kdeglobals"); 1033 } 1034 1035 if (m_colorScheme == requestedColorScheme) { 1036 return; 1037 } 1038 1039 m_colorScheme = requestedColorScheme; 1040 1041 if (m_palette) { 1042 disconnect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &Window::handlePaletteChange); 1043 m_palette.reset(); 1044 1045 // If there already was a palette, re-create it right away 1046 // so the signals for repainting the decoration are emitted. 1047 ensurePalette(); 1048 } 1049 1050 Q_EMIT colorSchemeChanged(); 1051 } 1052 1053 void Window::updateColorScheme() 1054 { 1055 setColorScheme(preferredColorScheme()); 1056 } 1057 1058 void Window::ensurePalette() 1059 { 1060 if (m_palette) { 1061 return; 1062 } 1063 1064 auto it = s_palettes.find(m_colorScheme); 1065 1066 if (it == s_palettes.end() || it->expired()) { 1067 m_palette = std::make_shared<Decoration::DecorationPalette>(m_colorScheme); 1068 if (m_palette->isValid()) { 1069 s_palettes[m_colorScheme] = m_palette; 1070 } else { 1071 if (!s_defaultPalette) { 1072 s_defaultPalette = std::make_shared<Decoration::DecorationPalette>(QStringLiteral("kdeglobals")); 1073 s_palettes[QStringLiteral("kdeglobals")] = s_defaultPalette; 1074 } 1075 1076 m_palette = s_defaultPalette; 1077 } 1078 1079 if (m_colorScheme == QStringLiteral("kdeglobals")) { 1080 s_defaultPalette = m_palette; 1081 } 1082 } else { 1083 m_palette = it->lock(); 1084 } 1085 1086 connect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &Window::handlePaletteChange); 1087 1088 handlePaletteChange(); 1089 } 1090 1091 void Window::handlePaletteChange() 1092 { 1093 Q_EMIT paletteChanged(palette()); 1094 } 1095 1096 QRectF Window::keepInArea(QRectF geometry, QRectF area, bool partial) 1097 { 1098 if (partial) { 1099 // increase the area so that can have only 100 pixels in the area 1100 const QRectF geometry = moveResizeGeometry(); 1101 area.setLeft(std::min(area.left() - geometry.width() + 100, area.left())); 1102 area.setTop(std::min(area.top() - geometry.height() + 100, area.top())); 1103 area.setRight(std::max(area.right() + geometry.width() - 100, area.right())); 1104 area.setBottom(std::max(area.bottom() + geometry.height() - 100, area.bottom())); 1105 } 1106 if (!partial) { 1107 // resize to fit into area 1108 if (area.width() < geometry.width() || area.height() < geometry.height()) { 1109 geometry = resizeWithChecks(geometry, geometry.size().boundedTo(area.size())); 1110 } 1111 } 1112 1113 if (geometry.right() > area.right() && geometry.width() <= area.width()) { 1114 geometry.moveRight(area.right()); 1115 } 1116 if (geometry.bottom() > area.bottom() && geometry.height() <= area.height()) { 1117 geometry.moveBottom(area.bottom()); 1118 } 1119 1120 if (geometry.left() < area.left()) { 1121 geometry.moveLeft(area.left()); 1122 } 1123 if (geometry.top() < area.top()) { 1124 geometry.moveTop(area.top()); 1125 } 1126 return geometry; 1127 } 1128 1129 void Window::keepInArea(QRectF area, bool partial) 1130 { 1131 moveResize(keepInArea(moveResizeGeometry(), area, partial)); 1132 } 1133 1134 /** 1135 * Returns the maximum client size, not the maximum frame size. 1136 */ 1137 QSizeF Window::maxSize() const 1138 { 1139 return rules()->checkMaxSize(QSize(INT_MAX, INT_MAX)); 1140 } 1141 1142 /** 1143 * Returns the minimum client size, not the minimum frame size. 1144 */ 1145 QSizeF Window::minSize() const 1146 { 1147 return rules()->checkMinSize(QSize(0, 0)); 1148 } 1149 1150 void Window::blockGeometryUpdates(bool block) 1151 { 1152 if (block) { 1153 if (m_blockGeometryUpdates == 0) { 1154 m_pendingMoveResizeMode = MoveResizeMode::None; 1155 } 1156 ++m_blockGeometryUpdates; 1157 } else { 1158 if (--m_blockGeometryUpdates == 0) { 1159 if (m_pendingMoveResizeMode != MoveResizeMode::None) { 1160 moveResizeInternal(moveResizeGeometry(), m_pendingMoveResizeMode); 1161 m_pendingMoveResizeMode = MoveResizeMode::None; 1162 } 1163 } 1164 } 1165 } 1166 1167 void Window::maximize(MaximizeMode mode) 1168 { 1169 qCWarning(KWIN_CORE, "%s doesn't support setting maximized state", metaObject()->className()); 1170 } 1171 1172 void Window::setMaximize(bool vertically, bool horizontally) 1173 { 1174 MaximizeMode mode = MaximizeRestore; 1175 if (vertically) { 1176 mode = MaximizeMode(mode | MaximizeVertical); 1177 } 1178 if (horizontally) { 1179 mode = MaximizeMode(mode | MaximizeHorizontal); 1180 } 1181 1182 maximize(mode); 1183 } 1184 1185 bool Window::startInteractiveMoveResize() 1186 { 1187 Q_ASSERT(!isInteractiveMoveResize()); 1188 Q_ASSERT(QWidget::keyboardGrabber() == nullptr); 1189 Q_ASSERT(QWidget::mouseGrabber() == nullptr); 1190 stopDelayedInteractiveMoveResize(); 1191 if (QApplication::activePopupWidget() != nullptr) { 1192 return false; // popups have grab 1193 } 1194 if (isRequestedFullScreen() && (workspace()->outputs().count() < 2 || !isMovableAcrossScreens())) { 1195 return false; 1196 } 1197 if (!doStartInteractiveMoveResize()) { 1198 return false; 1199 } 1200 1201 invalidateDecorationDoubleClickTimer(); 1202 1203 setInteractiveMoveResize(true); 1204 workspace()->setMoveResizeWindow(this); 1205 1206 m_interactiveMoveResize.initialGeometry = moveResizeGeometry(); 1207 m_interactiveMoveResize.startOutput = moveResizeOutput(); 1208 m_interactiveMoveResize.initialMaximizeMode = requestedMaximizeMode(); 1209 m_interactiveMoveResize.initialQuickTileMode = quickTileMode(); 1210 m_interactiveMoveResize.initialGeometryRestore = geometryRestore(); 1211 1212 if (requestedMaximizeMode() != MaximizeRestore) { 1213 switch (interactiveMoveResizeGravity()) { 1214 case Gravity::Left: 1215 case Gravity::Right: 1216 // Quit maximized horizontally state if the window is resized horizontally. 1217 if (requestedMaximizeMode() & MaximizeHorizontal) { 1218 QRectF originalGeometry = geometryRestore(); 1219 originalGeometry.setX(moveResizeGeometry().x()); 1220 originalGeometry.setWidth(moveResizeGeometry().width()); 1221 setGeometryRestore(originalGeometry); 1222 maximize(requestedMaximizeMode() ^ MaximizeHorizontal); 1223 } 1224 break; 1225 case Gravity::Top: 1226 case Gravity::Bottom: 1227 // Quit maximized vertically state if the window is resized vertically. 1228 if (requestedMaximizeMode() & MaximizeVertical) { 1229 QRectF originalGeometry = geometryRestore(); 1230 originalGeometry.setY(moveResizeGeometry().y()); 1231 originalGeometry.setHeight(moveResizeGeometry().height()); 1232 setGeometryRestore(originalGeometry); 1233 maximize(requestedMaximizeMode() ^ MaximizeVertical); 1234 } 1235 break; 1236 case Gravity::TopLeft: 1237 case Gravity::BottomLeft: 1238 case Gravity::TopRight: 1239 case Gravity::BottomRight: 1240 // Quit the maximized mode if the window is resized by dragging one of its corners. 1241 setGeometryRestore(moveResizeGeometry()); 1242 maximize(MaximizeRestore); 1243 break; 1244 default: 1245 break; 1246 } 1247 } 1248 1249 if (m_tile && !m_tile->supportsResizeGravity(interactiveMoveResizeGravity())) { 1250 setQuickTileMode(QuickTileFlag::None); 1251 } 1252 1253 updateElectricGeometryRestore(); 1254 checkUnrestrictedInteractiveMoveResize(); 1255 Q_EMIT interactiveMoveResizeStarted(); 1256 if (workspace()->screenEdges()->isDesktopSwitchingMovingClients()) { 1257 workspace()->screenEdges()->reserveDesktopSwitching(true, Qt::Vertical | Qt::Horizontal); 1258 } 1259 return true; 1260 } 1261 1262 void Window::finishInteractiveMoveResize(bool cancel) 1263 { 1264 const bool wasMove = isInteractiveMove(); 1265 GeometryUpdatesBlocker blocker(this); 1266 leaveInteractiveMoveResize(); 1267 1268 doFinishInteractiveMoveResize(); 1269 1270 if (cancel) { 1271 moveResize(initialInteractiveMoveResizeGeometry()); 1272 if (m_interactiveMoveResize.initialMaximizeMode != MaximizeMode::MaximizeRestore) { 1273 setMaximize(m_interactiveMoveResize.initialMaximizeMode & MaximizeMode::MaximizeVertical, m_interactiveMoveResize.initialMaximizeMode & MaximizeMode::MaximizeHorizontal); 1274 setGeometryRestore(m_interactiveMoveResize.initialGeometryRestore); 1275 } else if (m_interactiveMoveResize.initialQuickTileMode) { 1276 setQuickTileMode(m_interactiveMoveResize.initialQuickTileMode, true); 1277 setGeometryRestore(m_interactiveMoveResize.initialGeometryRestore); 1278 } 1279 } else if (moveResizeOutput() != interactiveMoveResizeStartOutput()) { 1280 workspace()->sendWindowToOutput(this, moveResizeOutput()); // checks rule validity 1281 if (isRequestedFullScreen() || requestedMaximizeMode() != MaximizeRestore) { 1282 checkWorkspacePosition(); 1283 } 1284 } 1285 1286 if (isElectricBorderMaximizing()) { 1287 setQuickTileMode(electricBorderMode()); 1288 setElectricBorderMaximizing(false); 1289 } else if (wasMove && (input()->modifiersRelevantForGlobalShortcuts() & Qt::ShiftModifier)) { 1290 setQuickTileMode(QuickTileFlag::Custom); 1291 } 1292 setElectricBorderMode(QuickTileMode(QuickTileFlag::None)); 1293 workspace()->outline()->hide(); 1294 1295 m_interactiveMoveResize.counter++; 1296 Q_EMIT interactiveMoveResizeFinished(); 1297 } 1298 1299 // This function checks if it actually makes sense to perform a restricted move/resize. 1300 // If e.g. the titlebar is already outside of the workarea, there's no point in performing 1301 // a restricted move resize, because then e.g. resize would also move the window (#74555). 1302 // NOTE: Most of it is duplicated from handleMoveResize(). 1303 void Window::checkUnrestrictedInteractiveMoveResize() 1304 { 1305 if (isUnrestrictedInteractiveMoveResize()) { 1306 return; 1307 } 1308 const QRectF &moveResizeGeom = moveResizeGeometry(); 1309 QRectF desktopArea = workspace()->clientArea(WorkArea, this, moveResizeGeom.center()); 1310 int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge; 1311 // restricted move/resize - keep at least part of the titlebar always visible 1312 // how much must remain visible when moved away in that direction 1313 left_marge = std::min(100. + borderRight(), moveResizeGeom.width()); 1314 right_marge = std::min(100. + borderLeft(), moveResizeGeom.width()); 1315 // width/height change with opaque resizing, use the initial ones 1316 titlebar_marge = initialInteractiveMoveResizeGeometry().height(); 1317 top_marge = borderBottom(); 1318 bottom_marge = borderTop(); 1319 if (isInteractiveResize()) { 1320 if (moveResizeGeom.bottom() < desktopArea.top() + top_marge) { 1321 setUnrestrictedInteractiveMoveResize(true); 1322 } 1323 if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge) { 1324 setUnrestrictedInteractiveMoveResize(true); 1325 } 1326 if (moveResizeGeom.right() < desktopArea.left() + left_marge) { 1327 setUnrestrictedInteractiveMoveResize(true); 1328 } 1329 if (moveResizeGeom.left() > desktopArea.right() - right_marge) { 1330 setUnrestrictedInteractiveMoveResize(true); 1331 } 1332 if (!isUnrestrictedInteractiveMoveResize() && moveResizeGeom.top() < desktopArea.top()) { // titlebar mustn't go out 1333 setUnrestrictedInteractiveMoveResize(true); 1334 } 1335 } 1336 if (isInteractiveMove()) { 1337 if (moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge) { 1338 setUnrestrictedInteractiveMoveResize(true); 1339 } 1340 // no need to check top_marge, titlebar_marge already handles it 1341 if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge) { // titlebar mustn't go out 1342 setUnrestrictedInteractiveMoveResize(true); 1343 } 1344 if (moveResizeGeom.right() < desktopArea.left() + left_marge) { 1345 setUnrestrictedInteractiveMoveResize(true); 1346 } 1347 if (moveResizeGeom.left() > desktopArea.right() - right_marge) { 1348 setUnrestrictedInteractiveMoveResize(true); 1349 } 1350 } 1351 } 1352 1353 // When the user pressed mouse on the titlebar, don't activate move immediately, 1354 // since it may be just a click. Activate instead after a delay. Move used to be 1355 // activated only after moving by several pixels, but that looks bad. 1356 void Window::startDelayedInteractiveMoveResize() 1357 { 1358 Q_ASSERT(!m_interactiveMoveResize.delayedTimer); 1359 m_interactiveMoveResize.delayedTimer = new QTimer(this); 1360 m_interactiveMoveResize.delayedTimer->setSingleShot(true); 1361 connect(m_interactiveMoveResize.delayedTimer, &QTimer::timeout, this, [this]() { 1362 Q_ASSERT(isInteractiveMoveResizePointerButtonDown()); 1363 if (!startInteractiveMoveResize()) { 1364 setInteractiveMoveResizePointerButtonDown(false); 1365 } 1366 updateCursor(); 1367 stopDelayedInteractiveMoveResize(); 1368 }); 1369 m_interactiveMoveResize.delayedTimer->start(QApplication::startDragTime()); 1370 } 1371 1372 void Window::stopDelayedInteractiveMoveResize() 1373 { 1374 delete m_interactiveMoveResize.delayedTimer; 1375 m_interactiveMoveResize.delayedTimer = nullptr; 1376 } 1377 1378 void Window::updateInteractiveMoveResize(const QPointF ¤tGlobalCursor) 1379 { 1380 handleInteractiveMoveResize(pos(), currentGlobalCursor); 1381 } 1382 1383 void Window::handleInteractiveMoveResize(const QPointF &local, const QPointF &global) 1384 { 1385 const QRectF oldGeo = moveResizeGeometry(); 1386 handleInteractiveMoveResize(local.x(), local.y(), global.x(), global.y()); 1387 if (!isRequestedFullScreen() && isInteractiveMove()) { 1388 if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && oldGeo != moveResizeGeometry()) { 1389 GeometryUpdatesBlocker blocker(this); 1390 setQuickTileMode(QuickTileFlag::None); 1391 const QRectF &geom_restore = geometryRestore(); 1392 setInteractiveMoveOffset(QPointF(double(interactiveMoveOffset().x()) / double(oldGeo.width()) * double(geom_restore.width()), 1393 double(interactiveMoveOffset().y()) / double(oldGeo.height()) * double(geom_restore.height()))); 1394 if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore) { 1395 setMoveResizeGeometry(geom_restore); 1396 } 1397 handleInteractiveMoveResize(local.x(), local.y(), global.x(), global.y()); // fix position 1398 } 1399 1400 if (input()->modifiersRelevantForGlobalShortcuts() & Qt::ShiftModifier) { 1401 resetQuickTilingMaximizationZones(); 1402 const auto &r = quickTileGeometry(QuickTileFlag::Custom, global); 1403 if (r.isEmpty()) { 1404 workspace()->outline()->hide(); 1405 } else { 1406 if (!workspace()->outline()->isActive() || workspace()->outline()->geometry() != r.toRect()) { 1407 workspace()->outline()->show(r.toRect(), moveResizeGeometry().toRect()); 1408 } 1409 } 1410 } else { 1411 if (quickTileMode() == QuickTileMode(QuickTileFlag::None) && isResizable()) { 1412 checkQuickTilingMaximizationZones(global.x(), global.y()); 1413 } 1414 if (!m_electricMaximizing) { 1415 // Only if we are in an electric maximizing gesture we should keep the outline, 1416 // otherwise we must make sure it's hidden 1417 workspace()->outline()->hide(); 1418 } 1419 } 1420 } 1421 } 1422 1423 void Window::handleInteractiveMoveResize(qreal x, qreal y, qreal x_root, qreal y_root) 1424 { 1425 if (isWaitingForInteractiveMoveResizeSync()) { 1426 return; // we're still waiting for the client or the timeout 1427 } 1428 1429 const Gravity gravity = interactiveMoveResizeGravity(); 1430 if ((gravity == Gravity::None && !isMovableAcrossScreens()) 1431 || (gravity != Gravity::None && (isShade() || !isResizable()))) { 1432 return; 1433 } 1434 1435 if (!isInteractiveMoveResize()) { 1436 QPointF p(QPointF(x /* - padding_left*/, y /* - padding_top*/) - interactiveMoveOffset()); 1437 if (p.manhattanLength() >= QApplication::startDragDistance()) { 1438 if (!startInteractiveMoveResize()) { 1439 setInteractiveMoveResizePointerButtonDown(false); 1440 updateCursor(); 1441 return; 1442 } 1443 updateCursor(); 1444 } else { 1445 return; 1446 } 1447 } 1448 1449 // ShadeHover or ShadeActive, ShadeNormal was already avoided above 1450 if (gravity != Gravity::None && shadeMode() != ShadeNone) { 1451 setShade(ShadeNone); 1452 } 1453 1454 QPointF globalPos(x_root, y_root); 1455 // these two points limit the geometry rectangle, i.e. if bottomleft resizing is done, 1456 // the bottomleft corner should be at is at (topleft.x(), bottomright().y()) 1457 QPointF topleft = globalPos - interactiveMoveOffset(); 1458 QPointF bottomright = globalPos + invertedInteractiveMoveOffset(); 1459 const QRectF currentMoveResizeGeom = moveResizeGeometry(); 1460 QRectF nextMoveResizeGeom = moveResizeGeometry(); 1461 1462 // TODO move whole group when moving its leader or when the leader is not mapped? 1463 1464 auto titleBarRect = [this](const QRectF &rect, bool &transposed, int &requiredPixels) -> QRectF { 1465 QRectF titleRect = rect; 1466 titleRect.moveTopLeft(QPointF(0, 0)); 1467 switch (titlebarPosition()) { 1468 default: 1469 case Qt::TopEdge: 1470 titleRect.setHeight(borderTop()); 1471 break; 1472 case Qt::LeftEdge: 1473 titleRect.setWidth(borderLeft()); 1474 transposed = true; 1475 break; 1476 case Qt::BottomEdge: 1477 titleRect.setTop(titleRect.bottom() - borderBottom()); 1478 break; 1479 case Qt::RightEdge: 1480 titleRect.setLeft(titleRect.right() - borderRight()); 1481 transposed = true; 1482 break; 1483 } 1484 // When doing a restricted move we must always keep 100px of the titlebar 1485 // visible to allow the user to be able to move it again. 1486 requiredPixels = std::min(100 * (transposed ? titleRect.width() : titleRect.height()), 1487 rect.width() * rect.height()); 1488 return titleRect; 1489 }; 1490 1491 if (isInteractiveResize()) { 1492 if (m_tile && m_tile->supportsResizeGravity(gravity)) { 1493 m_tile->resizeFromGravity(gravity, x_root, y_root); 1494 return; 1495 } 1496 1497 QRectF orig = initialInteractiveMoveResizeGeometry(); 1498 SizeMode sizeMode = SizeModeAny; 1499 auto calculateMoveResizeGeom = [&topleft, &bottomright, &orig, &nextMoveResizeGeom, &sizeMode, &gravity]() { 1500 switch (gravity) { 1501 case Gravity::TopLeft: 1502 nextMoveResizeGeom = QRectF(topleft, orig.bottomRight()); 1503 break; 1504 case Gravity::BottomRight: 1505 nextMoveResizeGeom = QRectF(orig.topLeft(), bottomright); 1506 break; 1507 case Gravity::BottomLeft: 1508 nextMoveResizeGeom = QRectF(QPointF(topleft.x(), orig.y()), QPointF(orig.right(), bottomright.y())); 1509 break; 1510 case Gravity::TopRight: 1511 nextMoveResizeGeom = QRectF(QPointF(orig.x(), topleft.y()), QPointF(bottomright.x(), orig.bottom())); 1512 break; 1513 case Gravity::Top: 1514 nextMoveResizeGeom = QRectF(QPointF(orig.left(), topleft.y()), orig.bottomRight()); 1515 sizeMode = SizeModeFixedH; // try not to affect height 1516 break; 1517 case Gravity::Bottom: 1518 nextMoveResizeGeom = QRectF(orig.topLeft(), QPointF(orig.right(), bottomright.y())); 1519 sizeMode = SizeModeFixedH; 1520 break; 1521 case Gravity::Left: 1522 nextMoveResizeGeom = QRectF(QPointF(topleft.x(), orig.top()), orig.bottomRight()); 1523 sizeMode = SizeModeFixedW; 1524 break; 1525 case Gravity::Right: 1526 nextMoveResizeGeom = QRectF(orig.topLeft(), QPointF(bottomright.x(), orig.bottom())); 1527 sizeMode = SizeModeFixedW; 1528 break; 1529 case Gravity::None: 1530 Q_UNREACHABLE(); 1531 break; 1532 } 1533 }; 1534 1535 // first resize (without checking constrains), then snap, then check bounds, then check constrains 1536 calculateMoveResizeGeom(); 1537 // adjust new size to snap to other windows/borders 1538 nextMoveResizeGeom = workspace()->adjustWindowSize(this, nextMoveResizeGeom, gravity); 1539 1540 if (!isUnrestrictedInteractiveMoveResize()) { 1541 // Make sure the titlebar isn't behind a restricted area. We don't need to restrict 1542 // the other directions. If not visible enough, move the window to the closest valid 1543 // point. We bruteforce this by slowly moving the window back to its previous position 1544 const StrutRects strut = workspace()->restrictedMoveArea(VirtualDesktopManager::self()->currentDesktop()); 1545 QRegion availableArea(workspace()->clientArea(FullArea, this, workspace()->activeOutput()).toRect()); 1546 for (const QRect &rect : strut) { 1547 availableArea -= rect; 1548 } 1549 bool transposed = false; 1550 int requiredPixels; 1551 QRectF bTitleRect = titleBarRect(nextMoveResizeGeom, transposed, requiredPixels); 1552 int lastVisiblePixels = -1; 1553 QRectF lastTry = nextMoveResizeGeom; 1554 bool titleFailed = false; 1555 for (;;) { 1556 const QRect titleRect = bTitleRect.translated(nextMoveResizeGeom.topLeft()).toRect(); 1557 int visiblePixels = 0; 1558 int realVisiblePixels = 0; 1559 for (const QRect &rect : availableArea) { 1560 const QRect r = rect & titleRect; 1561 realVisiblePixels += r.width() * r.height(); 1562 if ((transposed && r.width() == titleRect.width()) || // Only the full size regions... 1563 (!transposed && r.height() == titleRect.height())) { // ...prevents long slim areas 1564 visiblePixels += r.width() * r.height(); 1565 } 1566 } 1567 1568 if (visiblePixels >= requiredPixels) { 1569 break; // We have reached a valid position 1570 } 1571 1572 if (realVisiblePixels <= lastVisiblePixels) { 1573 if (titleFailed && realVisiblePixels < lastVisiblePixels) { 1574 break; // we won't become better 1575 } else { 1576 if (!titleFailed) { 1577 nextMoveResizeGeom = lastTry; 1578 } 1579 titleFailed = true; 1580 } 1581 } 1582 lastVisiblePixels = realVisiblePixels; 1583 QRectF currentTry = nextMoveResizeGeom; 1584 lastTry = currentTry; 1585 1586 // Not visible enough, move the window to the closest valid point. We bruteforce 1587 // this by slowly moving the window back to its previous position. 1588 // The geometry changes at up to two edges, the one with the title (if) shall take 1589 // precedence. The opposing edge has no impact on visiblePixels and only one of 1590 // the adjacent can alter at a time, ie. it's enough to ignore adjacent edges 1591 // if the title edge altered 1592 bool leftChanged = !qFuzzyCompare(currentMoveResizeGeom.left(), currentTry.left()); 1593 bool rightChanged = !qFuzzyCompare(currentMoveResizeGeom.right(), currentTry.right()); 1594 bool topChanged = !qFuzzyCompare(currentMoveResizeGeom.top(), currentTry.top()); 1595 bool btmChanged = !qFuzzyCompare(currentMoveResizeGeom.bottom(), currentTry.bottom()); 1596 auto fixChangedState = [titleFailed](bool &major, bool &counter, bool &ad1, bool &ad2) { 1597 counter = false; 1598 if (titleFailed) { 1599 major = false; 1600 } 1601 if (major) { 1602 ad1 = ad2 = false; 1603 } 1604 }; 1605 switch (titlebarPosition()) { 1606 default: 1607 case Qt::TopEdge: 1608 fixChangedState(topChanged, btmChanged, leftChanged, rightChanged); 1609 break; 1610 case Qt::LeftEdge: 1611 fixChangedState(leftChanged, rightChanged, topChanged, btmChanged); 1612 break; 1613 case Qt::BottomEdge: 1614 fixChangedState(btmChanged, topChanged, leftChanged, rightChanged); 1615 break; 1616 case Qt::RightEdge: 1617 fixChangedState(rightChanged, leftChanged, topChanged, btmChanged); 1618 break; 1619 } 1620 if (topChanged) { 1621 currentTry.setTop(currentTry.y() + qBound(-1.0, currentMoveResizeGeom.y() - currentTry.y(), 1.0)); 1622 } else if (leftChanged) { 1623 currentTry.setLeft(currentTry.x() + qBound(-1.0, currentMoveResizeGeom.x() - currentTry.x(), 1.0)); 1624 } else if (btmChanged) { 1625 currentTry.setBottom(currentTry.bottom() + qBound(-1.0, currentMoveResizeGeom.bottom() - currentTry.bottom(), 1.0)); 1626 } else if (rightChanged) { 1627 currentTry.setRight(currentTry.right() + qBound(-1.0, currentMoveResizeGeom.right() - currentTry.right(), 1.0)); 1628 } else { 1629 break; // no position changed - that's certainly not good 1630 } 1631 nextMoveResizeGeom = currentTry; 1632 } 1633 } 1634 1635 // Always obey size hints, even when in "unrestricted" mode 1636 QSizeF size = constrainFrameSize(nextMoveResizeGeom.size(), sizeMode); 1637 // the new topleft and bottomright corners (after checking size constrains), if they'll be needed 1638 topleft = QPointF(nextMoveResizeGeom.right() - size.width(), nextMoveResizeGeom.bottom() - size.height()); 1639 bottomright = QPointF(nextMoveResizeGeom.left() + size.width(), nextMoveResizeGeom.top() + size.height()); 1640 orig = nextMoveResizeGeom; 1641 1642 // if aspect ratios are specified, both dimensions may change. 1643 // Therefore grow to the right/bottom if needed. 1644 // TODO it should probably obey gravity rather than always using right/bottom ? 1645 if (sizeMode == SizeModeFixedH) { 1646 orig.setRight(bottomright.x()); 1647 } else if (sizeMode == SizeModeFixedW) { 1648 orig.setBottom(bottomright.y()); 1649 } 1650 1651 calculateMoveResizeGeom(); 1652 } else if (isInteractiveMove()) { 1653 Q_ASSERT(gravity == Gravity::None); 1654 if (!isMovable()) { // isMovableAcrossScreens() must have been true to get here 1655 // Special moving of maximized windows on Xinerama screens 1656 Output *output = workspace()->outputAt(globalPos); 1657 if (isRequestedFullScreen()) { 1658 nextMoveResizeGeom = workspace()->clientArea(FullScreenArea, this, output); 1659 } else { 1660 nextMoveResizeGeom = workspace()->clientArea(MaximizeArea, this, output); 1661 const QSizeF adjSize = constrainFrameSize(nextMoveResizeGeom.size(), SizeModeMax); 1662 if (adjSize != nextMoveResizeGeom.size()) { 1663 QRectF r(nextMoveResizeGeom); 1664 nextMoveResizeGeom.setSize(adjSize); 1665 nextMoveResizeGeom.moveCenter(r.center()); 1666 } 1667 } 1668 } else { 1669 // first move, then snap, then check bounds 1670 QRectF geometry = nextMoveResizeGeom; 1671 geometry.moveTopLeft(topleft); 1672 geometry.moveTopLeft(workspace()->adjustWindowPosition(this, geometry.topLeft(), 1673 isUnrestrictedInteractiveMoveResize())); 1674 nextMoveResizeGeom = geometry; 1675 1676 if (!isUnrestrictedInteractiveMoveResize()) { 1677 const StrutRects strut = workspace()->restrictedMoveArea(VirtualDesktopManager::self()->currentDesktop()); 1678 QRegion availableArea(workspace()->clientArea(FullArea, this, workspace()->activeOutput()).toRect()); 1679 for (const QRect &rect : strut) { 1680 availableArea -= rect; // Strut areas 1681 } 1682 bool transposed = false; 1683 int requiredPixels; 1684 QRectF bTitleRect = titleBarRect(nextMoveResizeGeom, transposed, requiredPixels); 1685 for (;;) { 1686 QRectF currentTry = nextMoveResizeGeom; 1687 const QRect titleRect = bTitleRect.translated(currentTry.topLeft()).toRect(); 1688 int visiblePixels = 0; 1689 for (const QRect &rect : availableArea) { 1690 const QRect r = rect & titleRect; 1691 if ((transposed && r.width() == titleRect.width()) || // Only the full size regions... 1692 (!transposed && r.height() == titleRect.height())) { // ...prevents long slim areas 1693 visiblePixels += r.width() * r.height(); 1694 } 1695 } 1696 if (visiblePixels >= requiredPixels) { 1697 break; // We have reached a valid position 1698 } 1699 1700 // (esp.) if there're more screens with different struts (panels) it the titlebar 1701 // will be movable outside the movearea (covering one of the panels) until it 1702 // crosses the panel "too much" (not enough visiblePixels) and then stucks because 1703 // it's usually only pushed by 1px to either direction 1704 // so we first check whether we intersect suc strut and move the window below it 1705 // immediately (it's still possible to hit the visiblePixels >= titlebarArea break 1706 // by moving the window slightly downwards, but it won't stuck) 1707 // see bug #274466 1708 // and bug #301805 for why we can't just match the titlearea against the screen 1709 if (workspace()->outputs().count() > 1) { // optimization 1710 // TODO: could be useful on partial screen struts (half-width panels etc.) 1711 int newTitleTop = -1; 1712 for (const QRect ®ion : strut) { 1713 QRectF r = region; 1714 if (r.top() == 0 && r.width() > r.height() && // "top panel" 1715 r.intersects(currentTry) && currentTry.top() < r.bottom()) { 1716 newTitleTop = r.bottom(); 1717 break; 1718 } 1719 } 1720 if (newTitleTop > -1) { 1721 currentTry.moveTop(newTitleTop); // invalid position, possibly on screen change 1722 nextMoveResizeGeom = currentTry; 1723 break; 1724 } 1725 } 1726 1727 int dx = sign(currentMoveResizeGeom.x() - currentTry.x()), 1728 dy = sign(currentMoveResizeGeom.y() - currentTry.y()); 1729 if (visiblePixels && dx) { // means there's no full width cap -> favor horizontally 1730 dy = 0; 1731 } else if (dy) { 1732 dx = 0; 1733 } 1734 1735 // Move it back 1736 currentTry.translate(dx, dy); 1737 nextMoveResizeGeom = currentTry; 1738 1739 // sinces nextMoveResizeGeom is fractional, at best it is within 1 unit of currentMoveResizeGeom 1740 if (std::abs(currentMoveResizeGeom.left() - nextMoveResizeGeom.left()) <= 1.0 1741 && std::abs(currentMoveResizeGeom.right() - nextMoveResizeGeom.right()) <= 1.0 1742 && std::abs(currentMoveResizeGeom.top() - nextMoveResizeGeom.top()) <= 1.0 1743 && std::abs(currentMoveResizeGeom.bottom() - nextMoveResizeGeom.bottom()) <= 1.0) { 1744 break; // Prevent lockup 1745 } 1746 } 1747 } 1748 } 1749 } else { 1750 Q_UNREACHABLE(); 1751 } 1752 1753 if (nextMoveResizeGeom != currentMoveResizeGeom) { 1754 if (isInteractiveMove()) { 1755 move(nextMoveResizeGeom.topLeft()); 1756 } else { 1757 doInteractiveResizeSync(nextMoveResizeGeom); 1758 } 1759 1760 Q_EMIT interactiveMoveResizeStepped(nextMoveResizeGeom); 1761 } 1762 } 1763 1764 StrutRect Window::strutRect(StrutArea area) const 1765 { 1766 return StrutRect(); 1767 } 1768 1769 StrutRects Window::strutRects() const 1770 { 1771 StrutRects region; 1772 if (const StrutRect strut = strutRect(StrutAreaTop); strut.isValid()) { 1773 region += strut; 1774 } 1775 if (const StrutRect strut = strutRect(StrutAreaRight); strut.isValid()) { 1776 region += strut; 1777 } 1778 if (const StrutRect strut = strutRect(StrutAreaBottom); strut.isValid()) { 1779 region += strut; 1780 } 1781 if (const StrutRect strut = strutRect(StrutAreaLeft); strut.isValid()) { 1782 region += strut; 1783 } 1784 return region; 1785 } 1786 1787 bool Window::hasStrut() const 1788 { 1789 return false; 1790 } 1791 1792 void Window::setupWindowManagementInterface() 1793 { 1794 if (m_windowManagementInterface) { 1795 // already setup 1796 return; 1797 } 1798 if (!waylandServer() || !waylandServer()->windowManagement()) { 1799 return; 1800 } 1801 auto w = waylandServer()->windowManagement()->createWindow(this, internalId()); 1802 w->setTitle(caption()); 1803 w->setActive(isActive()); 1804 w->setFullscreen(isFullScreen()); 1805 w->setKeepAbove(keepAbove()); 1806 w->setKeepBelow(keepBelow()); 1807 w->setMaximized(maximizeMode() == KWin::MaximizeFull); 1808 w->setMinimized(isMinimized()); 1809 w->setDemandsAttention(isDemandingAttention()); 1810 w->setCloseable(isCloseable()); 1811 w->setMaximizeable(isMaximizable()); 1812 w->setMinimizeable(isMinimizable()); 1813 w->setFullscreenable(isFullScreenable()); 1814 w->setApplicationMenuPaths(applicationMenuServiceName(), applicationMenuObjectPath()); 1815 w->setIcon(icon()); 1816 auto updateAppId = [this, w] { 1817 w->setResourceName(resourceName()); 1818 w->setAppId(m_desktopFileName.isEmpty() ? resourceClass() : m_desktopFileName); 1819 }; 1820 updateAppId(); 1821 w->setSkipTaskbar(skipTaskbar()); 1822 w->setSkipSwitcher(skipSwitcher()); 1823 w->setPid(pid()); 1824 w->setShadeable(isShadeable()); 1825 w->setShaded(isShade()); 1826 w->setResizable(isResizable()); 1827 w->setMovable(isMovable()); 1828 w->setVirtualDesktopChangeable(true); // FIXME Matches X11Window::actionSupported(), but both should be implemented. 1829 w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr); 1830 w->setGeometry(frameGeometry().toRect()); 1831 connect(this, &Window::skipTaskbarChanged, w, [w, this]() { 1832 w->setSkipTaskbar(skipTaskbar()); 1833 }); 1834 connect(this, &Window::skipSwitcherChanged, w, [w, this]() { 1835 w->setSkipSwitcher(skipSwitcher()); 1836 }); 1837 connect(this, &Window::captionChanged, w, [w, this] { 1838 w->setTitle(caption()); 1839 }); 1840 1841 connect(this, &Window::activeChanged, w, [w, this] { 1842 w->setActive(isActive()); 1843 }); 1844 connect(this, &Window::fullScreenChanged, w, [w, this] { 1845 w->setFullscreen(isFullScreen()); 1846 }); 1847 connect(this, &Window::keepAboveChanged, w, &PlasmaWindowInterface::setKeepAbove); 1848 connect(this, &Window::keepBelowChanged, w, &PlasmaWindowInterface::setKeepBelow); 1849 connect(this, &Window::minimizedChanged, w, [w, this] { 1850 w->setMinimized(isMinimized()); 1851 }); 1852 connect(this, &Window::maximizedChanged, w, [w, this]() { 1853 w->setMaximized(maximizeMode() == MaximizeFull); 1854 }); 1855 connect(this, &Window::demandsAttentionChanged, w, [w, this] { 1856 w->setDemandsAttention(isDemandingAttention()); 1857 }); 1858 connect(this, &Window::iconChanged, w, [w, this]() { 1859 w->setIcon(icon()); 1860 }); 1861 connect(this, &Window::windowClassChanged, w, updateAppId); 1862 connect(this, &Window::desktopFileNameChanged, w, updateAppId); 1863 connect(this, &Window::shadeChanged, w, [w, this] { 1864 w->setShaded(isShade()); 1865 }); 1866 connect(this, &Window::transientChanged, w, [w, this]() { 1867 w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr); 1868 }); 1869 connect(this, &Window::frameGeometryChanged, w, [w, this]() { 1870 w->setGeometry(frameGeometry().toRect()); 1871 }); 1872 connect(this, &Window::applicationMenuChanged, w, [w, this]() { 1873 w->setApplicationMenuPaths(applicationMenuServiceName(), applicationMenuObjectPath()); 1874 }); 1875 connect(w, &PlasmaWindowInterface::closeRequested, this, [this] { 1876 closeWindow(); 1877 }); 1878 connect(w, &PlasmaWindowInterface::moveRequested, this, [this]() { 1879 Cursors::self()->mouse()->setPos(frameGeometry().center()); 1880 performMouseCommand(Options::MouseMove, Cursors::self()->mouse()->pos()); 1881 }); 1882 connect(w, &PlasmaWindowInterface::resizeRequested, this, [this]() { 1883 Cursors::self()->mouse()->setPos(frameGeometry().bottomRight()); 1884 performMouseCommand(Options::MouseResize, Cursors::self()->mouse()->pos()); 1885 }); 1886 connect(w, &PlasmaWindowInterface::fullscreenRequested, this, [this](bool set) { 1887 setFullScreen(set); 1888 }); 1889 connect(w, &PlasmaWindowInterface::minimizedRequested, this, [this](bool set) { 1890 setMinimized(set); 1891 }); 1892 connect(w, &PlasmaWindowInterface::maximizedRequested, this, [this](bool set) { 1893 maximize(set ? MaximizeFull : MaximizeRestore); 1894 }); 1895 connect(w, &PlasmaWindowInterface::keepAboveRequested, this, [this](bool set) { 1896 setKeepAbove(set); 1897 }); 1898 connect(w, &PlasmaWindowInterface::keepBelowRequested, this, [this](bool set) { 1899 setKeepBelow(set); 1900 }); 1901 connect(w, &PlasmaWindowInterface::demandsAttentionRequested, this, [this](bool set) { 1902 demandAttention(set); 1903 }); 1904 connect(w, &PlasmaWindowInterface::activeRequested, this, [this](bool set) { 1905 if (set) { 1906 workspace()->activateWindow(this, true); 1907 } 1908 }); 1909 connect(w, &PlasmaWindowInterface::shadedRequested, this, [this](bool set) { 1910 setShade(set); 1911 }); 1912 1913 for (const auto vd : std::as_const(m_desktops)) { 1914 w->addPlasmaVirtualDesktop(vd->id()); 1915 } 1916 // We need to set `OnAllDesktops` after the actual VD list has been added. 1917 // Otherwise it will unconditionally add the current desktop to the interface 1918 // which may not be the case, for example, when using rules 1919 w->setOnAllDesktops(isOnAllDesktops()); 1920 1921 // Plasma Virtual desktop management 1922 // show/hide when the window enters/exits from desktop 1923 connect(w, &PlasmaWindowInterface::enterPlasmaVirtualDesktopRequested, this, [this](const QString &desktopId) { 1924 VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId); 1925 if (vd) { 1926 enterDesktop(vd); 1927 } 1928 }); 1929 connect(w, &PlasmaWindowInterface::enterNewPlasmaVirtualDesktopRequested, this, [this]() { 1930 VirtualDesktopManager::self()->setCount(VirtualDesktopManager::self()->count() + 1); 1931 enterDesktop(VirtualDesktopManager::self()->desktops().last()); 1932 }); 1933 connect(w, &PlasmaWindowInterface::leavePlasmaVirtualDesktopRequested, this, [this](const QString &desktopId) { 1934 VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId); 1935 if (vd) { 1936 leaveDesktop(vd); 1937 } 1938 }); 1939 1940 for (const auto &activity : std::as_const(m_activityList)) { 1941 w->addPlasmaActivity(activity); 1942 } 1943 1944 connect(this, &Window::activitiesChanged, w, [w, this] { 1945 const auto newActivities = QSet<QString>(m_activityList.begin(), m_activityList.end()); 1946 const auto oldActivitiesList = w->plasmaActivities(); 1947 const auto oldActivities = QSet<QString>(oldActivitiesList.begin(), oldActivitiesList.end()); 1948 1949 const auto activitiesToAdd = newActivities - oldActivities; 1950 for (const auto &activity : activitiesToAdd) { 1951 w->addPlasmaActivity(activity); 1952 } 1953 1954 const auto activitiesToRemove = oldActivities - newActivities; 1955 for (const auto &activity : activitiesToRemove) { 1956 w->removePlasmaActivity(activity); 1957 } 1958 }); 1959 1960 // Plasma Activities management 1961 // show/hide when the window enters/exits activity 1962 connect(w, &PlasmaWindowInterface::enterPlasmaActivityRequested, this, [this](const QString &activityId) { 1963 setOnActivity(activityId, true); 1964 }); 1965 connect(w, &PlasmaWindowInterface::leavePlasmaActivityRequested, this, [this](const QString &activityId) { 1966 setOnActivity(activityId, false); 1967 }); 1968 connect(w, &PlasmaWindowInterface::sendToOutput, this, [this](OutputInterface *output) { 1969 sendToOutput(output->handle()); 1970 }); 1971 1972 m_windowManagementInterface = w; 1973 } 1974 1975 void Window::destroyWindowManagementInterface() 1976 { 1977 delete m_windowManagementInterface; 1978 m_windowManagementInterface = nullptr; 1979 } 1980 1981 Options::MouseCommand Window::getMouseCommand(Qt::MouseButton button, bool *handled) const 1982 { 1983 *handled = false; 1984 if (button == Qt::NoButton) { 1985 return Options::MouseNothing; 1986 } 1987 if (isActive()) { 1988 if (options->isClickRaise() && !isMostRecentlyRaised()) { 1989 *handled = true; 1990 return Options::MouseActivateRaiseAndPassClick; 1991 } 1992 } else { 1993 *handled = true; 1994 switch (button) { 1995 case Qt::LeftButton: 1996 return options->commandWindow1(); 1997 case Qt::MiddleButton: 1998 return options->commandWindow2(); 1999 case Qt::RightButton: 2000 return options->commandWindow3(); 2001 default: 2002 // all other buttons pass Activate & Pass Client 2003 return Options::MouseActivateAndPassClick; 2004 } 2005 } 2006 return Options::MouseNothing; 2007 } 2008 2009 Options::MouseCommand Window::getWheelCommand(Qt::Orientation orientation, bool *handled) const 2010 { 2011 *handled = false; 2012 if (orientation != Qt::Vertical) { 2013 return Options::MouseNothing; 2014 } 2015 if (!isActive()) { 2016 *handled = true; 2017 return options->commandWindowWheel(); 2018 } 2019 return Options::MouseNothing; 2020 } 2021 2022 bool Window::performMouseCommand(Options::MouseCommand cmd, const QPointF &globalPos) 2023 { 2024 bool replay = false; 2025 switch (cmd) { 2026 case Options::MouseRaise: 2027 workspace()->raiseWindow(this); 2028 break; 2029 case Options::MouseLower: { 2030 workspace()->lowerWindow(this); 2031 // used to be activateNextWindow(this), then topWindowOnDesktop 2032 // since this is a mouseOp it's however safe to use the window under the mouse instead 2033 if (isActive() && options->focusPolicyIsReasonable()) { 2034 Window *next = workspace()->windowUnderMouse(output()); 2035 if (next && next != this) { 2036 workspace()->requestFocus(next, false); 2037 } 2038 } 2039 break; 2040 } 2041 case Options::MouseOperationsMenu: 2042 if (isActive() && options->isClickRaise()) { 2043 autoRaise(); 2044 } 2045 workspace()->showWindowMenu(QRect(globalPos.toPoint(), globalPos.toPoint()), this); 2046 break; 2047 case Options::MouseToggleRaiseAndLower: 2048 workspace()->raiseOrLowerWindow(this); 2049 break; 2050 case Options::MouseActivateAndRaise: { 2051 replay = isActive(); // for clickraise mode 2052 bool mustReplay = !rules()->checkAcceptFocus(acceptsFocus()); 2053 if (mustReplay) { 2054 auto it = workspace()->stackingOrder().constEnd(), 2055 begin = workspace()->stackingOrder().constBegin(); 2056 while (mustReplay && --it != begin && *it != this) { 2057 auto c = *it; 2058 if (!c->isClient() || (c->keepAbove() && !keepAbove()) || (keepBelow() && !c->keepBelow())) { 2059 continue; // can never raise above "it" 2060 } 2061 mustReplay = !(c->isOnCurrentDesktop() && c->isOnCurrentActivity() && c->frameGeometry().intersects(frameGeometry())); 2062 } 2063 } 2064 workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise); 2065 workspace()->setActiveOutput(globalPos); 2066 replay = replay || mustReplay; 2067 break; 2068 } 2069 case Options::MouseActivateAndLower: 2070 workspace()->requestFocus(this); 2071 workspace()->lowerWindow(this); 2072 workspace()->setActiveOutput(globalPos); 2073 replay = replay || !rules()->checkAcceptFocus(acceptsFocus()); 2074 break; 2075 case Options::MouseActivate: 2076 replay = isActive(); // for clickraise mode 2077 workspace()->takeActivity(this, Workspace::ActivityFocus); 2078 workspace()->setActiveOutput(globalPos); 2079 replay = replay || !rules()->checkAcceptFocus(acceptsFocus()); 2080 break; 2081 case Options::MouseActivateRaiseAndPassClick: 2082 workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise); 2083 workspace()->setActiveOutput(globalPos); 2084 replay = true; 2085 break; 2086 case Options::MouseActivateAndPassClick: 2087 workspace()->takeActivity(this, Workspace::ActivityFocus); 2088 workspace()->setActiveOutput(globalPos); 2089 replay = true; 2090 break; 2091 case Options::MouseMaximize: 2092 maximize(MaximizeFull); 2093 break; 2094 case Options::MouseRestore: 2095 maximize(MaximizeRestore); 2096 break; 2097 case Options::MouseMinimize: 2098 setMinimized(true); 2099 break; 2100 case Options::MouseAbove: { 2101 StackingUpdatesBlocker blocker(workspace()); 2102 if (keepBelow()) { 2103 setKeepBelow(false); 2104 } else { 2105 setKeepAbove(true); 2106 } 2107 break; 2108 } 2109 case Options::MouseBelow: { 2110 StackingUpdatesBlocker blocker(workspace()); 2111 if (keepAbove()) { 2112 setKeepAbove(false); 2113 } else { 2114 setKeepBelow(true); 2115 } 2116 break; 2117 } 2118 case Options::MousePreviousDesktop: 2119 workspace()->windowToPreviousDesktop(this); 2120 break; 2121 case Options::MouseNextDesktop: 2122 workspace()->windowToNextDesktop(this); 2123 break; 2124 case Options::MouseOpacityMore: 2125 if (!isDesktop()) { // No point in changing the opacity of the desktop 2126 setOpacity(std::min(opacity() + 0.1, 1.0)); 2127 } 2128 break; 2129 case Options::MouseOpacityLess: 2130 if (!isDesktop()) { // No point in changing the opacity of the desktop 2131 setOpacity(std::max(opacity() - 0.1, 0.1)); 2132 } 2133 break; 2134 case Options::MouseClose: 2135 closeWindow(); 2136 break; 2137 case Options::MouseActivateRaiseAndMove: 2138 case Options::MouseActivateRaiseAndUnrestrictedMove: 2139 workspace()->raiseWindow(this); 2140 workspace()->requestFocus(this); 2141 workspace()->setActiveOutput(globalPos); 2142 // fallthrough 2143 case Options::MouseMove: 2144 case Options::MouseUnrestrictedMove: { 2145 if (!isMovableAcrossScreens()) { 2146 replay = true; 2147 break; 2148 } 2149 if (isInteractiveMoveResize()) { 2150 finishInteractiveMoveResize(false); 2151 } 2152 setInteractiveMoveResizeGravity(Gravity::None); 2153 setInteractiveMoveResizePointerButtonDown(true); 2154 setInteractiveMoveOffset(QPointF(globalPos.x() - x(), globalPos.y() - y())); // map from global 2155 setInvertedInteractiveMoveOffset(rect().bottomRight() - interactiveMoveOffset()); 2156 setUnrestrictedInteractiveMoveResize((cmd == Options::MouseActivateRaiseAndUnrestrictedMove 2157 || cmd == Options::MouseUnrestrictedMove)); 2158 if (!startInteractiveMoveResize()) { 2159 setInteractiveMoveResizePointerButtonDown(false); 2160 } 2161 updateCursor(); 2162 break; 2163 } 2164 case Options::MouseResize: 2165 case Options::MouseUnrestrictedResize: { 2166 if (!isResizable() || isShade()) { 2167 break; 2168 } 2169 if (isInteractiveMoveResize()) { 2170 finishInteractiveMoveResize(false); 2171 } 2172 setInteractiveMoveResizePointerButtonDown(true); 2173 const QPointF moveOffset = QPointF(globalPos.x() - x(), globalPos.y() - y()); // map from global 2174 setInteractiveMoveOffset(moveOffset); 2175 int x = moveOffset.x(), y = moveOffset.y(); 2176 bool left = x < width() / 3; 2177 bool right = x >= 2 * width() / 3; 2178 bool top = y < height() / 3; 2179 bool bot = y >= 2 * height() / 3; 2180 Gravity gravity; 2181 if (top) { 2182 gravity = left ? Gravity::TopLeft : (right ? Gravity::TopRight : Gravity::Top); 2183 } else if (bot) { 2184 gravity = left ? Gravity::BottomLeft : (right ? Gravity::BottomRight : Gravity::Bottom); 2185 } else { 2186 gravity = (x < width() / 2) ? Gravity::Left : Gravity::Right; 2187 } 2188 setInteractiveMoveResizeGravity(gravity); 2189 setInvertedInteractiveMoveOffset(rect().bottomRight() - moveOffset); 2190 setUnrestrictedInteractiveMoveResize((cmd == Options::MouseUnrestrictedResize)); 2191 if (!startInteractiveMoveResize()) { 2192 setInteractiveMoveResizePointerButtonDown(false); 2193 } 2194 updateCursor(); 2195 break; 2196 } 2197 case Options::MouseShade: 2198 toggleShade(); 2199 cancelShadeHoverTimer(); 2200 break; 2201 case Options::MouseSetShade: 2202 setShade(ShadeNormal); 2203 cancelShadeHoverTimer(); 2204 break; 2205 case Options::MouseUnsetShade: 2206 setShade(ShadeNone); 2207 cancelShadeHoverTimer(); 2208 break; 2209 case Options::MouseNothing: 2210 default: 2211 replay = true; 2212 break; 2213 } 2214 return replay; 2215 } 2216 2217 void Window::setTransientFor(Window *transientFor) 2218 { 2219 if (transientFor == this) { 2220 // cannot be transient for one self 2221 return; 2222 } 2223 if (m_transientFor == transientFor) { 2224 return; 2225 } 2226 m_transientFor = transientFor; 2227 Q_EMIT transientChanged(); 2228 } 2229 2230 const Window *Window::transientFor() const 2231 { 2232 return m_transientFor; 2233 } 2234 2235 Window *Window::transientFor() 2236 { 2237 return m_transientFor; 2238 } 2239 2240 bool Window::hasTransientPlacementHint() const 2241 { 2242 return false; 2243 } 2244 2245 QRectF Window::transientPlacement() const 2246 { 2247 Q_UNREACHABLE(); 2248 return QRectF(); 2249 } 2250 2251 bool Window::hasTransient(const Window *c, bool indirect) const 2252 { 2253 return c->transientFor() == this; 2254 } 2255 2256 QList<Window *> Window::mainWindows() const 2257 { 2258 if (const Window *t = transientFor()) { 2259 return QList<Window *>{const_cast<Window *>(t)}; 2260 } 2261 return QList<Window *>(); 2262 } 2263 2264 QList<Window *> Window::allMainWindows() const 2265 { 2266 auto result = mainWindows(); 2267 for (const auto *window : result) { 2268 result += window->allMainWindows(); 2269 } 2270 return result; 2271 } 2272 2273 void Window::setModal(bool m) 2274 { 2275 // Qt-3.2 can have even modal normal windows :( 2276 if (m_modal == m) { 2277 return; 2278 } 2279 m_modal = m; 2280 Q_EMIT modalChanged(); 2281 // Changing modality for a mapped window is weird (?) 2282 // _NET_WM_STATE_MODAL should possibly rather be _NET_WM_WINDOW_TYPE_MODAL_DIALOG 2283 } 2284 2285 bool Window::isModal() const 2286 { 2287 return m_modal; 2288 } 2289 2290 bool Window::isTransient() const 2291 { 2292 return false; 2293 } 2294 2295 // check whether a transient should be actually kept above its mainwindow 2296 // there may be some special cases where this rule shouldn't be enfored 2297 static bool shouldKeepTransientAbove(const Window *parent, const Window *transient) 2298 { 2299 // #93832 - don't keep splashscreens above dialogs 2300 if (transient->isSplash() && parent->isDialog()) { 2301 return false; 2302 } 2303 // This is rather a hack for #76026. Don't keep non-modal dialogs above 2304 // the mainwindow, but only if they're group transient (since only such dialogs 2305 // have taskbar entry in Kicker). A proper way of doing this (both kwin and kicker) 2306 // needs to be found. 2307 if (transient->isDialog() && !transient->isModal() && transient->groupTransient()) { 2308 return false; 2309 } 2310 // #63223 - don't keep transients above docks, because the dock is kept high, 2311 // and e.g. dialogs for them would be too high too 2312 // ignore this if the transient has a placement hint which indicates it should go above it's parent 2313 if (parent->isDock() && !transient->hasTransientPlacementHint()) { 2314 return false; 2315 } 2316 return true; 2317 } 2318 2319 void Window::addTransient(Window *cl) 2320 { 2321 Q_ASSERT(!m_transients.contains(cl)); 2322 Q_ASSERT(cl != this); 2323 m_transients.append(cl); 2324 if (shouldKeepTransientAbove(this, cl)) { 2325 workspace()->constrain(this, cl); 2326 } 2327 } 2328 2329 void Window::removeTransient(Window *cl) 2330 { 2331 m_transients.removeAll(cl); 2332 if (cl->transientFor() == this) { 2333 cl->setTransientFor(nullptr); 2334 } 2335 workspace()->unconstrain(this, cl); 2336 } 2337 2338 void Window::removeTransientFromList(Window *cl) 2339 { 2340 m_transients.removeAll(cl); 2341 } 2342 2343 bool Window::isActiveFullScreen() const 2344 { 2345 if (!isFullScreen()) { 2346 return false; 2347 } 2348 2349 const auto ac = workspace()->mostRecentlyActivatedWindow(); // instead of activeWindow() - avoids flicker 2350 // according to NETWM spec implementation notes suggests 2351 // "focused windows having state _NET_WM_STATE_FULLSCREEN" to be on the highest layer. 2352 // we'll also take the screen into account 2353 return ac && (ac == this || !ac->isOnOutput(output()) || ac->allMainWindows().contains(const_cast<Window *>(this))); 2354 } 2355 2356 int Window::borderBottom() const 2357 { 2358 return isDecorated() ? decoration()->borderBottom() : 0; 2359 } 2360 2361 int Window::borderLeft() const 2362 { 2363 return isDecorated() ? decoration()->borderLeft() : 0; 2364 } 2365 2366 int Window::borderRight() const 2367 { 2368 return isDecorated() ? decoration()->borderRight() : 0; 2369 } 2370 2371 int Window::borderTop() const 2372 { 2373 return isDecorated() ? decoration()->borderTop() : 0; 2374 } 2375 2376 void Window::updateCursor() 2377 { 2378 Gravity gravity = interactiveMoveResizeGravity(); 2379 if (!isResizable() || isShade()) { 2380 gravity = Gravity::None; 2381 } 2382 CursorShape c = Qt::ArrowCursor; 2383 switch (gravity) { 2384 case Gravity::TopLeft: 2385 c = KWin::ExtendedCursor::SizeNorthWest; 2386 break; 2387 case Gravity::BottomRight: 2388 c = KWin::ExtendedCursor::SizeSouthEast; 2389 break; 2390 case Gravity::BottomLeft: 2391 c = KWin::ExtendedCursor::SizeSouthWest; 2392 break; 2393 case Gravity::TopRight: 2394 c = KWin::ExtendedCursor::SizeNorthEast; 2395 break; 2396 case Gravity::Top: 2397 c = KWin::ExtendedCursor::SizeNorth; 2398 break; 2399 case Gravity::Bottom: 2400 c = KWin::ExtendedCursor::SizeSouth; 2401 break; 2402 case Gravity::Left: 2403 c = KWin::ExtendedCursor::SizeWest; 2404 break; 2405 case Gravity::Right: 2406 c = KWin::ExtendedCursor::SizeEast; 2407 break; 2408 default: 2409 if (isInteractiveMoveResize()) { 2410 c = Qt::SizeAllCursor; 2411 } else { 2412 c = Qt::ArrowCursor; 2413 } 2414 break; 2415 } 2416 if (c == m_interactiveMoveResize.cursor) { 2417 return; 2418 } 2419 m_interactiveMoveResize.cursor = c; 2420 Q_EMIT moveResizeCursorChanged(c); 2421 } 2422 2423 void Window::leaveInteractiveMoveResize() 2424 { 2425 workspace()->setMoveResizeWindow(nullptr); 2426 setInteractiveMoveResize(false); 2427 if (workspace()->screenEdges()->isDesktopSwitchingMovingClients()) { 2428 workspace()->screenEdges()->reserveDesktopSwitching(false, Qt::Vertical | Qt::Horizontal); 2429 } 2430 if (isElectricBorderMaximizing()) { 2431 workspace()->outline()->hide(); 2432 elevate(false); 2433 } 2434 } 2435 2436 bool Window::doStartInteractiveMoveResize() 2437 { 2438 return true; 2439 } 2440 2441 void Window::doFinishInteractiveMoveResize() 2442 { 2443 } 2444 2445 bool Window::isWaitingForInteractiveMoveResizeSync() const 2446 { 2447 return false; 2448 } 2449 2450 void Window::doInteractiveResizeSync(const QRectF &) 2451 { 2452 } 2453 2454 void Window::checkQuickTilingMaximizationZones(int xroot, int yroot) 2455 { 2456 QuickTileMode mode = QuickTileFlag::None; 2457 bool innerBorder = false; 2458 2459 const auto outputs = workspace()->outputs(); 2460 for (const Output *output : outputs) { 2461 if (!output->geometry().contains(QPoint(xroot, yroot))) { 2462 continue; 2463 } 2464 2465 auto isInScreen = [&output, &outputs](const QPoint &pt) { 2466 for (const Output *other : outputs) { 2467 if (other == output) { 2468 continue; 2469 } 2470 if (other->geometry().contains(pt)) { 2471 return true; 2472 } 2473 } 2474 return false; 2475 }; 2476 2477 QRectF area = workspace()->clientArea(MaximizeArea, this, QPointF(xroot, yroot)); 2478 if (options->electricBorderTiling()) { 2479 if (xroot <= area.x() + 20) { 2480 mode |= QuickTileFlag::Left; 2481 innerBorder = isInScreen(QPoint(area.x() - 1, yroot)); 2482 } else if (xroot >= area.x() + area.width() - 20) { 2483 mode |= QuickTileFlag::Right; 2484 innerBorder = isInScreen(QPoint(area.right() + 1, yroot)); 2485 } 2486 } 2487 2488 if (mode != QuickTileMode(QuickTileFlag::None)) { 2489 if (yroot <= area.y() + area.height() * options->electricBorderCornerRatio()) { 2490 mode |= QuickTileFlag::Top; 2491 } else if (yroot >= area.y() + area.height() - area.height() * options->electricBorderCornerRatio()) { 2492 mode |= QuickTileFlag::Bottom; 2493 } 2494 } else if (options->electricBorderMaximize() && yroot <= area.y() + 5 && isMaximizable()) { 2495 mode = QuickTileFlag::Maximize; 2496 innerBorder = isInScreen(QPoint(xroot, area.y() - 1)); 2497 } 2498 break; // no point in checking other screens to contain this... "point"... 2499 } 2500 if (mode != electricBorderMode()) { 2501 setElectricBorderMode(mode); 2502 if (innerBorder) { 2503 if (!m_electricMaximizingDelay) { 2504 m_electricMaximizingDelay = new QTimer(this); 2505 m_electricMaximizingDelay->setInterval(250); 2506 m_electricMaximizingDelay->setSingleShot(true); 2507 connect(m_electricMaximizingDelay, &QTimer::timeout, this, [this]() { 2508 if (isInteractiveMove()) { 2509 setElectricBorderMaximizing(electricBorderMode() != QuickTileMode(QuickTileFlag::None)); 2510 } 2511 }); 2512 } 2513 m_electricMaximizingDelay->start(); 2514 } else { 2515 setElectricBorderMaximizing(mode != QuickTileMode(QuickTileFlag::None)); 2516 } 2517 } 2518 } 2519 2520 void Window::resetQuickTilingMaximizationZones() 2521 { 2522 if (electricBorderMode() != QuickTileMode(QuickTileFlag::None)) { 2523 if (m_electricMaximizingDelay) { 2524 m_electricMaximizingDelay->stop(); 2525 } 2526 setElectricBorderMaximizing(false); 2527 setElectricBorderMode(QuickTileFlag::None); 2528 } 2529 } 2530 2531 void Window::keyPressEvent(uint key_code) 2532 { 2533 if (!isInteractiveMove() && !isInteractiveResize()) { 2534 return; 2535 } 2536 bool is_control = key_code & Qt::CTRL; 2537 bool is_alt = key_code & Qt::ALT; 2538 key_code = key_code & ~Qt::KeyboardModifierMask; 2539 int delta = is_control ? 1 : is_alt ? 32 2540 : 8; 2541 QPointF pos = Cursors::self()->mouse()->pos(); 2542 switch (key_code) { 2543 case Qt::Key_Left: 2544 pos.rx() -= delta; 2545 break; 2546 case Qt::Key_Right: 2547 pos.rx() += delta; 2548 break; 2549 case Qt::Key_Up: 2550 pos.ry() -= delta; 2551 break; 2552 case Qt::Key_Down: 2553 pos.ry() += delta; 2554 break; 2555 case Qt::Key_Space: 2556 case Qt::Key_Return: 2557 case Qt::Key_Enter: 2558 setInteractiveMoveResizePointerButtonDown(false); 2559 finishInteractiveMoveResize(false); 2560 updateCursor(); 2561 break; 2562 case Qt::Key_Escape: 2563 setInteractiveMoveResizePointerButtonDown(false); 2564 finishInteractiveMoveResize(true); 2565 updateCursor(); 2566 break; 2567 default: 2568 return; 2569 } 2570 Cursors::self()->mouse()->setPos(pos); 2571 } 2572 2573 QSizeF Window::resizeIncrements() const 2574 { 2575 return QSizeF(1, 1); 2576 } 2577 2578 void Window::dontInteractiveMoveResize() 2579 { 2580 setInteractiveMoveResizePointerButtonDown(false); 2581 stopDelayedInteractiveMoveResize(); 2582 if (isInteractiveMoveResize()) { 2583 finishInteractiveMoveResize(false); 2584 } 2585 } 2586 2587 Gravity Window::mouseGravity() const 2588 { 2589 if (isDecorated()) { 2590 switch (decoration()->sectionUnderMouse()) { 2591 case Qt::BottomLeftSection: 2592 return Gravity::BottomLeft; 2593 case Qt::BottomRightSection: 2594 return Gravity::BottomRight; 2595 case Qt::BottomSection: 2596 return Gravity::Bottom; 2597 case Qt::LeftSection: 2598 return Gravity::Left; 2599 case Qt::RightSection: 2600 return Gravity::Right; 2601 case Qt::TopSection: 2602 return Gravity::Top; 2603 case Qt::TopLeftSection: 2604 return Gravity::TopLeft; 2605 case Qt::TopRightSection: 2606 return Gravity::TopRight; 2607 default: 2608 return Gravity::None; 2609 } 2610 } 2611 return Gravity::None; 2612 } 2613 2614 void Window::endInteractiveMoveResize() 2615 { 2616 setInteractiveMoveResizePointerButtonDown(false); 2617 stopDelayedInteractiveMoveResize(); 2618 if (isInteractiveMoveResize()) { 2619 finishInteractiveMoveResize(false); 2620 setInteractiveMoveResizeGravity(mouseGravity()); 2621 } 2622 updateCursor(); 2623 } 2624 2625 void Window::setDecoration(std::shared_ptr<KDecoration2::Decoration> decoration) 2626 { 2627 if (m_decoration.decoration == decoration) { 2628 return; 2629 } 2630 if (decoration) { 2631 QMetaObject::invokeMethod(decoration.get(), QOverload<>::of(&KDecoration2::Decoration::update), Qt::QueuedConnection); 2632 connect(decoration.get(), &KDecoration2::Decoration::shadowChanged, this, [this]() { 2633 if (!isDeleted()) { 2634 updateShadow(); 2635 } 2636 }); 2637 connect(decoration.get(), &KDecoration2::Decoration::bordersChanged, this, [this]() { 2638 if (!isDeleted()) { 2639 updateDecorationInputShape(); 2640 } 2641 }); 2642 connect(decoration.get(), &KDecoration2::Decoration::resizeOnlyBordersChanged, this, [this]() { 2643 if (!isDeleted()) { 2644 updateDecorationInputShape(); 2645 } 2646 }); 2647 connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::sizeChanged, this, [this]() { 2648 if (!isDeleted()) { 2649 updateDecorationInputShape(); 2650 } 2651 }); 2652 } 2653 m_decoration.decoration = decoration; 2654 updateDecorationInputShape(); 2655 Q_EMIT decorationChanged(); 2656 } 2657 2658 void Window::updateDecorationInputShape() 2659 { 2660 if (!isDecorated()) { 2661 m_decoration.inputRegion = QRegion(); 2662 return; 2663 } 2664 2665 const QMargins borders = decoration()->borders(); 2666 const QMargins resizeBorders = decoration()->resizeOnlyBorders(); 2667 2668 const QRectF innerRect = QRectF(QPointF(borderLeft(), borderTop()), decoratedClient()->size()); 2669 const QRectF outerRect = innerRect + borders + resizeBorders; 2670 2671 m_decoration.inputRegion = QRegion(outerRect.toAlignedRect()) - innerRect.toAlignedRect(); 2672 } 2673 2674 bool Window::decorationHasAlpha() const 2675 { 2676 if (!isDecorated() || decoration()->isOpaque()) { 2677 // either no decoration or decoration has alpha disabled 2678 return false; 2679 } 2680 return true; 2681 } 2682 2683 void Window::triggerDecorationRepaint() 2684 { 2685 if (isDecorated()) { 2686 decoration()->update(); 2687 } 2688 } 2689 2690 void Window::layoutDecorationRects(QRectF &left, QRectF &top, QRectF &right, QRectF &bottom) const 2691 { 2692 if (!isDecorated()) { 2693 return; 2694 } 2695 QRectF r = decoration()->rect(); 2696 2697 top = QRectF(r.x(), r.y(), r.width(), borderTop()); 2698 bottom = QRectF(r.x(), r.y() + r.height() - borderBottom(), 2699 r.width(), borderBottom()); 2700 left = QRectF(r.x(), r.y() + top.height(), 2701 borderLeft(), r.height() - top.height() - bottom.height()); 2702 right = QRectF(r.x() + r.width() - borderRight(), r.y() + top.height(), 2703 borderRight(), r.height() - top.height() - bottom.height()); 2704 } 2705 2706 void Window::processDecorationMove(const QPointF &localPos, const QPointF &globalPos) 2707 { 2708 if (isInteractiveMoveResizePointerButtonDown()) { 2709 handleInteractiveMoveResize(localPos.x(), localPos.y(), globalPos.x(), globalPos.y()); 2710 return; 2711 } 2712 // TODO: handle modifiers 2713 Gravity newGravity = mouseGravity(); 2714 if (newGravity != interactiveMoveResizeGravity()) { 2715 setInteractiveMoveResizeGravity(newGravity); 2716 updateCursor(); 2717 } 2718 } 2719 2720 bool Window::processDecorationButtonPress(QMouseEvent *event, bool ignoreMenu) 2721 { 2722 Options::MouseCommand com = Options::MouseNothing; 2723 bool active = isActive(); 2724 if (!wantsInput()) { // we cannot be active, use it anyway 2725 active = true; 2726 } 2727 2728 // check whether it is a double click 2729 if (event->button() == Qt::LeftButton && titlebarPositionUnderMouse()) { 2730 if (m_decoration.doubleClickTimer.isValid()) { 2731 const qint64 interval = m_decoration.doubleClickTimer.elapsed(); 2732 m_decoration.doubleClickTimer.invalidate(); 2733 if (interval > QGuiApplication::styleHints()->mouseDoubleClickInterval()) { 2734 m_decoration.doubleClickTimer.start(); // expired -> new first click and pot. init 2735 } else { 2736 Workspace::self()->performWindowOperation(this, options->operationTitlebarDblClick()); 2737 dontInteractiveMoveResize(); 2738 return false; 2739 } 2740 } else { 2741 m_decoration.doubleClickTimer.start(); // new first click and pot. init, could be invalidated by release - see below 2742 } 2743 } 2744 2745 if (event->button() == Qt::LeftButton) { 2746 com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1(); 2747 } else if (event->button() == Qt::MiddleButton) { 2748 com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2(); 2749 } else if (event->button() == Qt::RightButton) { 2750 com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3(); 2751 } 2752 if (event->button() == Qt::LeftButton 2753 && com != Options::MouseOperationsMenu // actions where it's not possible to get the matching 2754 && com != Options::MouseMinimize) // mouse release event 2755 { 2756 setInteractiveMoveResizeGravity(mouseGravity()); 2757 setInteractiveMoveResizePointerButtonDown(true); 2758 setInteractiveMoveOffset(event->pos()); 2759 setInvertedInteractiveMoveOffset(rect().bottomRight() - interactiveMoveOffset()); 2760 setUnrestrictedInteractiveMoveResize(false); 2761 startDelayedInteractiveMoveResize(); 2762 updateCursor(); 2763 } 2764 // In the new API the decoration may process the menu action to display an inactive tab's menu. 2765 // If the event is unhandled then the core will create one for the active window in the group. 2766 if (!ignoreMenu || com != Options::MouseOperationsMenu) { 2767 performMouseCommand(com, event->globalPos()); 2768 } 2769 return !( // Return events that should be passed to the decoration in the new API 2770 com == Options::MouseRaise || com == Options::MouseOperationsMenu || com == Options::MouseActivateAndRaise || com == Options::MouseActivate || com == Options::MouseActivateRaiseAndPassClick || com == Options::MouseActivateAndPassClick || com == Options::MouseNothing); 2771 } 2772 2773 void Window::processDecorationButtonRelease(QMouseEvent *event) 2774 { 2775 if (isDecorated()) { 2776 if (event->isAccepted() || !titlebarPositionUnderMouse()) { 2777 invalidateDecorationDoubleClickTimer(); // click was for the deco and shall not init a doubleclick 2778 } 2779 } 2780 2781 if (event->buttons() == Qt::NoButton) { 2782 setInteractiveMoveResizePointerButtonDown(false); 2783 stopDelayedInteractiveMoveResize(); 2784 if (isInteractiveMoveResize()) { 2785 finishInteractiveMoveResize(false); 2786 setInteractiveMoveResizeGravity(mouseGravity()); 2787 } 2788 updateCursor(); 2789 } 2790 } 2791 2792 void Window::startDecorationDoubleClickTimer() 2793 { 2794 m_decoration.doubleClickTimer.start(); 2795 } 2796 2797 void Window::invalidateDecorationDoubleClickTimer() 2798 { 2799 m_decoration.doubleClickTimer.invalidate(); 2800 } 2801 2802 bool Window::providesContextHelp() const 2803 { 2804 return false; 2805 } 2806 2807 void Window::showContextHelp() 2808 { 2809 } 2810 2811 Decoration::DecoratedClientImpl *Window::decoratedClient() const 2812 { 2813 return m_decoration.client; 2814 } 2815 2816 void Window::setDecoratedClient(Decoration::DecoratedClientImpl *client) 2817 { 2818 m_decoration.client = client; 2819 } 2820 2821 void Window::pointerEnterEvent(const QPointF &globalPos) 2822 { 2823 if (options->isShadeHover()) { 2824 cancelShadeHoverTimer(); 2825 startShadeHoverTimer(); 2826 } 2827 2828 if (options->focusPolicy() == Options::ClickToFocus || workspace()->userActionsMenu()->isShown()) { 2829 return; 2830 } 2831 2832 if (options->isAutoRaise() && !isDesktop() && !isDock() && workspace()->focusChangeEnabled() && globalPos != workspace()->focusMousePosition() && workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop(), options->isSeparateScreenFocus() ? output() : nullptr) != this) { 2833 startAutoRaise(); 2834 } 2835 2836 if (isDesktop() || isDock()) { 2837 return; 2838 } 2839 // for FocusFollowsMouse, change focus only if the mouse has actually been moved, not if the focus 2840 // change came because of window changes (e.g. closing a window) - #92290 2841 if (options->focusPolicy() != Options::FocusFollowsMouse 2842 || globalPos != workspace()->focusMousePosition()) { 2843 workspace()->requestDelayFocus(this); 2844 } 2845 } 2846 2847 void Window::pointerLeaveEvent() 2848 { 2849 cancelAutoRaise(); 2850 workspace()->cancelDelayFocus(); 2851 cancelShadeHoverTimer(); 2852 startShadeUnhoverTimer(); 2853 // TODO: send hover leave to deco 2854 // TODO: handle Options::FocusStrictlyUnderMouse 2855 } 2856 2857 QRectF Window::iconGeometry() const 2858 { 2859 if (!windowManagementInterface() || !waylandServer()) { 2860 // window management interface is only available if the surface is mapped 2861 return QRectF(); 2862 } 2863 2864 int minDistance = INT_MAX; 2865 Window *candidatePanel = nullptr; 2866 QRectF candidateGeom; 2867 2868 const auto minGeometries = windowManagementInterface()->minimizedGeometries(); 2869 for (auto i = minGeometries.constBegin(), end = minGeometries.constEnd(); i != end; ++i) { 2870 Window *panel = waylandServer()->findWindow(i.key()); 2871 if (!panel) { 2872 continue; 2873 } 2874 const int distance = QPointF(panel->pos() - pos()).manhattanLength(); 2875 if (distance < minDistance) { 2876 minDistance = distance; 2877 candidatePanel = panel; 2878 candidateGeom = i.value(); 2879 } 2880 } 2881 if (!candidatePanel) { 2882 // Check all mainwindows of this window. 2883 const auto windows = mainWindows(); 2884 for (Window *mainWindow : windows) { 2885 const auto geom = mainWindow->iconGeometry(); 2886 if (geom.isValid()) { 2887 return geom; 2888 } 2889 } 2890 return QRectF(); 2891 } 2892 return candidateGeom.translated(candidatePanel->pos()); 2893 } 2894 2895 QRectF Window::virtualKeyboardGeometry() const 2896 { 2897 return m_virtualKeyboardGeometry; 2898 } 2899 2900 void Window::setVirtualKeyboardGeometry(const QRectF &geo) 2901 { 2902 // No keyboard anymore 2903 if (geo.isEmpty() && !m_keyboardGeometryRestore.isEmpty()) { 2904 const QRectF availableArea = workspace()->clientArea(MaximizeArea, this); 2905 QRectF newWindowGeometry = (requestedMaximizeMode() & MaximizeHorizontal) ? availableArea : m_keyboardGeometryRestore; 2906 moveResize(newWindowGeometry); 2907 m_keyboardGeometryRestore = QRectF(); 2908 } else if (geo.isEmpty()) { 2909 return; 2910 // The keyboard has just been opened (rather than resized) save window geometry for a restore 2911 } else if (m_keyboardGeometryRestore.isEmpty()) { 2912 m_keyboardGeometryRestore = moveResizeGeometry(); 2913 } 2914 2915 m_virtualKeyboardGeometry = geo; 2916 2917 // Don't resize Desktop and fullscreen windows 2918 if (isRequestedFullScreen() || isDesktop()) { 2919 return; 2920 } 2921 2922 if (!geo.intersects(m_keyboardGeometryRestore)) { 2923 return; 2924 } 2925 2926 const QRectF availableArea = workspace()->clientArea(MaximizeArea, this); 2927 QRectF newWindowGeometry = (requestedMaximizeMode() & MaximizeHorizontal) ? availableArea : m_keyboardGeometryRestore; 2928 newWindowGeometry.setHeight(std::min(newWindowGeometry.height(), geo.top() - availableArea.top())); 2929 newWindowGeometry.moveTop(std::max(geo.top() - newWindowGeometry.height(), availableArea.top())); 2930 newWindowGeometry = newWindowGeometry.intersected(availableArea); 2931 moveResize(newWindowGeometry); 2932 } 2933 2934 QRectF Window::keyboardGeometryRestore() const 2935 { 2936 return m_keyboardGeometryRestore; 2937 } 2938 2939 void Window::setKeyboardGeometryRestore(const QRectF &geom) 2940 { 2941 m_keyboardGeometryRestore = geom; 2942 } 2943 2944 bool Window::dockWantsInput() const 2945 { 2946 return false; 2947 } 2948 2949 void Window::setDesktopFileName(const QString &name) 2950 { 2951 const QString effectiveName = rules()->checkDesktopFile(name); 2952 if (effectiveName == m_desktopFileName) { 2953 return; 2954 } 2955 m_desktopFileName = effectiveName; 2956 updateWindowRules(Rules::DesktopFile); 2957 Q_EMIT desktopFileNameChanged(); 2958 } 2959 2960 QString Window::iconFromDesktopFile(const QString &desktopFileName) 2961 { 2962 const QString absolutePath = findDesktopFile(desktopFileName); 2963 if (absolutePath.isEmpty()) { 2964 return {}; 2965 } 2966 2967 KDesktopFile df(absolutePath); 2968 return df.readIcon(); 2969 } 2970 2971 QString Window::iconFromDesktopFile() const 2972 { 2973 return iconFromDesktopFile(m_desktopFileName); 2974 } 2975 2976 QString Window::findDesktopFile(const QString &desktopFileName) 2977 { 2978 if (desktopFileName.isEmpty()) { 2979 return {}; 2980 } 2981 2982 const QString desktopFileNameWithPrefix = desktopFileName + QLatin1String(".desktop"); 2983 QString desktopFilePath; 2984 2985 if (QDir::isAbsolutePath(desktopFileName)) { 2986 if (QFile::exists(desktopFileNameWithPrefix)) { 2987 desktopFilePath = desktopFileNameWithPrefix; 2988 } else { 2989 desktopFilePath = desktopFileName; 2990 } 2991 } 2992 2993 if (desktopFilePath.isEmpty()) { 2994 desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, 2995 desktopFileNameWithPrefix); 2996 } 2997 if (desktopFilePath.isEmpty()) { 2998 desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, 2999 desktopFileName); 3000 } 3001 return desktopFilePath; 3002 } 3003 3004 bool Window::hasApplicationMenu() const 3005 { 3006 return Workspace::self()->applicationMenu()->applicationMenuEnabled() && !m_applicationMenuServiceName.isEmpty() && !m_applicationMenuObjectPath.isEmpty(); 3007 } 3008 3009 void Window::updateApplicationMenuServiceName(const QString &serviceName) 3010 { 3011 const bool old_hasApplicationMenu = hasApplicationMenu(); 3012 3013 m_applicationMenuServiceName = serviceName; 3014 3015 const bool new_hasApplicationMenu = hasApplicationMenu(); 3016 3017 Q_EMIT applicationMenuChanged(); 3018 if (old_hasApplicationMenu != new_hasApplicationMenu) { 3019 Q_EMIT hasApplicationMenuChanged(new_hasApplicationMenu); 3020 } 3021 } 3022 3023 void Window::updateApplicationMenuObjectPath(const QString &objectPath) 3024 { 3025 const bool old_hasApplicationMenu = hasApplicationMenu(); 3026 3027 m_applicationMenuObjectPath = objectPath; 3028 3029 const bool new_hasApplicationMenu = hasApplicationMenu(); 3030 3031 Q_EMIT applicationMenuChanged(); 3032 if (old_hasApplicationMenu != new_hasApplicationMenu) { 3033 Q_EMIT hasApplicationMenuChanged(new_hasApplicationMenu); 3034 } 3035 } 3036 3037 void Window::setApplicationMenuActive(bool applicationMenuActive) 3038 { 3039 if (m_applicationMenuActive != applicationMenuActive) { 3040 m_applicationMenuActive = applicationMenuActive; 3041 Q_EMIT applicationMenuActiveChanged(applicationMenuActive); 3042 } 3043 } 3044 3045 void Window::showApplicationMenu(int actionId) 3046 { 3047 if (isDecorated()) { 3048 decoration()->showApplicationMenu(actionId); 3049 } else { 3050 // we don't know where the application menu button will be, show it in the top left corner instead 3051 Workspace::self()->showApplicationMenu(QRect(), this, actionId); 3052 } 3053 } 3054 3055 bool Window::unresponsive() const 3056 { 3057 return m_unresponsive; 3058 } 3059 3060 void Window::setUnresponsive(bool unresponsive) 3061 { 3062 if (m_unresponsive != unresponsive) { 3063 m_unresponsive = unresponsive; 3064 Q_EMIT unresponsiveChanged(m_unresponsive); 3065 Q_EMIT captionChanged(); 3066 } 3067 } 3068 3069 QString Window::shortcutCaptionSuffix() const 3070 { 3071 if (shortcut().isEmpty()) { 3072 return QString(); 3073 } 3074 return QLatin1String(" {") + shortcut().toString() + QLatin1Char('}'); 3075 } 3076 3077 QString Window::caption() const 3078 { 3079 QString cap = captionNormal() + captionSuffix(); 3080 if (unresponsive()) { 3081 cap += QLatin1String(" "); 3082 cap += i18nc("Application is not responding, appended to window title", "(Not Responding)"); 3083 } 3084 return cap; 3085 } 3086 3087 /** 3088 * Returns the list of activities the window window is on. 3089 * if it's on all activities, the list will be empty. 3090 * Don't use this, use isOnActivity() and friends (from class Window) 3091 */ 3092 QStringList Window::activities() const 3093 { 3094 return m_activityList; 3095 } 3096 3097 bool Window::isOnCurrentActivity() const 3098 { 3099 #if KWIN_BUILD_ACTIVITIES 3100 if (!Workspace::self()->activities()) { 3101 return true; 3102 } 3103 return isOnActivity(Workspace::self()->activities()->current()); 3104 #else 3105 return true; 3106 #endif 3107 } 3108 3109 /** 3110 * Sets whether the window is on @p activity. 3111 * If you remove it from its last activity, then it's on all activities. 3112 * 3113 * Note: If it was on all activities and you try to remove it from one, nothing will happen; 3114 * I don't think that's an important enough use case to handle here. 3115 */ 3116 void Window::setOnActivity(const QString &activity, bool enable) 3117 { 3118 #if KWIN_BUILD_ACTIVITIES 3119 if (!Workspace::self()->activities()) { 3120 return; 3121 } 3122 QStringList newActivitiesList = activities(); 3123 if (newActivitiesList.contains(activity) == enable) { 3124 // nothing to do 3125 return; 3126 } 3127 if (enable) { 3128 QStringList allActivities = Workspace::self()->activities()->all(); 3129 if (!allActivities.contains(activity)) { 3130 // bogus ID 3131 return; 3132 } 3133 newActivitiesList.append(activity); 3134 } else { 3135 newActivitiesList.removeOne(activity); 3136 } 3137 setOnActivities(newActivitiesList); 3138 #endif 3139 } 3140 3141 /** 3142 * set exactly which activities this window is on 3143 */ 3144 void Window::setOnActivities(const QStringList &newActivitiesList) 3145 { 3146 #if KWIN_BUILD_ACTIVITIES 3147 if (!Workspace::self()->activities()) { 3148 return; 3149 } 3150 if (Workspace::self()->activities()->serviceStatus() != KActivities::Consumer::Running) { 3151 return; 3152 } 3153 const auto allActivities = Workspace::self()->activities()->all(); 3154 const auto activityList = [&] { 3155 auto result = rules()->checkActivity(newActivitiesList); 3156 3157 const auto it = std::remove_if(result.begin(), result.end(), [=](const QString &activity) { 3158 return !allActivities.contains(activity); 3159 }); 3160 result.erase(it, result.end()); 3161 return result; 3162 }(); 3163 3164 const auto allActivityExplicitlyRequested = activityList.isEmpty() || activityList.contains(Activities::nullUuid()); 3165 const auto allActivitiesCovered = activityList.size() > 1 && activityList.size() == allActivities.size(); 3166 3167 if (allActivityExplicitlyRequested || allActivitiesCovered) { 3168 if (!m_activityList.isEmpty()) { 3169 m_activityList.clear(); 3170 doSetOnActivities(m_activityList); 3171 } 3172 } else { 3173 if (m_activityList != activityList) { 3174 m_activityList = activityList; 3175 doSetOnActivities(m_activityList); 3176 } 3177 } 3178 3179 updateActivities(false); 3180 #endif 3181 } 3182 3183 void Window::doSetOnActivities(const QStringList &activityList) 3184 { 3185 } 3186 3187 /** 3188 * if @p all is true, sets on all activities. 3189 * if it's false, sets it to only be on the current activity 3190 */ 3191 void Window::setOnAllActivities(bool all) 3192 { 3193 #if KWIN_BUILD_ACTIVITIES 3194 if (all == isOnAllActivities()) { 3195 return; 3196 } 3197 if (all) { 3198 setOnActivities(QStringList()); 3199 } else { 3200 setOnActivity(Workspace::self()->activities()->current(), true); 3201 } 3202 #endif 3203 } 3204 3205 /** 3206 * update after activities changed 3207 */ 3208 void Window::updateActivities(bool includeTransients) 3209 { 3210 if (m_activityUpdatesBlocked) { 3211 m_blockedActivityUpdatesRequireTransients |= includeTransients; 3212 return; 3213 } 3214 Q_EMIT activitiesChanged(); 3215 m_blockedActivityUpdatesRequireTransients = false; // reset 3216 Workspace::self()->focusChain()->update(this, FocusChain::MakeFirst); 3217 updateWindowRules(Rules::Activity); 3218 } 3219 3220 void Window::blockActivityUpdates(bool b) 3221 { 3222 if (b) { 3223 ++m_activityUpdatesBlocked; 3224 } else { 3225 Q_ASSERT(m_activityUpdatesBlocked); 3226 --m_activityUpdatesBlocked; 3227 if (!m_activityUpdatesBlocked) { 3228 updateActivities(m_blockedActivityUpdatesRequireTransients); 3229 } 3230 } 3231 } 3232 3233 bool Window::groupTransient() const 3234 { 3235 return false; 3236 } 3237 3238 const Group *Window::group() const 3239 { 3240 return nullptr; 3241 } 3242 3243 Group *Window::group() 3244 { 3245 return nullptr; 3246 } 3247 3248 QPointF Window::framePosToClientPos(const QPointF &point) const 3249 { 3250 return point + QPointF(borderLeft(), borderTop()); 3251 } 3252 3253 QPointF Window::clientPosToFramePos(const QPointF &point) const 3254 { 3255 return point - QPointF(borderLeft(), borderTop()); 3256 } 3257 3258 QSizeF Window::frameSizeToClientSize(const QSizeF &size) const 3259 { 3260 const qreal width = size.width() - borderLeft() - borderRight(); 3261 const qreal height = size.height() - borderTop() - borderBottom(); 3262 return QSizeF(width, height); 3263 } 3264 3265 QSizeF Window::clientSizeToFrameSize(const QSizeF &size) const 3266 { 3267 const qreal width = size.width() + borderLeft() + borderRight(); 3268 const qreal height = size.height() + borderTop() + borderBottom(); 3269 return QSizeF(width, height); 3270 } 3271 3272 QRectF Window::frameRectToClientRect(const QRectF &rect) const 3273 { 3274 const QPointF position = framePosToClientPos(rect.topLeft()); 3275 const QSizeF size = frameSizeToClientSize(rect.size()); 3276 return QRectF(position, size); 3277 } 3278 3279 QRectF Window::clientRectToFrameRect(const QRectF &rect) const 3280 { 3281 const QPointF position = clientPosToFramePos(rect.topLeft()); 3282 const QSizeF size = clientSizeToFrameSize(rect.size()); 3283 return QRectF(position, size); 3284 } 3285 3286 QRectF Window::moveResizeGeometry() const 3287 { 3288 return m_moveResizeGeometry; 3289 } 3290 3291 void Window::setMoveResizeGeometry(const QRectF &geo) 3292 { 3293 m_moveResizeGeometry = geo; 3294 m_moveResizeOutput = workspace()->outputAt(geo.center()); 3295 } 3296 3297 Output *Window::moveResizeOutput() const 3298 { 3299 return m_moveResizeOutput; 3300 } 3301 3302 void Window::setMoveResizeOutput(Output *output) 3303 { 3304 m_moveResizeOutput = output; 3305 } 3306 3307 void Window::move(const QPointF &point) 3308 { 3309 const QRectF rect = QRectF(point, m_moveResizeGeometry.size()); 3310 3311 setMoveResizeGeometry(rect); 3312 moveResizeInternal(rect, MoveResizeMode::Move); 3313 } 3314 3315 void Window::resize(const QSizeF &size) 3316 { 3317 const QRectF rect = QRectF(m_moveResizeGeometry.topLeft(), size); 3318 3319 setMoveResizeGeometry(rect); 3320 moveResizeInternal(rect, MoveResizeMode::Resize); 3321 } 3322 3323 void Window::moveResize(const QRectF &rect) 3324 { 3325 setMoveResizeGeometry(rect); 3326 moveResizeInternal(rect, MoveResizeMode::MoveResize); 3327 } 3328 3329 void Window::setElectricBorderMode(QuickTileMode mode) 3330 { 3331 if (mode != QuickTileMode(QuickTileFlag::Maximize)) { 3332 // sanitize the mode, ie. simplify "invalid" combinations 3333 if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal)) { 3334 mode &= ~QuickTileMode(QuickTileFlag::Horizontal); 3335 } 3336 if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical)) { 3337 mode &= ~QuickTileMode(QuickTileFlag::Vertical); 3338 } 3339 } 3340 m_electricMode = mode; 3341 } 3342 3343 void Window::setElectricBorderMaximizing(bool maximizing) 3344 { 3345 m_electricMaximizing = maximizing; 3346 if (maximizing) { 3347 workspace()->outline()->show(quickTileGeometry(electricBorderMode(), Cursors::self()->mouse()->pos()).toRect(), moveResizeGeometry().toRect()); 3348 } else { 3349 workspace()->outline()->hide(); 3350 } 3351 elevate(maximizing); 3352 } 3353 3354 QRectF Window::quickTileGeometry(QuickTileMode mode, const QPointF &pos) const 3355 { 3356 if (mode == QuickTileMode(QuickTileFlag::Maximize)) { 3357 if (requestedMaximizeMode() == MaximizeFull) { 3358 return geometryRestore(); 3359 } else { 3360 return workspace()->clientArea(MaximizeArea, this, pos); 3361 } 3362 } 3363 3364 Output *output = workspace()->outputAt(pos); 3365 3366 if (mode & QuickTileFlag::Custom) { 3367 Tile *tile = workspace()->tileManager(output)->bestTileForPosition(pos); 3368 if (tile) { 3369 return tile->windowGeometry(); 3370 } else { 3371 return QRectF(); 3372 } 3373 } 3374 3375 Tile *tile = workspace()->tileManager(output)->quickTile(mode); 3376 if (tile) { 3377 return tile->windowGeometry(); 3378 } 3379 return workspace()->clientArea(MaximizeArea, this, pos); 3380 } 3381 3382 void Window::updateElectricGeometryRestore() 3383 { 3384 m_electricGeometryRestore = geometryRestore(); 3385 if (m_interactiveMoveResize.initialQuickTileMode == QuickTileMode(QuickTileFlag::None)) { 3386 if (!(requestedMaximizeMode() & MaximizeHorizontal)) { 3387 m_electricGeometryRestore.setX(x()); 3388 m_electricGeometryRestore.setWidth(width()); 3389 } 3390 if (!(requestedMaximizeMode() & MaximizeVertical)) { 3391 m_electricGeometryRestore.setY(y()); 3392 m_electricGeometryRestore.setHeight(height()); 3393 } 3394 } 3395 } 3396 3397 QRectF Window::quickTileGeometryRestore() const 3398 { 3399 if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { 3400 // If the window is tiled, geometryRestore() already has a good value. 3401 return geometryRestore(); 3402 } 3403 3404 if (isElectricBorderMaximizing()) { 3405 return m_electricGeometryRestore; 3406 } else { 3407 return moveResizeGeometry(); 3408 } 3409 } 3410 3411 void Window::setQuickTileMode(QuickTileMode mode, bool keyboard) 3412 { 3413 // Only allow quick tile on a regular window. 3414 if (!isResizable()) { 3415 return; 3416 } 3417 if (isAppletPopup()) { 3418 return; 3419 } 3420 3421 workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event 3422 3423 GeometryUpdatesBlocker blocker(this); 3424 3425 setTile(nullptr); 3426 3427 if (mode == QuickTileMode(QuickTileFlag::Maximize)) { 3428 if (requestedMaximizeMode() == MaximizeFull) { 3429 m_quickTileMode = int(QuickTileFlag::None); 3430 setMaximize(false, false); 3431 } else { 3432 QRectF effectiveGeometryRestore = quickTileGeometryRestore(); 3433 m_quickTileMode = int(QuickTileFlag::Maximize); 3434 setMaximize(true, true); 3435 setGeometryRestore(effectiveGeometryRestore); 3436 } 3437 doSetQuickTileMode(); 3438 Q_EMIT quickTileModeChanged(); 3439 return; 3440 } 3441 3442 // sanitize the mode, ie. simplify "invalid" combinations 3443 if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal)) { 3444 mode &= ~QuickTileMode(QuickTileFlag::Horizontal); 3445 } 3446 if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical)) { 3447 mode &= ~QuickTileMode(QuickTileFlag::Vertical); 3448 } 3449 3450 // restore from maximized so that it is possible to tile maximized windows with one hit or by dragging 3451 if (requestedMaximizeMode() != MaximizeRestore) { 3452 3453 if (mode != QuickTileMode(QuickTileFlag::None)) { 3454 m_quickTileMode = int(QuickTileFlag::None); // Temporary, so the maximize code doesn't get all confused 3455 3456 setMaximize(false, false); 3457 3458 moveResize(quickTileGeometry(mode, keyboard ? moveResizeGeometry().center() : Cursors::self()->mouse()->pos())); 3459 // Store the mode change 3460 m_quickTileMode = mode; 3461 } else { 3462 m_quickTileMode = mode; 3463 setMaximize(false, false); 3464 } 3465 3466 doSetQuickTileMode(); 3467 Q_EMIT quickTileModeChanged(); 3468 3469 return; 3470 } 3471 3472 QPointF whichScreen = keyboard ? moveResizeGeometry().center() : Cursors::self()->mouse()->pos(); 3473 if (mode != QuickTileMode(QuickTileFlag::None)) { 3474 // If trying to tile to the side that the window is already tiled to move the window to the next 3475 // screen near the tile if it exists and swap the tile side, otherwise toggle the mode (set QuickTileFlag::None) 3476 if (quickTileMode() == mode) { 3477 Output *currentOutput = moveResizeOutput(); 3478 Output *nextOutput = currentOutput; 3479 Output *candidateOutput = currentOutput; 3480 if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Left)) { 3481 candidateOutput = workspace()->findOutput(nextOutput, Workspace::DirectionWest); 3482 } else if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Right)) { 3483 candidateOutput = workspace()->findOutput(nextOutput, Workspace::DirectionEast); 3484 } 3485 bool shiftHorizontal = candidateOutput != nextOutput; 3486 nextOutput = candidateOutput; 3487 if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Top)) { 3488 candidateOutput = workspace()->findOutput(nextOutput, Workspace::DirectionNorth); 3489 } else if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Bottom)) { 3490 candidateOutput = workspace()->findOutput(nextOutput, Workspace::DirectionSouth); 3491 } 3492 bool shiftVertical = candidateOutput != nextOutput; 3493 nextOutput = candidateOutput; 3494 3495 if (nextOutput == currentOutput) { 3496 mode = QuickTileFlag::None; // No other screens in the tile direction, toggle tiling 3497 } else { 3498 // Move to other screen 3499 moveResize(geometryRestore().translated(nextOutput->geometry().topLeft() - currentOutput->geometry().topLeft())); 3500 whichScreen = nextOutput->geometry().center(); 3501 3502 // Swap sides 3503 if (shiftHorizontal) { 3504 mode = (~mode & QuickTileFlag::Horizontal) | (mode & QuickTileFlag::Vertical); 3505 } 3506 if (shiftVertical) { 3507 mode = (~mode & QuickTileFlag::Vertical) | (mode & QuickTileFlag::Horizontal); 3508 } 3509 } 3510 } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) { 3511 // Not coming out of an existing tile, not shifting monitors, we're setting a brand new tile. 3512 // Store geometry first, so we can go out of this tile later. 3513 setGeometryRestore(quickTileGeometryRestore()); 3514 } 3515 3516 m_quickTileMode = mode; 3517 } 3518 3519 if (mode == QuickTileMode(QuickTileFlag::None)) { 3520 setTile(nullptr); 3521 m_quickTileMode = int(QuickTileFlag::None); 3522 // Untiling, so just restore geometry, and we're done. 3523 if (geometryRestore().isValid()) { // invalid if we started maximized and wait for placement 3524 moveResize(geometryRestore()); 3525 } 3526 checkWorkspacePosition(); // Just in case it's a different screen 3527 } else if (mode == QuickTileMode(QuickTileFlag::Custom)) { 3528 Tile *tile = nullptr; 3529 if (keyboard) { 3530 tile = workspace()->tileManager(output())->bestTileForPosition(moveResizeGeometry().center()); 3531 } else { 3532 Output *output = workspace()->outputAt(Cursors::self()->mouse()->pos()); 3533 tile = workspace()->tileManager(output)->bestTileForPosition(Cursors::self()->mouse()->pos()); 3534 } 3535 setTile(tile); 3536 } else { 3537 // Use whichScreen to move to next screen when retiling to the same edge as the old behavior 3538 Output *output = workspace()->outputAt(whichScreen); 3539 Tile *tile = workspace()->tileManager(output)->quickTile(mode); 3540 setTile(tile); 3541 } 3542 3543 doSetQuickTileMode(); 3544 Q_EMIT quickTileModeChanged(); 3545 } 3546 3547 void Window::setTile(Tile *tile) 3548 { 3549 if (m_tile == tile) { 3550 return; 3551 } else if (m_tile) { 3552 m_tile->removeWindow(this); 3553 } 3554 3555 m_tile = tile; 3556 3557 if (m_tile) { 3558 m_tile->addWindow(this); 3559 } 3560 3561 Q_EMIT tileChanged(tile); 3562 } 3563 3564 Tile *Window::tile() const 3565 { 3566 return m_tile; 3567 } 3568 3569 void Window::doSetQuickTileMode() 3570 { 3571 } 3572 3573 void Window::doSetHidden() 3574 { 3575 } 3576 3577 void Window::doSetHiddenByShowDesktop() 3578 { 3579 } 3580 3581 QRectF Window::moveToArea(const QRectF &geometry, const QRectF &oldArea, const QRectF &newArea) 3582 { 3583 QRectF ret = geometry; 3584 // move the window to have the same relative position to the center of the screen 3585 // (i.e. one near the middle of the right edge will also end up near the middle of the right edge) 3586 QPointF center = geometry.center() - oldArea.center(); 3587 center.setX(center.x() * newArea.width() / oldArea.width()); 3588 center.setY(center.y() * newArea.height() / oldArea.height()); 3589 center += newArea.center(); 3590 ret.moveCenter(center); 3591 3592 // If the window was inside the old screen area, explicitly make sure its inside also the new screen area 3593 if (oldArea.contains(geometry)) { 3594 ret = keepInArea(ret, newArea); 3595 } 3596 return ret; 3597 } 3598 3599 QRectF Window::ensureSpecialStateGeometry(const QRectF &geometry) 3600 { 3601 if (isRequestedFullScreen()) { 3602 return workspace()->clientArea(FullScreenArea, this, geometry.center()); 3603 } else if (requestedMaximizeMode() != MaximizeRestore) { 3604 const QRectF maximizeArea = workspace()->clientArea(MaximizeArea, this, geometry.center()); 3605 QRectF ret = geometry; 3606 if (requestedMaximizeMode() & MaximizeHorizontal) { 3607 ret.setX(maximizeArea.x()); 3608 ret.setWidth(maximizeArea.width()); 3609 } 3610 if (requestedMaximizeMode() & MaximizeVertical) { 3611 ret.setY(maximizeArea.y()); 3612 ret.setHeight(maximizeArea.height()); 3613 } 3614 return ret; 3615 } else if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { 3616 return quickTileGeometry(quickTileMode(), geometry.center()); 3617 } else { 3618 return geometry; 3619 } 3620 } 3621 3622 void Window::sendToOutput(Output *newOutput) 3623 { 3624 newOutput = rules()->checkOutput(newOutput); 3625 if (isActive()) { 3626 workspace()->setActiveOutput(newOutput); 3627 // might impact the layer of a fullscreen window 3628 const auto windows = workspace()->windows(); 3629 for (Window *other : windows) { 3630 if (other->isFullScreen() && other->output() == newOutput) { 3631 other->updateLayer(); 3632 } 3633 } 3634 } 3635 if (moveResizeOutput() == newOutput) { 3636 return; 3637 } 3638 3639 const QRectF oldGeom = moveResizeGeometry(); 3640 const QRectF oldScreenArea = workspace()->clientArea(MaximizeArea, this, moveResizeOutput()); 3641 const QRectF screenArea = workspace()->clientArea(MaximizeArea, this, newOutput); 3642 3643 if (m_quickTileMode == QuickTileMode(QuickTileFlag::Custom)) { 3644 setTile(nullptr); 3645 } 3646 3647 QRectF newGeom = moveToArea(oldGeom, oldScreenArea, screenArea); 3648 newGeom = ensureSpecialStateGeometry(newGeom); 3649 moveResize(newGeom); 3650 3651 // move geometry restores to the new output as well 3652 m_fullscreenGeometryRestore = moveToArea(m_fullscreenGeometryRestore, oldScreenArea, screenArea); 3653 m_maximizeGeometryRestore = moveToArea(m_maximizeGeometryRestore, oldScreenArea, screenArea); 3654 3655 auto tso = workspace()->ensureStackingOrder(transients()); 3656 for (auto it = tso.constBegin(), end = tso.constEnd(); it != end; ++it) { 3657 (*it)->sendToOutput(newOutput); 3658 } 3659 } 3660 3661 void Window::checkWorkspacePosition(QRectF oldGeometry, const VirtualDesktop *oldDesktop) 3662 { 3663 if (isDeleted()) { 3664 qCWarning(KWIN_CORE) << "Window::checkWorkspacePosition: called for a closed window. Consider this a bug"; 3665 return; 3666 } 3667 if (isDock() || isDesktop() || !isPlaceable()) { 3668 return; 3669 } 3670 3671 QRectF newGeom = moveResizeGeometry(); 3672 3673 if (!oldGeometry.isValid()) { 3674 oldGeometry = newGeom; 3675 } 3676 3677 VirtualDesktop *desktop = !isOnCurrentDesktop() ? desktops().constLast() : VirtualDesktopManager::self()->currentDesktop(); 3678 if (!oldDesktop) { 3679 oldDesktop = desktop; 3680 } 3681 3682 // If the window was touching an edge before but not now move it so it is again. 3683 // Old and new maximums have different starting values so windows on the screen 3684 // edge will move when a new strut is placed on the edge. 3685 QRect oldScreenArea; 3686 QRect screenArea; 3687 if (workspace()->inUpdateClientArea()) { 3688 // check if the window is on an about to be destroyed output 3689 Output *newOutput = moveResizeOutput(); 3690 if (!workspace()->outputs().contains(newOutput)) { 3691 newOutput = workspace()->outputAt(newGeom.center()); 3692 } 3693 // we need to find the screen area as it was before the change 3694 oldScreenArea = workspace()->previousScreenSizes().value(moveResizeOutput()); 3695 if (oldScreenArea.isNull()) { 3696 oldScreenArea = newOutput->geometry(); 3697 } 3698 screenArea = newOutput->geometry(); 3699 newGeom.translate(screenArea.topLeft() - oldScreenArea.topLeft()); 3700 } else { 3701 oldScreenArea = workspace()->clientArea(ScreenArea, workspace()->outputAt(oldGeometry.center()), oldDesktop).toRect(); 3702 screenArea = workspace()->clientArea(ScreenArea, this, newGeom.center()).toRect(); 3703 } 3704 3705 if (isRequestedFullScreen() || requestedMaximizeMode() != MaximizeRestore || quickTileMode() != QuickTileMode(QuickTileFlag::None)) { 3706 moveResize(ensureSpecialStateGeometry(newGeom)); 3707 m_fullscreenGeometryRestore = moveToArea(m_fullscreenGeometryRestore, oldScreenArea, screenArea); 3708 m_maximizeGeometryRestore = moveToArea(m_maximizeGeometryRestore, oldScreenArea, screenArea); 3709 return; 3710 } 3711 3712 const QRect oldGeomTall = QRect(oldGeometry.x(), oldScreenArea.y(), oldGeometry.width(), oldScreenArea.height()); // Full screen height 3713 const QRect oldGeomWide = QRect(oldScreenArea.x(), oldGeometry.y(), oldScreenArea.width(), oldGeometry.height()); // Full screen width 3714 int oldTopMax = oldScreenArea.y(); 3715 int oldRightMax = oldScreenArea.x() + oldScreenArea.width(); 3716 int oldBottomMax = oldScreenArea.y() + oldScreenArea.height(); 3717 int oldLeftMax = oldScreenArea.x(); 3718 int topMax = screenArea.y(); 3719 int rightMax = screenArea.x() + screenArea.width(); 3720 int bottomMax = screenArea.y() + screenArea.height(); 3721 int leftMax = screenArea.x(); 3722 const QRect newGeomTall = QRect(newGeom.x(), screenArea.y(), newGeom.width(), screenArea.height()); // Full screen height 3723 const QRect newGeomWide = QRect(screenArea.x(), newGeom.y(), screenArea.width(), newGeom.height()); // Full screen width 3724 // Get the max strut point for each side where the window is (E.g. Highest point for 3725 // the bottom struts bounded by the window's left and right sides). 3726 3727 // These 4 compute old bounds ... 3728 auto moveAreaFunc = workspace()->inUpdateClientArea() ? &Workspace::previousRestrictedMoveArea : //... the restricted areas changed 3729 &Workspace::restrictedMoveArea; //... when e.g. active desktop or screen changes 3730 3731 for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaTop)) { 3732 QRect rect = r & oldGeomTall; 3733 if (!rect.isEmpty()) { 3734 oldTopMax = std::max(oldTopMax, rect.y() + rect.height()); 3735 } 3736 } 3737 for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaRight)) { 3738 QRect rect = r & oldGeomWide; 3739 if (!rect.isEmpty()) { 3740 oldRightMax = std::min(oldRightMax, rect.x()); 3741 } 3742 } 3743 for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaBottom)) { 3744 QRect rect = r & oldGeomTall; 3745 if (!rect.isEmpty()) { 3746 oldBottomMax = std::min(oldBottomMax, rect.y()); 3747 } 3748 } 3749 for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaLeft)) { 3750 QRect rect = r & oldGeomWide; 3751 if (!rect.isEmpty()) { 3752 oldLeftMax = std::max(oldLeftMax, rect.x() + rect.width()); 3753 } 3754 } 3755 3756 // These 4 compute new bounds 3757 for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaTop)) { 3758 QRect rect = r & newGeomTall; 3759 if (!rect.isEmpty()) { 3760 topMax = std::max(topMax, rect.y() + rect.height()); 3761 } 3762 } 3763 for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaRight)) { 3764 QRect rect = r & newGeomWide; 3765 if (!rect.isEmpty()) { 3766 rightMax = std::min(rightMax, rect.x()); 3767 } 3768 } 3769 for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaBottom)) { 3770 QRect rect = r & newGeomTall; 3771 if (!rect.isEmpty()) { 3772 bottomMax = std::min(bottomMax, rect.y()); 3773 } 3774 } 3775 for (const QRect &r : workspace()->restrictedMoveArea(desktop, StrutAreaLeft)) { 3776 QRect rect = r & newGeomWide; 3777 if (!rect.isEmpty()) { 3778 leftMax = std::max(leftMax, rect.x() + rect.width()); 3779 } 3780 } 3781 3782 // Check if the sides were inside or touching but are no longer 3783 enum { 3784 Left = 0, 3785 Top, 3786 Right, 3787 Bottom, 3788 }; 3789 bool keep[4] = {false, false, false, false}; 3790 bool save[4] = {false, false, false, false}; 3791 if (oldGeometry.x() >= oldLeftMax) { 3792 save[Left] = newGeom.x() < leftMax; 3793 } 3794 if (oldGeometry.x() == oldLeftMax) { 3795 keep[Left] = newGeom.x() != leftMax; 3796 } 3797 3798 if (oldGeometry.y() >= oldTopMax) { 3799 save[Top] = newGeom.y() < topMax; 3800 } 3801 if (oldGeometry.y() == oldTopMax) { 3802 keep[Top] = newGeom.y() != topMax; 3803 } 3804 3805 if (oldGeometry.right() <= oldRightMax) { 3806 save[Right] = newGeom.right() > rightMax; 3807 } 3808 if (oldGeometry.right() == oldRightMax) { 3809 keep[Right] = newGeom.right() != rightMax; 3810 } 3811 3812 if (oldGeometry.bottom() <= oldBottomMax) { 3813 save[Bottom] = newGeom.bottom() > bottomMax; 3814 } 3815 if (oldGeometry.bottom() == oldBottomMax) { 3816 keep[Bottom] = newGeom.bottom() != bottomMax; 3817 } 3818 3819 // if randomly touches opposing edges, do not favor either 3820 if (keep[Left] && keep[Right]) { 3821 keep[Left] = keep[Right] = false; 3822 } 3823 if (keep[Top] && keep[Bottom]) { 3824 keep[Top] = keep[Bottom] = false; 3825 } 3826 3827 if (save[Left] || keep[Left]) { 3828 newGeom.moveLeft(std::max(leftMax, screenArea.x())); 3829 } 3830 if (save[Top] || keep[Top]) { 3831 newGeom.moveTop(std::max(topMax, screenArea.y())); 3832 } 3833 if (save[Right] || keep[Right]) { 3834 newGeom.moveRight(std::min(rightMax, screenArea.right()) + 1); 3835 } 3836 if (save[Bottom] || keep[Bottom]) { 3837 newGeom.moveBottom(std::min(bottomMax, screenArea.bottom()) + 1); 3838 } 3839 3840 if (oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax) { 3841 newGeom.setLeft(std::max(leftMax, screenArea.x())); 3842 } 3843 if (oldGeometry.y() >= oldTopMax && newGeom.y() < topMax) { 3844 newGeom.setTop(std::max(topMax, screenArea.y())); 3845 } 3846 3847 checkOffscreenPosition(&newGeom, screenArea); 3848 // Obey size hints. TODO: We really should make sure it stays in the right place 3849 if (!isShade()) { 3850 newGeom.setSize(constrainFrameSize(newGeom.size())); 3851 } 3852 3853 moveResize(newGeom); 3854 } 3855 3856 void Window::checkOffscreenPosition(QRectF *geom, const QRectF &screenArea) 3857 { 3858 if (geom->left() > screenArea.right()) { 3859 geom->moveLeft(screenArea.right() - screenArea.width() / 4); 3860 } else if (geom->right() < screenArea.left()) { 3861 geom->moveRight(screenArea.left() + screenArea.width() / 4); 3862 } 3863 if (geom->top() > screenArea.bottom()) { 3864 geom->moveTop(screenArea.bottom() - screenArea.height() / 4); 3865 } else if (geom->bottom() < screenArea.top()) { 3866 geom->moveBottom(screenArea.top() + screenArea.width() / 4); 3867 } 3868 } 3869 3870 /** 3871 * Constrains the client size @p size according to a set of the window's size hints. 3872 * 3873 * Default implementation applies only minimum and maximum size constraints. 3874 */ 3875 QSizeF Window::constrainClientSize(const QSizeF &size, SizeMode mode) const 3876 { 3877 qreal width = size.width(); 3878 qreal height = size.height(); 3879 3880 // When user is resizing the window, the move resize geometry may have negative width or 3881 // height. In which case, we need to set negative dimensions to reasonable values. 3882 if (width < 1) { 3883 width = 1; 3884 } 3885 if (height < 1) { 3886 height = 1; 3887 } 3888 3889 const QSizeF minimumSize = minSize(); 3890 const QSizeF maximumSize = maxSize(); 3891 3892 width = std::clamp(width, minimumSize.width(), maximumSize.width()); 3893 height = std::clamp(height, minimumSize.height(), maximumSize.height()); 3894 3895 return QSizeF(width, height); 3896 } 3897 3898 /** 3899 * Constrains the frame size @p size according to a set of the window's size hints. 3900 */ 3901 QSizeF Window::constrainFrameSize(const QSizeF &size, SizeMode mode) const 3902 { 3903 const QSizeF unconstrainedClientSize = frameSizeToClientSize(size); 3904 const QSizeF constrainedClientSize = constrainClientSize(unconstrainedClientSize, mode); 3905 return clientSizeToFrameSize(constrainedClientSize); 3906 } 3907 3908 QRectF Window::fullscreenGeometryRestore() const 3909 { 3910 return m_fullscreenGeometryRestore; 3911 } 3912 3913 void Window::setFullscreenGeometryRestore(const QRectF &geom) 3914 { 3915 m_fullscreenGeometryRestore = geom; 3916 } 3917 3918 /** 3919 * Returns @c true if the Window can be shown in full screen mode; otherwise @c false. 3920 * 3921 * Default implementation returns @c false. 3922 */ 3923 bool Window::isFullScreenable() const 3924 { 3925 return false; 3926 } 3927 3928 /** 3929 * Returns @c true if the Window is currently being shown in full screen mode; otherwise @c false. 3930 * 3931 * A window in full screen mode occupies the entire screen with no window frame around it. 3932 * 3933 * Default implementation returns @c false. 3934 */ 3935 bool Window::isFullScreen() const 3936 { 3937 return false; 3938 } 3939 3940 bool Window::isRequestedFullScreen() const 3941 { 3942 return isFullScreen(); 3943 } 3944 3945 /** 3946 * Asks the Window to enter or leave full screen mode. 3947 * 3948 * Default implementation does nothing. 3949 * 3950 * @param set @c true if the Window has to be shown in full screen mode, otherwise @c false 3951 */ 3952 void Window::setFullScreen(bool set) 3953 { 3954 qCWarning(KWIN_CORE, "%s doesn't support setting fullscreen state", metaObject()->className()); 3955 } 3956 3957 /** 3958 * Returns @c true if the Window can be minimized; otherwise @c false. 3959 * 3960 * Default implementation returns @c false. 3961 */ 3962 bool Window::isMinimizable() const 3963 { 3964 return false; 3965 } 3966 3967 /** 3968 * Returns @c true if the Window can be maximized; otherwise @c false. 3969 * 3970 * Default implementation returns @c false. 3971 */ 3972 bool Window::isMaximizable() const 3973 { 3974 return false; 3975 } 3976 3977 /** 3978 * Returns the currently applied maximize mode. 3979 * 3980 * Default implementation returns MaximizeRestore. 3981 */ 3982 MaximizeMode Window::maximizeMode() const 3983 { 3984 return MaximizeRestore; 3985 } 3986 3987 /** 3988 * Returns the last requested maximize mode. 3989 * 3990 * On X11, this method always matches maximizeMode(). On Wayland, it is asynchronous. 3991 * 3992 * Default implementation matches maximizeMode(). 3993 */ 3994 MaximizeMode Window::requestedMaximizeMode() const 3995 { 3996 return maximizeMode(); 3997 } 3998 3999 /** 4000 * Returns the geometry of the Window before it was maximized or quick tiled. 4001 */ 4002 QRectF Window::geometryRestore() const 4003 { 4004 return m_maximizeGeometryRestore; 4005 } 4006 4007 /** 4008 * Sets the geometry of the Window before it was maximized or quick tiled to @p rect. 4009 */ 4010 void Window::setGeometryRestore(const QRectF &rect) 4011 { 4012 m_maximizeGeometryRestore = rect; 4013 } 4014 4015 void Window::invalidateDecoration() 4016 { 4017 } 4018 4019 bool Window::noBorder() const 4020 { 4021 return true; 4022 } 4023 4024 bool Window::userCanSetNoBorder() const 4025 { 4026 return false; 4027 } 4028 4029 void Window::setNoBorder(bool set) 4030 { 4031 qCWarning(KWIN_CORE, "%s doesn't support setting decorations", metaObject()->className()); 4032 } 4033 4034 void Window::checkNoBorder() 4035 { 4036 setNoBorder(false); 4037 } 4038 4039 void Window::showOnScreenEdge() 4040 { 4041 qCWarning(KWIN_CORE, "%s doesn't support screen edge activation", metaObject()->className()); 4042 } 4043 4044 bool Window::isPlaceable() const 4045 { 4046 return true; 4047 } 4048 4049 void Window::cleanTabBox() 4050 { 4051 #if KWIN_BUILD_TABBOX 4052 TabBox::TabBox *tabBox = workspace()->tabbox(); 4053 if (tabBox && tabBox->isDisplayed() && tabBox->currentClient() == this) { 4054 tabBox->nextPrev(true); 4055 } 4056 #endif 4057 } 4058 4059 bool Window::supportsWindowRules() const 4060 { 4061 return false; 4062 } 4063 4064 void Window::removeRule(Rules *rule) 4065 { 4066 m_rules.remove(rule); 4067 } 4068 4069 void Window::evaluateWindowRules() 4070 { 4071 setupWindowRules(); 4072 applyWindowRules(); 4073 } 4074 4075 void Window::setupWindowRules() 4076 { 4077 disconnect(this, &Window::captionNormalChanged, this, &Window::evaluateWindowRules); 4078 m_rules = workspace()->rulebook()->find(this); 4079 // check only after getting the rules, because there may be a rule forcing window type 4080 } 4081 4082 void Window::updateWindowRules(Rules::Types selection) 4083 { 4084 if (workspace()->rulebook()->areUpdatesDisabled()) { 4085 return; 4086 } 4087 m_rules.update(this, selection); 4088 } 4089 4090 void Window::finishWindowRules() 4091 { 4092 disconnect(this, &Window::captionNormalChanged, this, &Window::evaluateWindowRules); 4093 updateWindowRules(Rules::All); 4094 m_rules = WindowRules(); 4095 } 4096 4097 // Applies Force, ForceTemporarily and ApplyNow rules 4098 // Used e.g. after the rules have been modified using the kcm. 4099 void Window::applyWindowRules() 4100 { 4101 Q_ASSERT(!isDeleted()); 4102 // apply force rules 4103 // Placement - does need explicit update, just like some others below 4104 // Geometry : setGeometry() doesn't check rules 4105 auto client_rules = rules(); 4106 const QRectF oldGeometry = moveResizeGeometry(); 4107 const QRectF geometry = client_rules->checkGeometrySafe(oldGeometry); 4108 if (geometry != oldGeometry) { 4109 moveResize(geometry); 4110 } 4111 // MinSize, MaxSize handled by Geometry 4112 // IgnoreGeometry 4113 setDesktops(desktops()); 4114 workspace()->sendWindowToOutput(this, moveResizeOutput()); 4115 setOnActivities(activities()); 4116 // Type 4117 maximize(requestedMaximizeMode()); 4118 setMinimized(isMinimized()); 4119 setShade(shadeMode()); 4120 setOriginalSkipTaskbar(skipTaskbar()); 4121 setSkipPager(skipPager()); 4122 setSkipSwitcher(skipSwitcher()); 4123 setKeepAbove(keepAbove()); 4124 setKeepBelow(keepBelow()); 4125 setFullScreen(isRequestedFullScreen()); 4126 setNoBorder(noBorder()); 4127 updateColorScheme(); 4128 updateLayer(); 4129 // FSP 4130 // AcceptFocus : 4131 if (workspace()->mostRecentlyActivatedWindow() == this 4132 && !client_rules->checkAcceptFocus(true)) { 4133 workspace()->activateNextWindow(this); 4134 } 4135 // Autogrouping : Only checked on window manage 4136 // AutogroupInForeground : Only checked on window manage 4137 // AutogroupById : Only checked on window manage 4138 // StrictGeometry 4139 setShortcut(rules()->checkShortcut(shortcut().toString())); 4140 // see also X11Window::setActive() 4141 if (isActive()) { 4142 setOpacity(rules()->checkOpacityActive(qRound(opacity() * 100.0)) / 100.0); 4143 workspace()->disableGlobalShortcutsForClient(rules()->checkDisableGlobalShortcuts(false)); 4144 } else { 4145 setOpacity(rules()->checkOpacityInactive(qRound(opacity() * 100.0)) / 100.0); 4146 } 4147 setDesktopFileName(rules()->checkDesktopFile(desktopFileName())); 4148 } 4149 4150 void Window::setLastUsageSerial(quint32 serial) 4151 { 4152 if (m_lastUsageSerial < serial) { 4153 m_lastUsageSerial = serial; 4154 } 4155 } 4156 4157 quint32 Window::lastUsageSerial() const 4158 { 4159 return m_lastUsageSerial; 4160 } 4161 4162 uint32_t Window::interactiveMoveResizeCount() const 4163 { 4164 return m_interactiveMoveResize.counter; 4165 } 4166 4167 void Window::setLockScreenOverlay(bool allowed) 4168 { 4169 if (m_lockScreenOverlay == allowed) { 4170 return; 4171 } 4172 m_lockScreenOverlay = allowed; 4173 Q_EMIT lockScreenOverlayChanged(); 4174 } 4175 4176 bool Window::isLockScreenOverlay() const 4177 { 4178 return m_lockScreenOverlay; 4179 } 4180 4181 void Window::refOffscreenRendering() 4182 { 4183 if (m_offscreenRenderCount == 0) { 4184 m_offscreenFramecallbackTimer.start(1'000'000 / output()->refreshRate()); 4185 } 4186 m_offscreenRenderCount++; 4187 } 4188 4189 void Window::unrefOffscreenRendering() 4190 { 4191 Q_ASSERT(m_offscreenRenderCount); 4192 m_offscreenRenderCount--; 4193 if (m_offscreenRenderCount == 0) { 4194 m_offscreenFramecallbackTimer.stop(); 4195 } 4196 } 4197 4198 void Window::maybeSendFrameCallback() 4199 { 4200 if (m_surface && !m_windowItem->isVisible()) { 4201 const auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); 4202 m_surface->traverseTree([this, ×tamp](SurfaceInterface *surface) { 4203 surface->frameRendered(timestamp); 4204 const auto feedback = surface->takePresentationFeedback(nullptr); 4205 if (feedback) { 4206 feedback->presented(std::chrono::nanoseconds(1'000'000'000'000 / output()->refreshRate()), std::chrono::steady_clock::now().time_since_epoch(), PresentationMode::VSync); 4207 } 4208 }); 4209 // update refresh rate, it might have changed 4210 m_offscreenFramecallbackTimer.start(1'000'000 / output()->refreshRate()); 4211 } 4212 } 4213 4214 WindowOffscreenRenderRef::WindowOffscreenRenderRef(Window *window) 4215 : m_window(window) 4216 { 4217 window->refOffscreenRendering(); 4218 } 4219 4220 WindowOffscreenRenderRef::~WindowOffscreenRenderRef() 4221 { 4222 if (m_window) { 4223 m_window->unrefOffscreenRendering(); 4224 } 4225 } 4226 4227 bool Window::isShown() const 4228 { 4229 return !isDeleted() && !isHidden() && !isHiddenByShowDesktop() && !isMinimized(); 4230 } 4231 4232 bool Window::isHidden() const 4233 { 4234 return m_hidden; 4235 } 4236 4237 void Window::setHidden(bool hidden) 4238 { 4239 if (m_hidden == hidden) { 4240 return; 4241 } 4242 m_hidden = hidden; 4243 doSetHidden(); 4244 if (hidden) { 4245 workspace()->windowHidden(this); 4246 Q_EMIT windowHidden(this); 4247 } else { 4248 Q_EMIT windowShown(this); 4249 } 4250 } 4251 4252 bool Window::isHiddenByShowDesktop() const 4253 { 4254 return m_hiddenByShowDesktop; 4255 } 4256 4257 void Window::setHiddenByShowDesktop(bool hidden) 4258 { 4259 if (m_hiddenByShowDesktop != hidden) { 4260 m_hiddenByShowDesktop = hidden; 4261 doSetHiddenByShowDesktop(); 4262 Q_EMIT hiddenByShowDesktopChanged(); 4263 } 4264 } 4265 4266 bool Window::isSuspended() const 4267 { 4268 return m_suspended; 4269 } 4270 4271 void Window::setSuspended(bool suspended) 4272 { 4273 if (isDeleted()) { 4274 return; 4275 } 4276 if (m_suspended != suspended) { 4277 m_suspended = suspended; 4278 doSetSuspended(); 4279 } 4280 } 4281 4282 void Window::doSetSuspended() 4283 { 4284 } 4285 4286 } // namespace KWin 4287 4288 #include "moc_window.cpp"