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