File indexing completed on 2024-12-01 05:02:00
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org> 0006 SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 // own 0011 #include "x11window.h" 0012 // kwin 0013 #include "core/output.h" 0014 #if KWIN_BUILD_ACTIVITIES 0015 #include "activities.h" 0016 #endif 0017 #include "atoms.h" 0018 #include "client_machine.h" 0019 #include "compositor.h" 0020 #include "cursor.h" 0021 #include "decorations/decoratedclient.h" 0022 #include "decorations/decorationbridge.h" 0023 #include "effect/effecthandler.h" 0024 #include "focuschain.h" 0025 #include "group.h" 0026 #include "killprompt.h" 0027 #include "netinfo.h" 0028 #include "placement.h" 0029 #include "scene/surfaceitem_x11.h" 0030 #include "scene/windowitem.h" 0031 #include "screenedge.h" 0032 #include "shadow.h" 0033 #include "virtualdesktops.h" 0034 #include "wayland/surface.h" 0035 #include "wayland_server.h" 0036 #include "workspace.h" 0037 #include <KDecoration2/DecoratedClient> 0038 #include <KDecoration2/Decoration> 0039 // KDE 0040 #include <KLocalizedString> 0041 #include <KStartupInfo> 0042 #include <KX11Extras> 0043 // Qt 0044 #include <QApplication> 0045 #include <QDebug> 0046 #include <QDir> 0047 #include <QFile> 0048 #include <QFileInfo> 0049 #include <QMouseEvent> 0050 #include <QPainter> 0051 #include <QProcess> 0052 // xcb 0053 #include <xcb/xcb_icccm.h> 0054 // system 0055 #include <unistd.h> 0056 // c++ 0057 #include <cmath> 0058 #include <csignal> 0059 0060 // Put all externs before the namespace statement to allow the linker 0061 // to resolve them properly 0062 0063 namespace KWin 0064 { 0065 0066 static uint32_t frameEventMask() 0067 { 0068 if (waylandServer()) { 0069 return XCB_EVENT_MASK_FOCUS_CHANGE 0070 | XCB_EVENT_MASK_STRUCTURE_NOTIFY 0071 | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT 0072 | XCB_EVENT_MASK_PROPERTY_CHANGE; 0073 } else { 0074 return XCB_EVENT_MASK_FOCUS_CHANGE 0075 | XCB_EVENT_MASK_STRUCTURE_NOTIFY 0076 | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT 0077 | XCB_EVENT_MASK_PROPERTY_CHANGE 0078 | XCB_EVENT_MASK_KEY_PRESS 0079 | XCB_EVENT_MASK_KEY_RELEASE 0080 | XCB_EVENT_MASK_ENTER_WINDOW 0081 | XCB_EVENT_MASK_LEAVE_WINDOW 0082 | XCB_EVENT_MASK_BUTTON_PRESS 0083 | XCB_EVENT_MASK_BUTTON_RELEASE 0084 | XCB_EVENT_MASK_BUTTON_MOTION 0085 | XCB_EVENT_MASK_POINTER_MOTION 0086 | XCB_EVENT_MASK_KEYMAP_STATE 0087 | XCB_EVENT_MASK_EXPOSURE 0088 | XCB_EVENT_MASK_VISIBILITY_CHANGE; 0089 } 0090 } 0091 0092 static uint32_t wrapperEventMask() 0093 { 0094 if (waylandServer()) { 0095 return XCB_EVENT_MASK_FOCUS_CHANGE 0096 | XCB_EVENT_MASK_STRUCTURE_NOTIFY 0097 | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT 0098 | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; 0099 } else { 0100 return XCB_EVENT_MASK_FOCUS_CHANGE 0101 | XCB_EVENT_MASK_STRUCTURE_NOTIFY 0102 | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT 0103 | XCB_EVENT_MASK_KEY_PRESS 0104 | XCB_EVENT_MASK_KEY_RELEASE 0105 | XCB_EVENT_MASK_ENTER_WINDOW 0106 | XCB_EVENT_MASK_LEAVE_WINDOW 0107 | XCB_EVENT_MASK_BUTTON_PRESS 0108 | XCB_EVENT_MASK_BUTTON_RELEASE 0109 | XCB_EVENT_MASK_BUTTON_MOTION 0110 | XCB_EVENT_MASK_POINTER_MOTION 0111 | XCB_EVENT_MASK_KEYMAP_STATE 0112 | XCB_EVENT_MASK_EXPOSURE 0113 | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; 0114 } 0115 } 0116 0117 static uint32_t clientEventMask() 0118 { 0119 if (waylandServer()) { 0120 return XCB_EVENT_MASK_FOCUS_CHANGE 0121 | XCB_EVENT_MASK_PROPERTY_CHANGE; 0122 } else { 0123 return XCB_EVENT_MASK_FOCUS_CHANGE 0124 | XCB_EVENT_MASK_PROPERTY_CHANGE 0125 | XCB_EVENT_MASK_ENTER_WINDOW 0126 | XCB_EVENT_MASK_LEAVE_WINDOW 0127 | XCB_EVENT_MASK_KEY_PRESS 0128 | XCB_EVENT_MASK_KEY_RELEASE; 0129 } 0130 } 0131 0132 // window types that are supported as normal windows (i.e. KWin actually manages them) 0133 const NET::WindowTypes SUPPORTED_MANAGED_WINDOW_TYPES_MASK = NET::NormalMask 0134 | NET::DesktopMask 0135 | NET::DockMask 0136 | NET::ToolbarMask 0137 | NET::MenuMask 0138 | NET::DialogMask 0139 /*| NET::OverrideMask*/ 0140 | NET::TopMenuMask 0141 | NET::UtilityMask 0142 | NET::SplashMask 0143 | NET::NotificationMask 0144 | NET::OnScreenDisplayMask 0145 | NET::CriticalNotificationMask 0146 | NET::AppletPopupMask; 0147 0148 // window types that are supported as unmanaged (mainly for compositing) 0149 const NET::WindowTypes SUPPORTED_UNMANAGED_WINDOW_TYPES_MASK = NET::NormalMask 0150 | NET::DesktopMask 0151 | NET::DockMask 0152 | NET::ToolbarMask 0153 | NET::MenuMask 0154 | NET::DialogMask 0155 /*| NET::OverrideMask*/ 0156 | NET::TopMenuMask 0157 | NET::UtilityMask 0158 | NET::SplashMask 0159 | NET::DropdownMenuMask 0160 | NET::PopupMenuMask 0161 | NET::TooltipMask 0162 | NET::NotificationMask 0163 | NET::ComboBoxMask 0164 | NET::DNDIconMask 0165 | NET::OnScreenDisplayMask 0166 | NET::CriticalNotificationMask; 0167 0168 X11DecorationRenderer::X11DecorationRenderer(Decoration::DecoratedClientImpl *client) 0169 : DecorationRenderer(client) 0170 , m_scheduleTimer(new QTimer(this)) 0171 , m_gc(XCB_NONE) 0172 { 0173 // Delay any rendering to end of event cycle to catch multiple updates per cycle. 0174 m_scheduleTimer->setSingleShot(true); 0175 m_scheduleTimer->setInterval(0); 0176 connect(m_scheduleTimer, &QTimer::timeout, this, &X11DecorationRenderer::update); 0177 connect(this, &X11DecorationRenderer::damaged, m_scheduleTimer, static_cast<void (QTimer::*)()>(&QTimer::start)); 0178 } 0179 0180 X11DecorationRenderer::~X11DecorationRenderer() 0181 { 0182 if (m_gc != XCB_NONE) { 0183 xcb_free_gc(kwinApp()->x11Connection(), m_gc); 0184 } 0185 } 0186 0187 void X11DecorationRenderer::update() 0188 { 0189 if (!damage().isEmpty()) { 0190 render(damage()); 0191 resetDamage(); 0192 } 0193 } 0194 0195 void X11DecorationRenderer::render(const QRegion ®ion) 0196 { 0197 if (!client()) { 0198 return; 0199 } 0200 xcb_connection_t *c = kwinApp()->x11Connection(); 0201 X11Window *window = static_cast<X11Window *>(client()->window()); 0202 0203 if (m_gc == XCB_NONE) { 0204 m_gc = xcb_generate_id(c); 0205 xcb_create_gc(c, m_gc, window->frameId(), 0, nullptr); 0206 } 0207 0208 QRectF left, top, right, bottom; 0209 window->layoutDecorationRects(left, top, right, bottom); 0210 0211 const QRect geometry = region.boundingRect(); 0212 left = left.intersected(geometry); 0213 top = top.intersected(geometry); 0214 right = right.intersected(geometry); 0215 bottom = bottom.intersected(geometry); 0216 0217 auto renderPart = [this, c, window](const QRect &geo) { 0218 if (!geo.isValid()) { 0219 return; 0220 } 0221 0222 // Guess the pixel format of the X pixmap into which the QImage will be copied. 0223 QImage::Format format; 0224 const int depth = window->depth(); 0225 switch (depth) { 0226 case 30: 0227 format = QImage::Format_A2RGB30_Premultiplied; 0228 break; 0229 case 24: 0230 case 32: 0231 format = QImage::Format_ARGB32_Premultiplied; 0232 break; 0233 default: 0234 qCCritical(KWIN_CORE) << "Unsupported client depth" << depth; 0235 format = QImage::Format_ARGB32_Premultiplied; 0236 break; 0237 }; 0238 0239 QImage image(geo.width(), geo.height(), format); 0240 image.fill(Qt::transparent); 0241 QPainter p(&image); 0242 p.setRenderHint(QPainter::Antialiasing); 0243 p.setWindow(geo); 0244 p.setClipRect(geo); 0245 renderToPainter(&p, geo); 0246 0247 xcb_put_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, window->frameId(), m_gc, 0248 image.width(), image.height(), geo.x(), geo.y(), 0, window->depth(), 0249 image.sizeInBytes(), image.constBits()); 0250 }; 0251 renderPart(left.toRect()); 0252 renderPart(top.toRect()); 0253 renderPart(right.toRect()); 0254 renderPart(bottom.toRect()); 0255 0256 xcb_flush(c); 0257 resetImageSizesDirty(); 0258 } 0259 0260 // Creating a client: 0261 // - only by calling Workspace::createClient() 0262 // - it creates a new client and calls manage() for it 0263 // 0264 // Destroying a client: 0265 // - destroyWindow() - only when the window itself has been destroyed 0266 // - releaseWindow() - the window is kept, only the client itself is destroyed 0267 0268 /** 0269 * \class Client x11client.h 0270 * \brief The Client class encapsulates a window decoration frame. 0271 */ 0272 0273 /** 0274 * This ctor is "dumb" - it only initializes data. All the real initialization 0275 * is done in manage(). 0276 */ 0277 X11Window::X11Window() 0278 : Window() 0279 , m_client() 0280 , m_wrapper() 0281 , m_frame() 0282 , m_activityUpdatesBlocked(false) 0283 , m_blockedActivityUpdatesRequireTransients(false) 0284 , m_moveResizeGrabWindow() 0285 , move_resize_has_keyboard_grab(false) 0286 , m_managed(false) 0287 , m_transientForId(XCB_WINDOW_NONE) 0288 , m_originalTransientForId(XCB_WINDOW_NONE) 0289 , shade_below(nullptr) 0290 , m_motif(atoms->motif_wm_hints) 0291 , blocks_compositing(false) 0292 , in_group(nullptr) 0293 , ping_timer(nullptr) 0294 , m_pingTimestamp(XCB_TIME_CURRENT_TIME) 0295 , m_userTime(XCB_TIME_CURRENT_TIME) // Not known yet 0296 , allowed_actions() 0297 , shade_geometry_change(false) 0298 , sm_stacking_order(-1) 0299 , activitiesDefined(false) 0300 , sessionActivityOverride(false) 0301 , m_decoInputExtent() 0302 , m_focusOutTimer(nullptr) 0303 { 0304 // TODO: Do all as initialization 0305 m_syncRequest.counter = m_syncRequest.alarm = XCB_NONE; 0306 m_syncRequest.timeout = m_syncRequest.failsafeTimeout = nullptr; 0307 m_syncRequest.lastTimestamp = xTime(); 0308 m_syncRequest.isPending = false; 0309 m_syncRequest.interactiveResize = false; 0310 0311 // Set the initial mapping state 0312 mapping_state = Withdrawn; 0313 0314 info = nullptr; 0315 0316 m_fullscreenMode = FullScreenNone; 0317 noborder = false; 0318 app_noborder = false; 0319 ignore_focus_stealing = false; 0320 check_active_modal = false; 0321 0322 max_mode = MaximizeRestore; 0323 0324 connect(clientMachine(), &ClientMachine::localhostChanged, this, &X11Window::updateCaption); 0325 connect(options, &Options::configChanged, this, &X11Window::updateMouseGrab); 0326 connect(options, &Options::condensedTitleChanged, this, &X11Window::updateCaption); 0327 connect(this, &X11Window::shapeChanged, this, &X11Window::discardShapeRegion); 0328 0329 if (kwinApp()->operationMode() == Application::OperationModeX11) { 0330 connect(this, &X11Window::moveResizeCursorChanged, this, [this](CursorShape cursor) { 0331 xcb_cursor_t nativeCursor = Cursors::self()->mouse()->x11Cursor(cursor); 0332 m_frame.defineCursor(nativeCursor); 0333 if (m_decoInputExtent.isValid()) { 0334 m_decoInputExtent.defineCursor(nativeCursor); 0335 } 0336 if (isInteractiveMoveResize()) { 0337 // changing window attributes doesn't change cursor if there's pointer grab active 0338 xcb_change_active_pointer_grab(kwinApp()->x11Connection(), nativeCursor, xTime(), 0339 XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW); 0340 } 0341 }); 0342 } 0343 0344 m_releaseTimer.setSingleShot(true); 0345 connect(&m_releaseTimer, &QTimer::timeout, this, [this]() { 0346 releaseWindow(); 0347 }); 0348 0349 // SELI TODO: Initialize xsizehints?? 0350 } 0351 0352 /** 0353 * "Dumb" destructor. 0354 */ 0355 X11Window::~X11Window() 0356 { 0357 delete info; 0358 0359 if (m_killPrompt) { 0360 m_killPrompt->quit(); 0361 } 0362 0363 Q_ASSERT(!isInteractiveMoveResize()); 0364 Q_ASSERT(!check_active_modal); 0365 } 0366 0367 std::unique_ptr<WindowItem> X11Window::createItem(Scene *scene) 0368 { 0369 return std::make_unique<WindowItemX11>(this, scene); 0370 } 0371 0372 // Use destroyWindow() or releaseWindow(), Client instances cannot be deleted directly 0373 void X11Window::deleteClient(X11Window *c) 0374 { 0375 delete c; 0376 } 0377 0378 bool X11Window::hasScheduledRelease() const 0379 { 0380 return m_releaseTimer.isActive(); 0381 } 0382 0383 /** 0384 * Releases the window. The client has done its job and the window is still existing. 0385 */ 0386 void X11Window::releaseWindow(bool on_shutdown) 0387 { 0388 destroyWindowManagementInterface(); 0389 if (SurfaceItemX11 *item = qobject_cast<SurfaceItemX11 *>(surfaceItem())) { 0390 item->destroyDamage(); 0391 } 0392 0393 markAsDeleted(); 0394 Q_EMIT closed(); 0395 0396 if (isUnmanaged()) { 0397 m_releaseTimer.stop(); 0398 if (!findInternalWindow()) { // don't affect our own windows 0399 if (Xcb::Extensions::self()->isShapeAvailable()) { 0400 xcb_shape_select_input(kwinApp()->x11Connection(), window(), false); 0401 } 0402 Xcb::selectInput(window(), XCB_EVENT_MASK_NO_EVENT); 0403 } 0404 workspace()->removeUnmanaged(this); 0405 } else { 0406 cleanTabBox(); 0407 if (isInteractiveMoveResize()) { 0408 Q_EMIT interactiveMoveResizeFinished(); 0409 } 0410 workspace()->rulebook()->discardUsed(this, true); // Remove ForceTemporarily rules 0411 StackingUpdatesBlocker blocker(workspace()); 0412 if (isInteractiveMoveResize()) { 0413 leaveInteractiveMoveResize(); 0414 } 0415 finishWindowRules(); 0416 blockGeometryUpdates(); 0417 // Grab X during the release to make removing of properties, setting to withdrawn state 0418 // and repareting to root an atomic operation (https://lists.kde.org/?l=kde-devel&m=116448102901184&w=2) 0419 grabXServer(); 0420 exportMappingState(XCB_ICCCM_WM_STATE_WITHDRAWN); 0421 setModal(false); // Otherwise its mainwindow wouldn't get focus 0422 if (!on_shutdown) { 0423 workspace()->windowHidden(this); 0424 } 0425 m_frame.unmap(); // Destroying decoration would cause ugly visual effect 0426 cleanGrouping(); 0427 workspace()->removeX11Window(this); 0428 if (!on_shutdown) { 0429 // Only when the window is being unmapped, not when closing down KWin (NETWM sections 5.5,5.7) 0430 info->setDesktop(0); 0431 info->setState(NET::States(), info->state()); // Reset all state flags 0432 } 0433 if (WinInfo *cinfo = dynamic_cast<WinInfo *>(info)) { 0434 cinfo->disable(); 0435 } 0436 xcb_connection_t *c = kwinApp()->x11Connection(); 0437 m_client.deleteProperty(atoms->kde_net_wm_user_creation_time); 0438 m_client.deleteProperty(atoms->net_frame_extents); 0439 m_client.deleteProperty(atoms->kde_net_wm_frame_strut); 0440 const QPointF grav = calculateGravitation(true); 0441 m_client.reparent(kwinApp()->x11RootWindow(), grav.x(), grav.y()); 0442 xcb_change_save_set(c, XCB_SET_MODE_DELETE, m_client); 0443 m_client.selectInput(XCB_EVENT_MASK_NO_EVENT); 0444 if (on_shutdown) { 0445 // Map the window, so it can be found after another WM is started 0446 m_client.map(); 0447 // TODO: Preserve minimized, shaded etc. state? 0448 } else { // Make sure it's not mapped if the app unmapped it (#65279). The app 0449 // may do map+unmap before we initially map the window by calling rawShow() from manage(). 0450 m_client.unmap(); 0451 } 0452 m_client.reset(); 0453 m_wrapper.reset(); 0454 m_frame.reset(); 0455 unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry 0456 ungrabXServer(); 0457 } 0458 if (m_syncRequest.alarm != XCB_NONE) { 0459 xcb_sync_destroy_alarm(kwinApp()->x11Connection(), m_syncRequest.alarm); 0460 m_syncRequest.alarm = XCB_NONE; 0461 } 0462 unblockCompositing(); 0463 unref(); 0464 } 0465 0466 /** 0467 * Like releaseWindow(), but this one is called when the window has been already destroyed 0468 * (E.g. The application closed it) 0469 */ 0470 void X11Window::destroyWindow() 0471 { 0472 destroyWindowManagementInterface(); 0473 if (SurfaceItemX11 *item = qobject_cast<SurfaceItemX11 *>(surfaceItem())) { 0474 item->forgetDamage(); 0475 } 0476 0477 markAsDeleted(); 0478 Q_EMIT closed(); 0479 0480 if (isUnmanaged()) { 0481 m_releaseTimer.stop(); 0482 workspace()->removeUnmanaged(this); 0483 } else { 0484 cleanTabBox(); 0485 if (isInteractiveMoveResize()) { 0486 Q_EMIT interactiveMoveResizeFinished(); 0487 } 0488 workspace()->rulebook()->discardUsed(this, true); // Remove ForceTemporarily rules 0489 StackingUpdatesBlocker blocker(workspace()); 0490 if (isInteractiveMoveResize()) { 0491 leaveInteractiveMoveResize(); 0492 } 0493 finishWindowRules(); 0494 blockGeometryUpdates(); 0495 setModal(false); 0496 workspace()->windowHidden(this); 0497 cleanGrouping(); 0498 workspace()->removeX11Window(this); 0499 if (WinInfo *cinfo = dynamic_cast<WinInfo *>(info)) { 0500 cinfo->disable(); 0501 } 0502 m_client.reset(); // invalidate 0503 m_wrapper.reset(); 0504 m_frame.reset(); 0505 unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry 0506 } 0507 if (m_syncRequest.alarm != XCB_NONE) { 0508 xcb_sync_destroy_alarm(kwinApp()->x11Connection(), m_syncRequest.alarm); 0509 m_syncRequest.alarm = XCB_NONE; 0510 } 0511 0512 unblockCompositing(); 0513 unref(); 0514 } 0515 0516 bool X11Window::track(xcb_window_t w) 0517 { 0518 XServerGrabber xserverGrabber; 0519 Xcb::WindowAttributes attr(w); 0520 Xcb::WindowGeometry geo(w); 0521 if (attr.isNull() || attr->map_state != XCB_MAP_STATE_VIEWABLE) { 0522 return false; 0523 } 0524 if (attr->_class == XCB_WINDOW_CLASS_INPUT_ONLY) { 0525 return false; 0526 } 0527 if (geo.isNull()) { 0528 return false; 0529 } 0530 0531 m_unmanaged = true; 0532 0533 m_frame.reset(w, false); 0534 m_wrapper.reset(w, false); 0535 m_client.reset(w, false); 0536 0537 Xcb::selectInput(w, attr->your_event_mask | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE); 0538 m_bufferGeometry = geo.rect(); 0539 m_frameGeometry = geo.rect(); 0540 m_clientGeometry = geo.rect(); 0541 checkOutput(); 0542 m_visual = attr->visual; 0543 bit_depth = geo->depth; 0544 info = new NETWinInfo(kwinApp()->x11Connection(), w, kwinApp()->x11RootWindow(), 0545 NET::WMWindowType | NET::WMPid, 0546 NET::WM2Opacity | NET::WM2WindowRole | NET::WM2WindowClass | NET::WM2OpaqueRegion); 0547 setOpacity(info->opacityF()); 0548 getResourceClass(); 0549 getWmClientLeader(); 0550 getWmClientMachine(); 0551 if (Xcb::Extensions::self()->isShapeAvailable()) { 0552 xcb_shape_select_input(kwinApp()->x11Connection(), w, true); 0553 } 0554 detectShape(); 0555 getWmOpaqueRegion(); 0556 getSkipCloseAnimation(); 0557 updateShadow(); 0558 setupCompositing(); 0559 if (QWindow *internalWindow = findInternalWindow()) { 0560 m_outline = internalWindow->property("__kwin_outline").toBool(); 0561 } 0562 if (effects) { 0563 effects->checkInputWindowStacking(); 0564 } 0565 0566 switch (kwinApp()->operationMode()) { 0567 case Application::OperationModeXwayland: 0568 // The wayland surface is associated with the override-redirect window asynchronously. 0569 if (surface()) { 0570 associate(); 0571 } else { 0572 connect(this, &Window::surfaceChanged, this, &X11Window::associate); 0573 } 0574 break; 0575 case Application::OperationModeX11: 0576 // We have no way knowing whether the override-redirect window can be painted. Mark it 0577 // as ready for painting after synthetic 50ms delay. 0578 QTimer::singleShot(50, this, &X11Window::setReadyForPainting); 0579 break; 0580 case Application::OperationModeWaylandOnly: 0581 Q_UNREACHABLE(); 0582 } 0583 0584 return true; 0585 } 0586 0587 /** 0588 * Manages the clients. This means handling the very first maprequest: 0589 * reparenting, initial geometry, initial state, placement, etc. 0590 * Returns false if KWin is not going to manage this window. 0591 */ 0592 bool X11Window::manage(xcb_window_t w, bool isMapped) 0593 { 0594 StackingUpdatesBlocker stacking_blocker(workspace()); 0595 0596 Xcb::WindowAttributes attr(w); 0597 Xcb::WindowGeometry windowGeometry(w); 0598 if (attr.isNull() || windowGeometry.isNull()) { 0599 return false; 0600 } 0601 0602 // From this place on, manage() must not return false 0603 blockGeometryUpdates(); 0604 0605 embedClient(w, attr->visual, attr->colormap, windowGeometry->depth); 0606 0607 m_visual = attr->visual; 0608 bit_depth = windowGeometry->depth; 0609 0610 // SELI TODO: Order all these things in some sane manner 0611 0612 const NET::Properties properties = 0613 NET::WMDesktop | NET::WMState | NET::WMWindowType | NET::WMStrut | NET::WMName | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMIconName; 0614 const NET::Properties2 properties2 = 0615 NET::WM2BlockCompositing | NET::WM2WindowClass | NET::WM2WindowRole | NET::WM2UserTime | NET::WM2StartupId | NET::WM2ExtendedStrut | NET::WM2Opacity | NET::WM2FullscreenMonitors | NET::WM2GroupLeader | NET::WM2Urgency | NET::WM2Input | NET::WM2Protocols | NET::WM2InitialMappingState | NET::WM2IconPixmap | NET::WM2OpaqueRegion | NET::WM2DesktopFileName | NET::WM2GTKFrameExtents | NET::WM2GTKApplicationId; 0616 0617 auto wmClientLeaderCookie = fetchWmClientLeader(); 0618 auto skipCloseAnimationCookie = fetchSkipCloseAnimation(); 0619 auto showOnScreenEdgeCookie = fetchShowOnScreenEdge(); 0620 auto colorSchemeCookie = fetchPreferredColorScheme(); 0621 auto transientCookie = fetchTransient(); 0622 auto activitiesCookie = fetchActivities(); 0623 auto applicationMenuServiceNameCookie = fetchApplicationMenuServiceName(); 0624 auto applicationMenuObjectPathCookie = fetchApplicationMenuObjectPath(); 0625 0626 m_geometryHints.init(window()); 0627 m_motif.init(window()); 0628 info = new WinInfo(this, m_client, kwinApp()->x11RootWindow(), properties, properties2); 0629 0630 if (isDesktop() && bit_depth == 32) { 0631 // force desktop windows to be opaque. It's a desktop after all, there is no window below 0632 bit_depth = 24; 0633 } 0634 0635 // If it's already mapped, ignore hint 0636 bool init_minimize = !isMapped && (info->initialMappingState() == NET::Iconic); 0637 0638 getResourceClass(); 0639 readWmClientLeader(wmClientLeaderCookie); 0640 getWmClientMachine(); 0641 getSyncCounter(); 0642 setCaption(readName()); 0643 0644 setupWindowRules(); 0645 connect(this, &X11Window::windowClassChanged, this, &X11Window::evaluateWindowRules); 0646 0647 if (Xcb::Extensions::self()->isShapeAvailable()) { 0648 xcb_shape_select_input(kwinApp()->x11Connection(), window(), true); 0649 } 0650 detectShape(); 0651 detectNoBorder(); 0652 fetchIconicName(); 0653 setClientFrameExtents(info->gtkFrameExtents()); 0654 0655 // Needs to be done before readTransient() because of reading the group 0656 checkGroup(); 0657 updateUrgency(); 0658 updateAllowedActions(); // Group affects isMinimizable() 0659 0660 setModal((info->state() & NET::Modal) != 0); // Needs to be valid before handling groups 0661 readTransientProperty(transientCookie); 0662 QString desktopFileName = QString::fromUtf8(info->desktopFileName()); 0663 if (desktopFileName.isEmpty()) { 0664 desktopFileName = QString::fromUtf8(info->gtkApplicationId()); 0665 } 0666 setDesktopFileName(rules()->checkDesktopFile(desktopFileName, true)); 0667 getIcons(); 0668 connect(this, &X11Window::desktopFileNameChanged, this, &X11Window::getIcons); 0669 0670 m_geometryHints.read(); 0671 getMotifHints(); 0672 getWmOpaqueRegion(); 0673 readSkipCloseAnimation(skipCloseAnimationCookie); 0674 updateShadow(); 0675 0676 // TODO: Try to obey all state information from info->state() 0677 0678 setOriginalSkipTaskbar((info->state() & NET::SkipTaskbar) != 0); 0679 setSkipPager((info->state() & NET::SkipPager) != 0); 0680 setSkipSwitcher((info->state() & NET::SkipSwitcher) != 0); 0681 0682 setupCompositing(); 0683 0684 KStartupInfoId asn_id; 0685 KStartupInfoData asn_data; 0686 bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data); 0687 0688 // Make sure that the input window is created before we update the stacking order 0689 updateInputWindow(); 0690 updateLayer(); 0691 0692 SessionInfo *session = workspace()->sessionManager()->takeSessionInfo(this); 0693 if (session) { 0694 init_minimize = session->minimized; 0695 noborder = session->noBorder; 0696 } 0697 0698 setShortcut(rules()->checkShortcut(session ? session->shortcut : QString(), true)); 0699 0700 init_minimize = rules()->checkMinimize(init_minimize, !isMapped); 0701 noborder = rules()->checkNoBorder(noborder, !isMapped); 0702 0703 readActivities(activitiesCookie); 0704 0705 // Initial desktop placement 0706 std::optional<QList<VirtualDesktop *>> initialDesktops; 0707 if (session) { 0708 if (session->onAllDesktops) { 0709 initialDesktops = QList<VirtualDesktop *>{}; 0710 } else { 0711 VirtualDesktop *desktop = VirtualDesktopManager::self()->desktopForX11Id(session->desktop); 0712 if (desktop) { 0713 initialDesktops = QList<VirtualDesktop *>{desktop}; 0714 } 0715 } 0716 setOnActivities(session->activities); 0717 } else { 0718 // If this window is transient, ensure that it is opened on the 0719 // same window as its parent. this is necessary when an application 0720 // starts up on a different desktop than is currently displayed 0721 if (isTransient()) { 0722 auto mainwindows = mainWindows(); 0723 bool on_current = false; 0724 bool on_all = false; 0725 Window *maincl = nullptr; 0726 // This is slightly duplicated from Placement::placeOnMainWindow() 0727 for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) { 0728 if (mainwindows.count() > 1 && // A group-transient 0729 (*it)->isSpecialWindow() && // Don't consider toolbars etc when placing 0730 !(info->state() & NET::Modal)) { // except when it's modal (blocks specials as well) 0731 continue; 0732 } 0733 maincl = *it; 0734 if ((*it)->isOnCurrentDesktop()) { 0735 on_current = true; 0736 } 0737 if ((*it)->isOnAllDesktops()) { 0738 on_all = true; 0739 } 0740 } 0741 if (on_all) { 0742 initialDesktops = QList<VirtualDesktop *>{}; 0743 } else if (on_current) { 0744 initialDesktops = QList<VirtualDesktop *>{VirtualDesktopManager::self()->currentDesktop()}; 0745 } else if (maincl) { 0746 initialDesktops = maincl->desktops(); 0747 } 0748 0749 if (maincl) { 0750 setOnActivities(maincl->activities()); 0751 } 0752 } else { // a transient shall appear on its leader and not drag that around 0753 int desktopId = 0; 0754 if (info->desktop()) { 0755 desktopId = info->desktop(); // Window had the initial desktop property, force it 0756 } 0757 if (desktopId == 0 && asn_valid && asn_data.desktop() != 0) { 0758 desktopId = asn_data.desktop(); 0759 } 0760 if (desktopId) { 0761 if (desktopId == NET::OnAllDesktops) { 0762 initialDesktops = QList<VirtualDesktop *>{}; 0763 } else { 0764 VirtualDesktop *desktop = VirtualDesktopManager::self()->desktopForX11Id(desktopId); 0765 if (desktop) { 0766 initialDesktops = QList<VirtualDesktop *>{desktop}; 0767 } 0768 } 0769 } 0770 } 0771 #if KWIN_BUILD_ACTIVITIES 0772 if (Workspace::self()->activities() && !isMapped && !skipTaskbar() && isNormalWindow() && !activitiesDefined) { 0773 // a new, regular window, when we're not recovering from a crash, 0774 // and it hasn't got an activity. let's try giving it the current one. 0775 // TODO: decide whether to keep this before the 4.6 release 0776 // TODO: if we are keeping it (at least as an option), replace noborder checking 0777 // with a public API for setting windows to be on all activities. 0778 // something like KWindowSystem::setOnAllActivities or 0779 // KActivityConsumer::setOnAllActivities 0780 setOnActivity(Workspace::self()->activities()->current(), true); 0781 } 0782 #endif 0783 } 0784 0785 // If initialDesktops has no value, it means that the client doesn't prefer any 0786 // desktop so place it on the current virtual desktop. 0787 if (!initialDesktops.has_value()) { 0788 if (isDesktop()) { 0789 initialDesktops = QList<VirtualDesktop *>{}; 0790 } else { 0791 initialDesktops = QList<VirtualDesktop *>{VirtualDesktopManager::self()->currentDesktop()}; 0792 } 0793 } 0794 setDesktops(rules()->checkDesktops(*initialDesktops, !isMapped)); 0795 info->setDesktop(desktopId()); 0796 workspace()->updateOnAllDesktopsOfTransients(this); // SELI TODO 0797 // onAllDesktopsChange(); // Decoration doesn't exist here yet 0798 0799 QStringList activitiesList; 0800 activitiesList = rules()->checkActivity(activitiesList, !isMapped); 0801 if (!activitiesList.isEmpty()) { 0802 setOnActivities(activitiesList); 0803 } 0804 0805 QRectF geom = session ? session->geometry : windowGeometry.rect(); 0806 bool placementDone = false; 0807 0808 QRectF area; 0809 bool partial_keep_in_area = isMapped || session; 0810 if (isMapped || session) { 0811 area = workspace()->clientArea(FullArea, this, geom.center()); 0812 checkOffscreenPosition(&geom, area); 0813 } else { 0814 Output *output = nullptr; 0815 if (asn_data.xinerama() != -1) { 0816 output = workspace()->xineramaIndexToOutput(asn_data.xinerama()); 0817 } 0818 if (!output) { 0819 output = workspace()->activeOutput(); 0820 } 0821 output = rules()->checkOutput(output, !isMapped); 0822 area = workspace()->clientArea(PlacementArea, this, output->geometry().center()); 0823 } 0824 0825 if (isDesktop()) { 0826 // KWin doesn't manage desktop windows 0827 placementDone = true; 0828 } 0829 0830 bool usePosition = false; 0831 if (isMapped || session || placementDone) { 0832 placementDone = true; // Use geometry 0833 } else if (isTransient() && !isUtility() && !isDialog() && !isSplash()) { 0834 usePosition = true; 0835 } else if (isTransient() && !hasNETSupport()) { 0836 usePosition = true; 0837 } else if (isDialog() && hasNETSupport()) { 0838 // If the dialog is actually non-NETWM transient window, don't try to apply placement to it, 0839 // it breaks with too many things (xmms, display) 0840 if (mainWindows().count() >= 1) { 0841 #if 1 0842 // #78082 - Ok, it seems there are after all some cases when an application has a good 0843 // reason to specify a position for its dialog. Too bad other WMs have never bothered 0844 // with placement for dialogs, so apps always specify positions for their dialogs, 0845 // including such silly positions like always centered on the screen or under mouse. 0846 // Using ignoring requested position in window-specific settings helps, and now 0847 // there's also _NET_WM_FULL_PLACEMENT. 0848 usePosition = true; 0849 #else 0850 ; // Force using placement policy 0851 #endif 0852 } else { 0853 usePosition = true; 0854 } 0855 } else if (isSplash()) { 0856 ; // Force using placement policy 0857 } else { 0858 usePosition = true; 0859 } 0860 if (!rules()->checkIgnoreGeometry(!usePosition, true)) { 0861 if (m_geometryHints.hasPosition()) { 0862 placementDone = true; 0863 // Disobey xinerama placement option for now (#70943) 0864 area = workspace()->clientArea(PlacementArea, this, geom.center()); 0865 } 0866 } 0867 0868 if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom())) { 0869 placementDone = false; // Weird, do not trust. 0870 } 0871 0872 if (placementDone) { 0873 QPointF position = geom.topLeft(); 0874 // Session contains the position of the frame geometry before gravitating. 0875 if (!session) { 0876 position = clientPosToFramePos(position); 0877 } 0878 move(position); 0879 } 0880 0881 // Create client group if the window will have a decoration 0882 bool dontKeepInArea = false; 0883 setColorScheme(readPreferredColorScheme(colorSchemeCookie)); 0884 0885 readApplicationMenuServiceName(applicationMenuServiceNameCookie); 0886 readApplicationMenuObjectPath(applicationMenuObjectPathCookie); 0887 0888 updateDecoration(false); // Also gravitates 0889 // TODO: Is CentralGravity right here, when resizing is done after gravitating? 0890 const QSizeF constrainedClientSize = constrainClientSize(geom.size()); 0891 resize(rules()->checkSize(clientSizeToFrameSize(constrainedClientSize), !isMapped)); 0892 0893 QPointF forced_pos = rules()->checkPositionSafe(invalidPoint, !isMapped); 0894 if (forced_pos != invalidPoint) { 0895 move(forced_pos); 0896 placementDone = true; 0897 // Don't keep inside workarea if the window has specially configured position 0898 partial_keep_in_area = true; 0899 area = workspace()->clientArea(FullArea, this, geom.center()); 0900 } 0901 if (!placementDone) { 0902 // Placement needs to be after setting size 0903 workspace()->placement()->place(this, area); 0904 // The client may have been moved to another screen, update placement area. 0905 area = workspace()->clientArea(PlacementArea, this, moveResizeOutput()); 0906 dontKeepInArea = true; 0907 placementDone = true; 0908 } 0909 0910 // bugs #285967, #286146, #183694 0911 // geometry() now includes the requested size and the decoration and is at the correct screen/position (hopefully) 0912 // Maximization for oversized windows must happen NOW. 0913 // If we effectively pass keepInArea(), the window will resizeWithChecks() - i.e. constrained 0914 // to the combo of all screen MINUS all struts on the edges 0915 // If only one screen struts, this will affect screens as a side-effect, the window is artificailly shrinked 0916 // below the screen size and as result no more maximized what breaks KMainWindow's stupid width+1, height+1 hack 0917 // TODO: get KMainWindow a correct state storage what will allow to store the restore size as well. 0918 0919 if (!session) { // has a better handling of this 0920 setGeometryRestore(moveResizeGeometry()); // Remember restore geometry 0921 if (isMaximizable() && (width() >= area.width() || height() >= area.height())) { 0922 // Window is too large for the screen, maximize in the 0923 // directions necessary 0924 const QSizeF ss = workspace()->clientArea(ScreenArea, this, area.center()).size(); 0925 const QRectF fsa = workspace()->clientArea(FullArea, this, geom.center()); 0926 const QSizeF cs = clientSize(); 0927 int pseudo_max = ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0); 0928 if (width() >= area.width()) { 0929 pseudo_max |= MaximizeHorizontal; 0930 } 0931 if (height() >= area.height()) { 0932 pseudo_max |= MaximizeVertical; 0933 } 0934 0935 // heuristics: 0936 // if decorated client is smaller than the entire screen, the user might want to move it around (multiscreen) 0937 // in this case, if the decorated client is bigger than the screen (+1), we don't take this as an 0938 // attempt for maximization, but just constrain the size (the window simply wants to be bigger) 0939 // NOTICE 0940 // i intended a second check on cs < area.size() ("the managed client ("minus border") is smaller 0941 // than the workspace") but gtk / gimp seems to store it's size including the decoration, 0942 // thus a former maximized window wil become non-maximized 0943 bool keepInFsArea = false; 0944 if (width() < fsa.width() && (cs.width() > ss.width() + 1)) { 0945 pseudo_max &= ~MaximizeHorizontal; 0946 keepInFsArea = true; 0947 } 0948 if (height() < fsa.height() && (cs.height() > ss.height() + 1)) { 0949 pseudo_max &= ~MaximizeVertical; 0950 keepInFsArea = true; 0951 } 0952 0953 if (pseudo_max != MaximizeRestore) { 0954 maximize((MaximizeMode)pseudo_max); 0955 // from now on, care about maxmode, since the maximization call will override mode for fix aspects 0956 dontKeepInArea |= (max_mode == MaximizeFull); 0957 QRectF savedGeometry; // Use placement when unmaximizing ... 0958 if (!(max_mode & MaximizeVertical)) { 0959 savedGeometry.setY(y()); // ...but only for horizontal direction 0960 savedGeometry.setHeight(height()); 0961 } 0962 if (!(max_mode & MaximizeHorizontal)) { 0963 savedGeometry.setX(x()); // ...but only for vertical direction 0964 savedGeometry.setWidth(width()); 0965 } 0966 setGeometryRestore(savedGeometry); 0967 } 0968 if (keepInFsArea) { 0969 keepInArea(fsa, partial_keep_in_area); 0970 } 0971 } 0972 } 0973 0974 if ((!isSpecialWindow() || isToolbar()) && isMovable() && !dontKeepInArea) { 0975 keepInArea(area, partial_keep_in_area); 0976 } 0977 0978 updateShape(); 0979 0980 // CT: Extra check for stupid jdk 1.3.1. But should make sense in general 0981 // if client has initial state set to Iconic and is transient with a parent 0982 // window that is not Iconic, set init_state to Normal 0983 if (init_minimize && isTransient()) { 0984 auto mainwindows = mainWindows(); 0985 for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) { 0986 if ((*it)->isShown()) { 0987 init_minimize = false; // SELI TODO: Even e.g. for NET::Utility? 0988 } 0989 } 0990 } 0991 // If a dialog is shown for minimized window, minimize it too 0992 if (!init_minimize && isTransient() && mainWindows().count() > 0 && workspace()->sessionManager()->state() != SessionState::Saving) { 0993 bool visible_parent = false; 0994 // Use allMainWindows(), to include also main clients of group transients 0995 // that have been optimized out in X11Window::checkGroupTransients() 0996 auto mainwindows = allMainWindows(); 0997 for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) { 0998 if ((*it)->isShown()) { 0999 visible_parent = true; 1000 } 1001 } 1002 if (!visible_parent) { 1003 init_minimize = true; 1004 demandAttention(); 1005 } 1006 } 1007 1008 setMinimized(init_minimize); 1009 1010 // Other settings from the previous session 1011 if (session) { 1012 // Session restored windows are not considered to be new windows WRT rules, 1013 // I.e. obey only forcing rules 1014 setKeepAbove(session->keepAbove); 1015 setKeepBelow(session->keepBelow); 1016 setOriginalSkipTaskbar(session->skipTaskbar); 1017 setSkipPager(session->skipPager); 1018 setSkipSwitcher(session->skipSwitcher); 1019 setShade(session->shaded ? ShadeNormal : ShadeNone); 1020 setOpacity(session->opacity); 1021 setGeometryRestore(session->restore); 1022 if (session->maximized != MaximizeRestore) { 1023 maximize(MaximizeMode(session->maximized)); 1024 } 1025 if (session->fullscreen != FullScreenNone) { 1026 setFullScreen(true); 1027 setFullscreenGeometryRestore(session->fsrestore); 1028 } 1029 QRectF checkedGeometryRestore = geometryRestore(); 1030 checkOffscreenPosition(&checkedGeometryRestore, area); 1031 setGeometryRestore(checkedGeometryRestore); 1032 QRectF checkedFullscreenGeometryRestore = fullscreenGeometryRestore(); 1033 checkOffscreenPosition(&checkedFullscreenGeometryRestore, area); 1034 setFullscreenGeometryRestore(checkedFullscreenGeometryRestore); 1035 } else { 1036 // Window may want to be maximized 1037 // done after checking that the window isn't larger than the workarea, so that 1038 // the restore geometry from the checks above takes precedence, and window 1039 // isn't restored larger than the workarea 1040 MaximizeMode maxmode = static_cast<MaximizeMode>( 1041 ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0)); 1042 MaximizeMode forced_maxmode = rules()->checkMaximize(maxmode, !isMapped); 1043 1044 // Either hints were set to maximize, or is forced to maximize, 1045 // or is forced to non-maximize and hints were set to maximize 1046 if (forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore) { 1047 maximize(forced_maxmode); 1048 } 1049 1050 // Read other initial states 1051 setShade(rules()->checkShade(info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped)); 1052 setKeepAbove(rules()->checkKeepAbove(info->state() & NET::KeepAbove, !isMapped)); 1053 setKeepBelow(rules()->checkKeepBelow(info->state() & NET::KeepBelow, !isMapped)); 1054 setOriginalSkipTaskbar(rules()->checkSkipTaskbar(info->state() & NET::SkipTaskbar, !isMapped)); 1055 setSkipPager(rules()->checkSkipPager(info->state() & NET::SkipPager, !isMapped)); 1056 setSkipSwitcher(rules()->checkSkipSwitcher(info->state() & NET::SkipSwitcher, !isMapped)); 1057 if (info->state() & NET::DemandsAttention) { 1058 demandAttention(); 1059 } 1060 if (info->state() & NET::Modal) { 1061 setModal(true); 1062 } 1063 setOpacity(info->opacityF()); 1064 1065 setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped)); 1066 } 1067 1068 updateAllowedActions(true); 1069 1070 // Set initial user time directly 1071 m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : nullptr, asn_valid ? &asn_data : nullptr, session); 1072 group()->updateUserTime(m_userTime); // And do what X11Window::updateUserTime() does 1073 1074 // This should avoid flicker, because real restacking is done 1075 // only after manage() finishes because of blocking, but the window is shown sooner 1076 m_frame.lower(); 1077 if (session && session->stackingOrder != -1) { 1078 sm_stacking_order = session->stackingOrder; 1079 workspace()->restoreSessionStackingOrder(this); 1080 } 1081 1082 if (Compositor::compositing()) { 1083 // Sending ConfigureNotify is done when setting mapping state below, 1084 // Getting the first sync response means window is ready for compositing 1085 sendSyncRequest(); 1086 } else { 1087 ready_for_painting = true; // set to true in case compositing is turned on later. bug #160393 1088 } 1089 1090 if (isShown()) { 1091 bool allow; 1092 if (session) { 1093 allow = session->active && (!workspace()->wasUserInteraction() || workspace()->activeWindow() == nullptr || workspace()->activeWindow()->isDesktop()); 1094 } else { 1095 allow = allowWindowActivation(userTime(), false); 1096 } 1097 1098 const bool isSessionSaving = workspace()->sessionManager()->state() == SessionState::Saving; 1099 1100 // If session saving, force showing new windows (i.e. "save file?" dialogs etc.) 1101 // also force if activation is allowed 1102 if (!isOnCurrentDesktop() && !isMapped && !session && (allow || isSessionSaving)) { 1103 VirtualDesktopManager::self()->setCurrent(desktopId()); 1104 } 1105 1106 // If the window is on an inactive activity during session saving, temporarily force it to show. 1107 if (!isMapped && !session && isSessionSaving && !isOnCurrentActivity()) { 1108 setSessionActivityOverride(true); 1109 const auto windows = mainWindows(); 1110 for (Window *w : windows) { 1111 if (X11Window *mw = dynamic_cast<X11Window *>(w)) { 1112 mw->setSessionActivityOverride(true); 1113 } 1114 } 1115 } 1116 1117 if (isOnCurrentDesktop() && !isMapped && !allow && (!session || session->stackingOrder < 0)) { 1118 workspace()->restackWindowUnderActive(this); 1119 } 1120 1121 updateVisibility(); 1122 1123 if (!isMapped) { 1124 if (allow && isOnCurrentDesktop()) { 1125 if (!isSpecialWindow()) { 1126 if (options->focusPolicyIsReasonable() && wantsTabFocus()) { 1127 workspace()->requestFocus(this); 1128 } 1129 } 1130 } else if (!session && !isSpecialWindow()) { 1131 demandAttention(); 1132 } 1133 } 1134 } else { 1135 updateVisibility(); 1136 } 1137 Q_ASSERT(mapping_state != Withdrawn); 1138 m_managed = true; 1139 blockGeometryUpdates(false); 1140 1141 if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) { 1142 // No known user time, set something old 1143 m_userTime = xTime() - 1000000; 1144 if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) { // Let's be paranoid 1145 m_userTime = xTime() - 1000000 + 10; 1146 } 1147 } 1148 1149 // sendSyntheticConfigureNotify(); // Done when setting mapping state 1150 1151 delete session; 1152 1153 applyWindowRules(); // Just in case 1154 workspace()->rulebook()->discardUsed(this, false); // Remove ApplyNow rules 1155 updateWindowRules(Rules::All); // Was blocked while !isManaged() 1156 1157 setBlockingCompositing(info->isBlockingCompositing()); 1158 readShowOnScreenEdge(showOnScreenEdgeCookie); 1159 1160 setupWindowManagementInterface(); 1161 1162 // Forward all opacity values to the frame in case there'll be other CM running. 1163 connect(Compositor::self(), &Compositor::compositingToggled, this, [this](bool active) { 1164 if (active) { 1165 return; 1166 } 1167 if (opacity() == 1.0) { 1168 return; 1169 } 1170 NETWinInfo info(kwinApp()->x11Connection(), frameId(), kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); 1171 info.setOpacityF(opacity()); 1172 }); 1173 1174 switch (kwinApp()->operationMode()) { 1175 case Application::OperationModeXwayland: 1176 // The wayland surface is associated with the window asynchronously. 1177 if (surface()) { 1178 associate(); 1179 } else { 1180 connect(this, &Window::surfaceChanged, this, &X11Window::associate); 1181 } 1182 connect(kwinApp(), &Application::xwaylandScaleChanged, this, &X11Window::handleXwaylandScaleChanged); 1183 break; 1184 case Application::OperationModeX11: 1185 break; 1186 case Application::OperationModeWaylandOnly: 1187 Q_UNREACHABLE(); 1188 } 1189 1190 return true; 1191 } 1192 1193 // Called only from manage() 1194 void X11Window::embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth) 1195 { 1196 Q_ASSERT(m_client == XCB_WINDOW_NONE); 1197 Q_ASSERT(frameId() == XCB_WINDOW_NONE); 1198 Q_ASSERT(m_wrapper == XCB_WINDOW_NONE); 1199 m_client.reset(w, false); 1200 1201 const uint32_t zero_value = 0; 1202 1203 xcb_connection_t *conn = kwinApp()->x11Connection(); 1204 1205 // We don't want the window to be destroyed when we quit 1206 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, m_client); 1207 1208 m_client.selectInput(zero_value); 1209 m_client.unmap(); 1210 m_client.setBorderWidth(zero_value); 1211 1212 // Note: These values must match the order in the xcb_cw_t enum 1213 const uint32_t cw_values[] = { 1214 0, // back_pixmap 1215 0, // border_pixel 1216 colormap, // colormap 1217 Cursors::self()->mouse()->x11Cursor(Qt::ArrowCursor)}; 1218 1219 const uint32_t cw_mask = XCB_CW_BACK_PIXMAP 1220 | XCB_CW_BORDER_PIXEL 1221 | XCB_CW_COLORMAP 1222 | XCB_CW_CURSOR; 1223 1224 // Create the frame window 1225 xcb_window_t frame = xcb_generate_id(conn); 1226 xcb_create_window(conn, depth, frame, kwinApp()->x11RootWindow(), 0, 0, 1, 1, 0, 1227 XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); 1228 m_frame.reset(frame); 1229 1230 // Create the wrapper window 1231 xcb_window_t wrapperId = xcb_generate_id(conn); 1232 xcb_create_window(conn, depth, wrapperId, frame, 0, 0, 1, 1, 0, 1233 XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); 1234 m_wrapper.reset(wrapperId); 1235 1236 m_client.reparent(m_wrapper); 1237 1238 // We could specify the event masks when we create the windows, but the original 1239 // Xlib code didn't. Let's preserve that behavior here for now so we don't end up 1240 // receiving any unexpected events from the wrapper creation or the reparenting. 1241 m_frame.selectInput(frameEventMask()); 1242 m_wrapper.selectInput(wrapperEventMask()); 1243 m_client.selectInput(clientEventMask()); 1244 1245 updateMouseGrab(); 1246 } 1247 1248 void X11Window::updateInputWindow() 1249 { 1250 if (!Xcb::Extensions::self()->isShapeInputAvailable()) { 1251 return; 1252 } 1253 1254 if (kwinApp()->operationMode() != Application::OperationModeX11) { 1255 return; 1256 } 1257 1258 QRegion region; 1259 1260 if (decoration()) { 1261 const QMargins &r = decoration()->resizeOnlyBorders(); 1262 const int left = r.left(); 1263 const int top = r.top(); 1264 const int right = r.right(); 1265 const int bottom = r.bottom(); 1266 if (left != 0 || top != 0 || right != 0 || bottom != 0) { 1267 region = QRegion(-left, 1268 -top, 1269 decoration()->size().width() + left + right, 1270 decoration()->size().height() + top + bottom); 1271 region = region.subtracted(decoration()->rect()); 1272 } 1273 } 1274 1275 if (region.isEmpty()) { 1276 m_decoInputExtent.reset(); 1277 return; 1278 } 1279 1280 QRectF bounds = region.boundingRect(); 1281 input_offset = bounds.topLeft(); 1282 1283 // Move the bounding rect to screen coordinates 1284 bounds.translate(frameGeometry().topLeft()); 1285 1286 // Move the region to input window coordinates 1287 region.translate(-input_offset.toPoint()); 1288 1289 if (!m_decoInputExtent.isValid()) { 1290 const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; 1291 const uint32_t values[] = {true, 1292 XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION}; 1293 m_decoInputExtent.create(bounds, XCB_WINDOW_CLASS_INPUT_ONLY, mask, values); 1294 if (mapping_state == Mapped) { 1295 m_decoInputExtent.map(); 1296 } 1297 } else { 1298 m_decoInputExtent.setGeometry(bounds); 1299 } 1300 1301 const QList<xcb_rectangle_t> rects = Xcb::regionToRects(region); 1302 xcb_shape_rectangles(kwinApp()->x11Connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, 1303 m_decoInputExtent, 0, 0, rects.count(), rects.constData()); 1304 } 1305 1306 void X11Window::updateDecoration(bool check_workspace_pos, bool force) 1307 { 1308 if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) { 1309 return; 1310 } 1311 QRectF oldgeom = moveResizeGeometry(); 1312 blockGeometryUpdates(true); 1313 if (force) { 1314 destroyDecoration(); 1315 } 1316 if (!noBorder()) { 1317 createDecoration(); 1318 } else { 1319 destroyDecoration(); 1320 } 1321 updateShadow(); 1322 if (check_workspace_pos) { 1323 checkWorkspacePosition(oldgeom); 1324 } 1325 updateInputWindow(); 1326 blockGeometryUpdates(false); 1327 updateFrameExtents(); 1328 } 1329 1330 void X11Window::invalidateDecoration() 1331 { 1332 updateDecoration(true, true); 1333 } 1334 1335 void X11Window::createDecoration() 1336 { 1337 std::shared_ptr<KDecoration2::Decoration> decoration(Workspace::self()->decorationBridge()->createDecoration(this)); 1338 if (decoration) { 1339 connect(decoration.get(), &KDecoration2::Decoration::resizeOnlyBordersChanged, this, [this]() { 1340 if (!isDeleted()) { 1341 updateInputWindow(); 1342 } 1343 }); 1344 connect(decoration.get(), &KDecoration2::Decoration::bordersChanged, this, [this]() { 1345 if (!isDeleted()) { 1346 updateFrameExtents(); 1347 } 1348 }); 1349 connect(decoration.get(), &KDecoration2::Decoration::bordersChanged, this, [this]() { 1350 if (isDeleted()) { 1351 return; 1352 } 1353 GeometryUpdatesBlocker blocker(this); 1354 const QRectF oldGeometry = moveResizeGeometry(); 1355 if (!isShade()) { 1356 checkWorkspacePosition(oldGeometry); 1357 } 1358 }); 1359 connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::sizeChanged, this, [this]() { 1360 if (!isDeleted()) { 1361 updateInputWindow(); 1362 } 1363 }); 1364 } 1365 setDecoration(decoration); 1366 1367 moveResize(QRectF(calculateGravitation(false), clientSizeToFrameSize(clientSize()))); 1368 maybeCreateX11DecorationRenderer(); 1369 } 1370 1371 void X11Window::destroyDecoration() 1372 { 1373 if (isDecorated()) { 1374 QPointF grav = calculateGravitation(true); 1375 setDecoration(nullptr); 1376 maybeDestroyX11DecorationRenderer(); 1377 moveResize(QRectF(grav, clientSizeToFrameSize(clientSize()))); 1378 } 1379 m_decoInputExtent.reset(); 1380 } 1381 1382 void X11Window::maybeCreateX11DecorationRenderer() 1383 { 1384 if (kwinApp()->operationMode() != Application::OperationModeX11) { 1385 return; 1386 } 1387 if (!Compositor::compositing() && decoratedClient()) { 1388 m_decorationRenderer = std::make_unique<X11DecorationRenderer>(decoratedClient()); 1389 decoration()->update(); 1390 } 1391 } 1392 1393 void X11Window::maybeDestroyX11DecorationRenderer() 1394 { 1395 m_decorationRenderer.reset(); 1396 } 1397 1398 void X11Window::detectNoBorder() 1399 { 1400 if (is_shape) { 1401 noborder = true; 1402 app_noborder = true; 1403 return; 1404 } 1405 switch (windowType()) { 1406 case NET::Desktop: 1407 case NET::Dock: 1408 case NET::TopMenu: 1409 case NET::Splash: 1410 case NET::Notification: 1411 case NET::OnScreenDisplay: 1412 case NET::CriticalNotification: 1413 case NET::AppletPopup: 1414 noborder = true; 1415 app_noborder = true; 1416 break; 1417 case NET::Unknown: 1418 case NET::Normal: 1419 case NET::Toolbar: 1420 case NET::Menu: 1421 case NET::Dialog: 1422 case NET::Utility: 1423 noborder = false; 1424 break; 1425 default: 1426 Q_UNREACHABLE(); 1427 } 1428 // NET::Override is some strange beast without clear definition, usually 1429 // just meaning "noborder", so let's treat it only as such flag, and ignore it as 1430 // a window type otherwise (SUPPORTED_WINDOW_TYPES_MASK doesn't include it) 1431 if (info->windowType(NET::OverrideMask) == NET::Override) { 1432 noborder = true; 1433 app_noborder = true; 1434 } 1435 } 1436 1437 void X11Window::updateFrameExtents() 1438 { 1439 NETStrut strut; 1440 strut.left = Xcb::toXNative(borderLeft()); 1441 strut.right = Xcb::toXNative(borderRight()); 1442 strut.top = Xcb::toXNative(borderTop()); 1443 strut.bottom = Xcb::toXNative(borderBottom()); 1444 info->setFrameExtents(strut); 1445 } 1446 1447 void X11Window::setClientFrameExtents(const NETStrut &strut) 1448 { 1449 const QMarginsF clientFrameExtents(Xcb::fromXNative(strut.left), 1450 Xcb::fromXNative(strut.top), 1451 Xcb::fromXNative(strut.right), 1452 Xcb::fromXNative(strut.bottom)); 1453 if (m_clientFrameExtents == clientFrameExtents) { 1454 return; 1455 } 1456 1457 m_clientFrameExtents = clientFrameExtents; 1458 1459 // We should resize the client when its custom frame extents are changed so 1460 // the logical bounds remain the same. This however means that we will send 1461 // several configure requests to the application upon restoring it from the 1462 // maximized or fullscreen state. Notice that a client-side decorated client 1463 // cannot be shaded, therefore it's okay not to use the adjusted size here. 1464 moveResize(moveResizeGeometry()); 1465 } 1466 1467 /** 1468 * Resizes the decoration, and makes sure the decoration widget gets resize event 1469 * even if the size hasn't changed. This is needed to make sure the decoration 1470 * re-layouts (e.g. when maximization state changes, 1471 * the decoration may alter some borders, but the actual size 1472 * of the decoration stays the same). 1473 */ 1474 void X11Window::resizeDecoration() 1475 { 1476 triggerDecorationRepaint(); 1477 updateInputWindow(); 1478 } 1479 1480 bool X11Window::userNoBorder() const 1481 { 1482 return noborder; 1483 } 1484 1485 bool X11Window::isFullScreenable() const 1486 { 1487 if (isUnmanaged()) { 1488 return false; 1489 } 1490 if (!rules()->checkFullScreen(true)) { 1491 return false; 1492 } 1493 if (rules()->checkStrictGeometry(true)) { 1494 // check geometry constraints (rule to obey is set) 1495 const QRectF fullScreenArea = workspace()->clientArea(FullScreenArea, this); 1496 const QSizeF constrainedClientSize = constrainClientSize(fullScreenArea.size()); 1497 if (rules()->checkSize(constrainedClientSize) != fullScreenArea.size()) { 1498 return false; // the app wouldn't fit exactly fullscreen geometry due to its strict geometry requirements 1499 } 1500 } 1501 // don't check size constrains - some apps request fullscreen despite requesting fixed size 1502 return isNormalWindow() || isDialog(); // also better disallow only weird types to go fullscreen 1503 } 1504 1505 bool X11Window::noBorder() const 1506 { 1507 return userNoBorder() || isFullScreen(); 1508 } 1509 1510 bool X11Window::userCanSetNoBorder() const 1511 { 1512 if (isUnmanaged()) { 1513 return false; 1514 } 1515 1516 // Client-side decorations and server-side decorations are mutually exclusive. 1517 if (isClientSideDecorated()) { 1518 return false; 1519 } 1520 1521 return !isFullScreen() && !isShade(); 1522 } 1523 1524 void X11Window::setNoBorder(bool set) 1525 { 1526 if (!userCanSetNoBorder()) { 1527 return; 1528 } 1529 set = rules()->checkNoBorder(set); 1530 if (noborder == set) { 1531 return; 1532 } 1533 noborder = set; 1534 updateDecoration(true, false); 1535 updateWindowRules(Rules::NoBorder); 1536 } 1537 1538 void X11Window::checkNoBorder() 1539 { 1540 setNoBorder(app_noborder); 1541 } 1542 1543 void X11Window::detectShape() 1544 { 1545 is_shape = Xcb::Extensions::self()->hasShape(window()); 1546 } 1547 1548 void X11Window::updateShape() 1549 { 1550 if (is_shape) { 1551 // Workaround for #19644 - Shaped windows shouldn't have decoration 1552 if (!app_noborder) { 1553 // Only when shape is detected for the first time, still let the user to override 1554 app_noborder = true; 1555 noborder = rules()->checkNoBorder(true); 1556 updateDecoration(true); 1557 } 1558 if (!isDecorated()) { 1559 xcb_shape_combine(kwinApp()->x11Connection(), 1560 XCB_SHAPE_SO_SET, 1561 XCB_SHAPE_SK_BOUNDING, 1562 XCB_SHAPE_SK_BOUNDING, 1563 frameId(), 1564 Xcb::toXNative(wrapperPos().x()), 1565 Xcb::toXNative(wrapperPos().y()), 1566 window()); 1567 } 1568 } else if (app_noborder) { 1569 xcb_shape_mask(kwinApp()->x11Connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, frameId(), 0, 0, XCB_PIXMAP_NONE); 1570 detectNoBorder(); 1571 app_noborder = noborder; 1572 noborder = rules()->checkNoBorder(noborder || m_motif.noBorder()); 1573 updateDecoration(true); 1574 } 1575 1576 // Decoration mask (i.e. 'else' here) setting is done in setMask() 1577 // when the decoration calls it or when the decoration is created/destroyed 1578 updateInputShape(); 1579 Q_EMIT shapeChanged(); 1580 } 1581 1582 static Xcb::Window shape_helper_window(XCB_WINDOW_NONE); 1583 1584 void X11Window::cleanupX11() 1585 { 1586 shape_helper_window.reset(); 1587 } 1588 1589 void X11Window::updateInputShape() 1590 { 1591 if (hiddenPreview()) { // Sets it to none, don't change 1592 return; 1593 } 1594 if (Xcb::Extensions::self()->isShapeInputAvailable()) { 1595 // There appears to be no way to find out if a window has input 1596 // shape set or not, so always propagate the input shape 1597 // (it's the same like the bounding shape by default). 1598 // Also, build the shape using a helper window, not directly 1599 // in the frame window, because the sequence set-shape-to-frame, 1600 // remove-shape-of-client, add-input-shape-of-client has the problem 1601 // that after the second step there's a hole in the input shape 1602 // until the real shape of the client is added and that can make 1603 // the window lose focus (which is a problem with mouse focus policies) 1604 // TODO: It seems there is, after all - XShapeGetRectangles() - but maybe this is better 1605 if (!shape_helper_window.isValid()) { 1606 shape_helper_window.create(QRect(0, 0, 1, 1)); 1607 } 1608 const QSizeF bufferSize = m_bufferGeometry.size(); 1609 shape_helper_window.resize(bufferSize); 1610 xcb_connection_t *c = kwinApp()->x11Connection(); 1611 xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING, 1612 shape_helper_window, 0, 0, frameId()); 1613 xcb_shape_combine(c, 1614 XCB_SHAPE_SO_SUBTRACT, 1615 XCB_SHAPE_SK_INPUT, 1616 XCB_SHAPE_SK_BOUNDING, 1617 shape_helper_window, 1618 Xcb::toXNative(wrapperPos().x()), 1619 Xcb::toXNative(wrapperPos().y()), 1620 window()); 1621 xcb_shape_combine(c, 1622 XCB_SHAPE_SO_UNION, 1623 XCB_SHAPE_SK_INPUT, 1624 XCB_SHAPE_SK_INPUT, 1625 shape_helper_window, 1626 Xcb::toXNative(wrapperPos().x()), 1627 Xcb::toXNative(wrapperPos().y()), 1628 window()); 1629 xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT, 1630 frameId(), 0, 0, shape_helper_window); 1631 } 1632 } 1633 1634 bool X11Window::setupCompositing() 1635 { 1636 if (!Window::setupCompositing()) { 1637 return false; 1638 } 1639 // If compositing is back on, stop rendering decoration in the frame window. 1640 maybeDestroyX11DecorationRenderer(); 1641 updateVisibility(); // for internalKeep() 1642 return true; 1643 } 1644 1645 void X11Window::finishCompositing() 1646 { 1647 Window::finishCompositing(); 1648 updateVisibility(); 1649 // If compositing is off, render the decoration in the X11 frame window. 1650 maybeCreateX11DecorationRenderer(); 1651 } 1652 1653 /** 1654 * Returns whether the window is minimizable or not 1655 */ 1656 bool X11Window::isMinimizable() const 1657 { 1658 if (isSpecialWindow() && !isTransient()) { 1659 return false; 1660 } 1661 if (isAppletPopup()) { 1662 return false; 1663 } 1664 if (!rules()->checkMinimize(true)) { 1665 return false; 1666 } 1667 1668 if (isTransient()) { 1669 // #66868 - Let other xmms windows be minimized when the mainwindow is minimized 1670 bool shown_mainwindow = false; 1671 auto mainwindows = mainWindows(); 1672 for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) { 1673 if ((*it)->isShown()) { 1674 shown_mainwindow = true; 1675 } 1676 } 1677 if (!shown_mainwindow) { 1678 return true; 1679 } 1680 } 1681 #if 0 1682 // This is here because kicker's taskbar doesn't provide separate entries 1683 // for windows with an explicitly given parent 1684 // TODO: perhaps this should be redone 1685 // Disabled for now, since at least modal dialogs should be minimizable 1686 // (resulting in the mainwindow being minimized too). 1687 if (transientFor() != NULL) 1688 return false; 1689 #endif 1690 if (!wantsTabFocus()) { // SELI, TODO: - NET::Utility? why wantsTabFocus() - skiptaskbar? ? 1691 return false; 1692 } 1693 return true; 1694 } 1695 1696 void X11Window::doMinimize() 1697 { 1698 if (m_managed) { 1699 if (isMinimized()) { 1700 workspace()->activateNextWindow(this); 1701 } 1702 } 1703 if (isShade()) { 1704 // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded 1705 info->setState(isMinimized() ? NET::States() : NET::Shaded, NET::Shaded); 1706 } 1707 updateVisibility(); 1708 updateAllowedActions(); 1709 workspace()->updateMinimizedOfTransients(this); 1710 } 1711 1712 QRectF X11Window::iconGeometry() const 1713 { 1714 NETRect r = info->iconGeometry(); 1715 QRectF geom = Xcb::fromXNative(QRect(r.pos.x, r.pos.y, r.size.width, r.size.height)); 1716 if (geom.isValid()) { 1717 return geom; 1718 } else { 1719 // Check all mainwindows of this window (recursively) 1720 const auto &clients = mainWindows(); 1721 for (Window *amainwin : clients) { 1722 X11Window *mainwin = dynamic_cast<X11Window *>(amainwin); 1723 if (!mainwin) { 1724 continue; 1725 } 1726 geom = mainwin->iconGeometry(); 1727 if (geom.isValid()) { 1728 return geom; 1729 } 1730 } 1731 // No mainwindow (or their parents) with icon geometry was found 1732 return Window::iconGeometry(); 1733 } 1734 } 1735 1736 bool X11Window::isShadeable() const 1737 { 1738 return !isSpecialWindow() && isDecorated() && (rules()->checkShade(ShadeNormal) != rules()->checkShade(ShadeNone)); 1739 } 1740 1741 void X11Window::doSetShade(ShadeMode previousShadeMode) 1742 { 1743 // TODO: All this unmapping, resizing etc. feels too much duplicated from elsewhere 1744 if (isShade()) { 1745 shade_geometry_change = true; 1746 QSizeF s(implicitSize()); 1747 s.setHeight(borderTop() + borderBottom()); 1748 m_wrapper.selectInput(wrapperEventMask() & ~XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); // Avoid getting UnmapNotify 1749 m_wrapper.unmap(); 1750 m_client.unmap(); 1751 m_wrapper.selectInput(wrapperEventMask()); 1752 exportMappingState(XCB_ICCCM_WM_STATE_ICONIC); 1753 resize(s); 1754 shade_geometry_change = false; 1755 if (previousShadeMode == ShadeHover) { 1756 if (shade_below && workspace()->stackingOrder().indexOf(shade_below) > -1) { 1757 workspace()->restack(this, shade_below, true); 1758 } 1759 if (isActive()) { 1760 workspace()->activateNextWindow(this); 1761 } 1762 } else if (isActive()) { 1763 workspace()->focusToNull(); 1764 } 1765 } else { 1766 shade_geometry_change = true; 1767 if (decoratedClient()) { 1768 decoratedClient()->signalShadeChange(); 1769 } 1770 QSizeF s(implicitSize()); 1771 shade_geometry_change = false; 1772 resize(s); 1773 setGeometryRestore(moveResizeGeometry()); 1774 if ((shadeMode() == ShadeHover || shadeMode() == ShadeActivated) && rules()->checkAcceptFocus(info->input())) { 1775 setActive(true); 1776 } 1777 if (shadeMode() == ShadeHover) { 1778 QList<Window *> order = workspace()->stackingOrder(); 1779 // invalidate, since "this" could be the topmost toplevel and shade_below dangeling 1780 shade_below = nullptr; 1781 // this is likely related to the index parameter?! 1782 for (int idx = order.indexOf(this) + 1; idx < order.count(); ++idx) { 1783 shade_below = qobject_cast<X11Window *>(order.at(idx)); 1784 if (shade_below) { 1785 break; 1786 } 1787 } 1788 if (shade_below && shade_below->isNormalWindow()) { 1789 workspace()->raiseWindow(this); 1790 } else { 1791 shade_below = nullptr; 1792 } 1793 } 1794 m_wrapper.map(); 1795 m_client.map(); 1796 exportMappingState(XCB_ICCCM_WM_STATE_NORMAL); 1797 if (isActive()) { 1798 workspace()->requestFocus(this); 1799 } 1800 } 1801 info->setState(isShade() ? NET::Shaded : NET::States(), NET::Shaded); 1802 info->setState((isShade() || !isShown()) ? NET::Hidden : NET::States(), NET::Hidden); 1803 updateVisibility(); 1804 updateAllowedActions(); 1805 discardWindowPixmap(); 1806 } 1807 1808 void X11Window::updateVisibility() 1809 { 1810 if (isUnmanaged() || isDeleted()) { 1811 return; 1812 } 1813 if (isHidden()) { 1814 info->setState(NET::Hidden, NET::Hidden); 1815 setSkipTaskbar(true); // Also hide from taskbar 1816 if (Compositor::compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) { 1817 internalKeep(); 1818 } else { 1819 internalHide(); 1820 } 1821 return; 1822 } 1823 if (isHiddenByShowDesktop()) { 1824 if (waylandServer()) { 1825 return; 1826 } 1827 if (Compositor::compositing() && options->hiddenPreviews() != HiddenPreviewsNever) { 1828 internalKeep(); 1829 } else { 1830 internalHide(); 1831 } 1832 return; 1833 } 1834 setSkipTaskbar(originalSkipTaskbar()); // Reset from 'hidden' 1835 if (isMinimized()) { 1836 info->setState(NET::Hidden, NET::Hidden); 1837 if (Compositor::compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) { 1838 internalKeep(); 1839 } else { 1840 internalHide(); 1841 } 1842 return; 1843 } 1844 info->setState(NET::States(), NET::Hidden); 1845 if (!isOnCurrentDesktop()) { 1846 if (Compositor::compositing() && options->hiddenPreviews() != HiddenPreviewsNever) { 1847 internalKeep(); 1848 } else { 1849 internalHide(); 1850 } 1851 return; 1852 } 1853 if (!isOnCurrentActivity()) { 1854 if (Compositor::compositing() && options->hiddenPreviews() != HiddenPreviewsNever) { 1855 internalKeep(); 1856 } else { 1857 internalHide(); 1858 } 1859 return; 1860 } 1861 internalShow(); 1862 } 1863 1864 /** 1865 * Sets the client window's mapping state. Possible values are 1866 * WithdrawnState, IconicState, NormalState. 1867 */ 1868 void X11Window::exportMappingState(int s) 1869 { 1870 Q_ASSERT(m_client != XCB_WINDOW_NONE); 1871 Q_ASSERT(!isDeleted() || s == XCB_ICCCM_WM_STATE_WITHDRAWN); 1872 if (s == XCB_ICCCM_WM_STATE_WITHDRAWN) { 1873 m_client.deleteProperty(atoms->wm_state); 1874 return; 1875 } 1876 Q_ASSERT(s == XCB_ICCCM_WM_STATE_NORMAL || s == XCB_ICCCM_WM_STATE_ICONIC); 1877 1878 int32_t data[2]; 1879 data[0] = s; 1880 data[1] = XCB_NONE; 1881 m_client.changeProperty(atoms->wm_state, atoms->wm_state, 32, 2, data); 1882 } 1883 1884 void X11Window::internalShow() 1885 { 1886 if (mapping_state == Mapped) { 1887 return; 1888 } 1889 MappingState old = mapping_state; 1890 mapping_state = Mapped; 1891 if (old == Unmapped || old == Withdrawn) { 1892 map(); 1893 } 1894 if (old == Kept) { 1895 m_decoInputExtent.map(); 1896 updateHiddenPreview(); 1897 } 1898 } 1899 1900 void X11Window::internalHide() 1901 { 1902 if (mapping_state == Unmapped) { 1903 return; 1904 } 1905 MappingState old = mapping_state; 1906 mapping_state = Unmapped; 1907 if (old == Mapped || old == Kept) { 1908 unmap(); 1909 } 1910 if (old == Kept) { 1911 updateHiddenPreview(); 1912 } 1913 } 1914 1915 void X11Window::internalKeep() 1916 { 1917 Q_ASSERT(Compositor::compositing()); 1918 if (mapping_state == Kept) { 1919 return; 1920 } 1921 MappingState old = mapping_state; 1922 mapping_state = Kept; 1923 if (old == Unmapped || old == Withdrawn) { 1924 map(); 1925 } 1926 m_decoInputExtent.unmap(); 1927 if (isActive()) { 1928 workspace()->focusToNull(); // get rid of input focus, bug #317484 1929 } 1930 updateHiddenPreview(); 1931 } 1932 1933 /** 1934 * Maps (shows) the client. Note that it is mapping state of the frame, 1935 * not necessarily the client window itself (i.e. a shaded window is here 1936 * considered mapped, even though it is in IconicState). 1937 */ 1938 void X11Window::map() 1939 { 1940 // XComposite invalidates backing pixmaps on unmap (minimize, different 1941 // virtual desktop, etc.). We kept the last known good pixmap around 1942 // for use in effects, but now we want to have access to the new pixmap 1943 if (Compositor::compositing()) { 1944 discardWindowPixmap(); 1945 } 1946 m_frame.map(); 1947 if (!isShade()) { 1948 m_wrapper.map(); 1949 m_client.map(); 1950 m_decoInputExtent.map(); 1951 exportMappingState(XCB_ICCCM_WM_STATE_NORMAL); 1952 } else { 1953 exportMappingState(XCB_ICCCM_WM_STATE_ICONIC); 1954 } 1955 } 1956 1957 /** 1958 * Unmaps the client. Again, this is about the frame. 1959 */ 1960 void X11Window::unmap() 1961 { 1962 // Here it may look like a race condition, as some other client might try to unmap 1963 // the window between these two XSelectInput() calls. However, they're supposed to 1964 // use XWithdrawWindow(), which also sends a synthetic event to the root window, 1965 // which won't be missed, so this shouldn't be a problem. The chance the real UnmapNotify 1966 // will be missed is also very minimal, so I don't think it's needed to grab the server 1967 // here. 1968 m_wrapper.selectInput(wrapperEventMask() & ~XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); // Avoid getting UnmapNotify 1969 m_frame.unmap(); 1970 m_wrapper.unmap(); 1971 m_client.unmap(); 1972 m_decoInputExtent.unmap(); 1973 m_wrapper.selectInput(wrapperEventMask()); 1974 exportMappingState(XCB_ICCCM_WM_STATE_ICONIC); 1975 } 1976 1977 /** 1978 * XComposite doesn't keep window pixmaps of unmapped windows, which means 1979 * there wouldn't be any previews of windows that are minimized or on another 1980 * virtual desktop. Therefore rawHide() actually keeps such windows mapped. 1981 * However special care needs to be taken so that such windows don't interfere. 1982 * Therefore they're put very low in the stacking order and they have input shape 1983 * set to none, which hopefully is enough. If there's no input shape available, 1984 * then it's hoped that there will be some other desktop above it *shrug*. 1985 * Using normal shape would be better, but that'd affect other things, e.g. painting 1986 * of the actual preview. 1987 */ 1988 void X11Window::updateHiddenPreview() 1989 { 1990 if (hiddenPreview()) { 1991 workspace()->forceRestacking(); 1992 if (Xcb::Extensions::self()->isShapeInputAvailable()) { 1993 xcb_shape_rectangles(kwinApp()->x11Connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, 1994 XCB_CLIP_ORDERING_UNSORTED, frameId(), 0, 0, 0, nullptr); 1995 } 1996 } else { 1997 workspace()->forceRestacking(); 1998 updateInputShape(); 1999 } 2000 } 2001 2002 void X11Window::sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1, uint32_t data2, uint32_t data3) 2003 { 2004 xcb_client_message_event_t ev; 2005 // Every X11 event is 32 bytes (see man xcb_send_event), so XCB will copy 2006 // 32 unconditionally. Add a static_assert to ensure we don't disclose 2007 // stack memory. 2008 static_assert(sizeof(ev) == 32, "Would leak stack data otherwise"); 2009 memset(&ev, 0, sizeof(ev)); 2010 ev.response_type = XCB_CLIENT_MESSAGE; 2011 ev.window = w; 2012 ev.type = a; 2013 ev.format = 32; 2014 ev.data.data32[0] = protocol; 2015 ev.data.data32[1] = xTime(); 2016 ev.data.data32[2] = data1; 2017 ev.data.data32[3] = data2; 2018 ev.data.data32[4] = data3; 2019 uint32_t eventMask = 0; 2020 if (w == kwinApp()->x11RootWindow()) { 2021 eventMask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Magic! 2022 } 2023 xcb_send_event(kwinApp()->x11Connection(), false, w, eventMask, reinterpret_cast<const char *>(&ev)); 2024 xcb_flush(kwinApp()->x11Connection()); 2025 } 2026 2027 /** 2028 * Returns whether the window may be closed (have a close button) 2029 */ 2030 bool X11Window::isCloseable() const 2031 { 2032 return !isUnmanaged() && rules()->checkCloseable(m_motif.close() && !isSpecialWindow()); 2033 } 2034 2035 /** 2036 * Closes the window by either sending a delete_window message or using XKill. 2037 */ 2038 void X11Window::closeWindow() 2039 { 2040 if (!isCloseable()) { 2041 return; 2042 } 2043 2044 // Update user time, because the window may create a confirming dialog. 2045 updateUserTime(); 2046 2047 if (info->supportsProtocol(NET::DeleteWindowProtocol)) { 2048 sendClientMessage(window(), atoms->wm_protocols, atoms->wm_delete_window); 2049 pingWindow(); 2050 } else { // Client will not react on wm_delete_window. We have not choice 2051 // but destroy his connection to the XServer. 2052 killWindow(); 2053 } 2054 } 2055 2056 /** 2057 * Kills the window via XKill 2058 */ 2059 void X11Window::killWindow() 2060 { 2061 qCDebug(KWIN_CORE) << "X11Window::killWindow():" << window(); 2062 if (isUnmanaged()) { 2063 xcb_kill_client(kwinApp()->x11Connection(), window()); 2064 } else { 2065 killProcess(false); 2066 m_client.kill(); // Always kill this client at the server 2067 destroyWindow(); 2068 } 2069 } 2070 2071 /** 2072 * Send a ping to the window using _NET_WM_PING if possible if it 2073 * doesn't respond within a reasonable time, it will be killed. 2074 */ 2075 void X11Window::pingWindow() 2076 { 2077 if (!info->supportsProtocol(NET::PingProtocol)) { 2078 return; // Can't ping :( 2079 } 2080 if (options->killPingTimeout() == 0) { 2081 return; // Turned off 2082 } 2083 if (ping_timer != nullptr) { 2084 return; // Pinging already 2085 } 2086 ping_timer = new QTimer(this); 2087 connect(ping_timer, &QTimer::timeout, this, [this]() { 2088 if (unresponsive()) { 2089 qCDebug(KWIN_CORE) << "Final ping timeout, asking to kill:" << caption(); 2090 ping_timer->deleteLater(); 2091 ping_timer = nullptr; 2092 killProcess(true, m_pingTimestamp); 2093 return; 2094 } 2095 2096 qCDebug(KWIN_CORE) << "First ping timeout:" << caption(); 2097 2098 setUnresponsive(true); 2099 ping_timer->start(); 2100 }); 2101 ping_timer->setSingleShot(true); 2102 // we'll run the timer twice, at first we'll desaturate the window 2103 // and the second time we'll show the "do you want to kill" prompt 2104 ping_timer->start(options->killPingTimeout() / 2); 2105 m_pingTimestamp = xTime(); 2106 rootInfo()->sendPing(window(), m_pingTimestamp); 2107 } 2108 2109 void X11Window::gotPing(xcb_timestamp_t timestamp) 2110 { 2111 // Just plain compare is not good enough because of 64bit and truncating and whatnot 2112 if (NET::timestampCompare(timestamp, m_pingTimestamp) != 0) { 2113 return; 2114 } 2115 delete ping_timer; 2116 ping_timer = nullptr; 2117 2118 setUnresponsive(false); 2119 2120 if (m_killPrompt) { 2121 m_killPrompt->quit(); 2122 } 2123 } 2124 2125 void X11Window::killProcess(bool ask, xcb_timestamp_t timestamp) 2126 { 2127 if (m_killPrompt && m_killPrompt->isRunning()) { 2128 return; 2129 } 2130 Q_ASSERT(!ask || timestamp != XCB_TIME_CURRENT_TIME); 2131 pid_t pid = info->pid(); 2132 if (pid <= 0 || clientMachine()->hostName().isEmpty()) { // Needed properties missing 2133 return; 2134 } 2135 qCDebug(KWIN_CORE) << "Kill process:" << pid << "(" << clientMachine()->hostName() << ")"; 2136 if (!ask) { 2137 if (!clientMachine()->isLocal()) { 2138 QStringList lst; 2139 lst << clientMachine()->hostName() << QStringLiteral("kill") << QString::number(pid); 2140 QProcess::startDetached(QStringLiteral("xon"), lst); 2141 } else { 2142 ::kill(pid, SIGTERM); 2143 } 2144 } else { 2145 if (!m_killPrompt) { 2146 m_killPrompt = std::make_unique<KillPrompt>(this); 2147 } 2148 m_killPrompt->start(timestamp); 2149 } 2150 } 2151 2152 void X11Window::doSetKeepAbove() 2153 { 2154 info->setState(keepAbove() ? NET::KeepAbove : NET::States(), NET::KeepAbove); 2155 } 2156 2157 void X11Window::doSetKeepBelow() 2158 { 2159 info->setState(keepBelow() ? NET::KeepBelow : NET::States(), NET::KeepBelow); 2160 } 2161 2162 void X11Window::doSetSkipTaskbar() 2163 { 2164 info->setState(skipTaskbar() ? NET::SkipTaskbar : NET::States(), NET::SkipTaskbar); 2165 } 2166 2167 void X11Window::doSetSkipPager() 2168 { 2169 info->setState(skipPager() ? NET::SkipPager : NET::States(), NET::SkipPager); 2170 } 2171 2172 void X11Window::doSetSkipSwitcher() 2173 { 2174 info->setState(skipSwitcher() ? NET::SkipSwitcher : NET::States(), NET::SkipSwitcher); 2175 } 2176 2177 void X11Window::doSetDesktop() 2178 { 2179 info->setDesktop(desktopId()); 2180 updateVisibility(); 2181 } 2182 2183 void X11Window::doSetDemandsAttention() 2184 { 2185 info->setState(isDemandingAttention() ? NET::DemandsAttention : NET::States(), NET::DemandsAttention); 2186 } 2187 2188 void X11Window::doSetHidden() 2189 { 2190 updateVisibility(); 2191 } 2192 2193 void X11Window::doSetHiddenByShowDesktop() 2194 { 2195 updateVisibility(); 2196 } 2197 2198 void X11Window::doSetOnActivities(const QStringList &activityList) 2199 { 2200 #if KWIN_BUILD_ACTIVITIES 2201 if (activityList.isEmpty()) { 2202 const QByteArray nullUuid = Activities::nullUuid().toUtf8(); 2203 m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, nullUuid.length(), nullUuid.constData()); 2204 } else { 2205 QByteArray joined = activityList.join(QStringLiteral(",")).toLatin1(); 2206 m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, joined.length(), joined.constData()); 2207 } 2208 #endif 2209 } 2210 2211 void X11Window::updateActivities(bool includeTransients) 2212 { 2213 Window::updateActivities(includeTransients); 2214 if (!m_activityUpdatesBlocked) { 2215 updateVisibility(); 2216 } 2217 } 2218 2219 /** 2220 * Returns the list of activities the client window is on. 2221 * if it's on all activities, the list will be empty. 2222 * Don't use this, use isOnActivity() and friends (from class Window) 2223 */ 2224 QStringList X11Window::activities() const 2225 { 2226 if (sessionActivityOverride) { 2227 return QStringList(); 2228 } 2229 return Window::activities(); 2230 } 2231 2232 /** 2233 * Performs the actual focusing of the window using XSetInputFocus and WM_TAKE_FOCUS 2234 */ 2235 bool X11Window::takeFocus() 2236 { 2237 const bool effectiveAcceptFocus = rules()->checkAcceptFocus(info->input()); 2238 const bool effectiveTakeFocus = rules()->checkAcceptFocus(info->supportsProtocol(NET::TakeFocusProtocol)); 2239 2240 if (effectiveAcceptFocus) { 2241 xcb_void_cookie_t cookie = xcb_set_input_focus_checked(kwinApp()->x11Connection(), 2242 XCB_INPUT_FOCUS_POINTER_ROOT, 2243 window(), XCB_TIME_CURRENT_TIME); 2244 UniqueCPtr<xcb_generic_error_t> error(xcb_request_check(kwinApp()->x11Connection(), cookie)); 2245 if (error) { 2246 qCWarning(KWIN_CORE, "Failed to focus 0x%x (error %d)", window(), error->error_code); 2247 return false; 2248 } 2249 } else { 2250 demandAttention(false); // window cannot take input, at least withdraw urgency 2251 } 2252 if (effectiveTakeFocus) { 2253 kwinApp()->updateXTime(); 2254 sendClientMessage(window(), atoms->wm_protocols, atoms->wm_take_focus); 2255 } 2256 2257 if (effectiveAcceptFocus || effectiveTakeFocus) { 2258 workspace()->setShouldGetFocus(this); 2259 } 2260 return true; 2261 } 2262 2263 /** 2264 * Returns whether the window provides context help or not. If it does, 2265 * you should show a help menu item or a help button like '?' and call 2266 * contextHelp() if this is invoked. 2267 * 2268 * \sa contextHelp() 2269 */ 2270 bool X11Window::providesContextHelp() const 2271 { 2272 return info->supportsProtocol(NET::ContextHelpProtocol); 2273 } 2274 2275 /** 2276 * Invokes context help on the window. Only works if the window 2277 * actually provides context help. 2278 * 2279 * \sa providesContextHelp() 2280 */ 2281 void X11Window::showContextHelp() 2282 { 2283 if (info->supportsProtocol(NET::ContextHelpProtocol)) { 2284 sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_context_help); 2285 } 2286 } 2287 2288 /** 2289 * Fetches the window's caption (WM_NAME property). It will be 2290 * stored in the client's caption(). 2291 */ 2292 void X11Window::fetchName() 2293 { 2294 setCaption(readName()); 2295 } 2296 2297 static inline QString readNameProperty(xcb_window_t w, xcb_atom_t atom) 2298 { 2299 const auto cookie = xcb_icccm_get_text_property_unchecked(kwinApp()->x11Connection(), w, atom); 2300 xcb_icccm_get_text_property_reply_t reply; 2301 if (xcb_icccm_get_wm_name_reply(kwinApp()->x11Connection(), cookie, &reply, nullptr)) { 2302 QString retVal; 2303 if (reply.encoding == atoms->utf8_string) { 2304 retVal = QString::fromUtf8(QByteArray(reply.name, reply.name_len)); 2305 } else if (reply.encoding == XCB_ATOM_STRING) { 2306 retVal = QString::fromLatin1(QByteArray(reply.name, reply.name_len)); 2307 } 2308 xcb_icccm_get_text_property_reply_wipe(&reply); 2309 return retVal.simplified(); 2310 } 2311 return QString(); 2312 } 2313 2314 QString X11Window::readName() const 2315 { 2316 if (info->name() && info->name()[0] != '\0') { 2317 return QString::fromUtf8(info->name()).simplified(); 2318 } else { 2319 return readNameProperty(window(), XCB_ATOM_WM_NAME); 2320 } 2321 } 2322 2323 // The list is taken from https://www.unicode.org/reports/tr9/ (#154840) 2324 static const QChar LRM(0x200E); 2325 2326 void X11Window::setCaption(const QString &_s, bool force) 2327 { 2328 QString s(_s); 2329 for (int i = 0; i < s.length();) { 2330 if (!s[i].isPrint()) { 2331 if (QChar(s[i]).isHighSurrogate() && i + 1 < s.length() && QChar(s[i + 1]).isLowSurrogate()) { 2332 const uint uc = QChar::surrogateToUcs4(s[i], s[i + 1]); 2333 if (!QChar::isPrint(uc)) { 2334 s.remove(i, 2); 2335 } else { 2336 i += 2; 2337 } 2338 continue; 2339 } 2340 s.remove(i, 1); 2341 continue; 2342 } 2343 ++i; 2344 } 2345 const bool changed = (s != cap_normal); 2346 if (!force && !changed) { 2347 return; 2348 } 2349 cap_normal = s; 2350 2351 bool was_suffix = (!cap_suffix.isEmpty()); 2352 cap_suffix.clear(); 2353 QString machine_suffix; 2354 if (!options->condensedTitle()) { // machine doesn't qualify for "clean" 2355 if (clientMachine()->hostName() != ClientMachine::localhost() && !clientMachine()->isLocal()) { 2356 machine_suffix = QLatin1String(" <@") + clientMachine()->hostName() + QLatin1Char('>') + LRM; 2357 } 2358 } 2359 QString shortcut_suffix = shortcutCaptionSuffix(); 2360 cap_suffix = machine_suffix + shortcut_suffix; 2361 if ((was_suffix && cap_suffix.isEmpty()) || force) { 2362 // If it was new window, it may have old value still set, if the window is reused 2363 info->setVisibleName(""); 2364 info->setVisibleIconName(""); 2365 } else if (!cap_suffix.isEmpty() && !cap_iconic.isEmpty()) { 2366 // Keep the same suffix in iconic name if it's set 2367 info->setVisibleIconName(QString(cap_iconic + cap_suffix).toUtf8().constData()); 2368 } 2369 2370 if (changed) { 2371 Q_EMIT captionNormalChanged(); 2372 } 2373 Q_EMIT captionChanged(); 2374 } 2375 2376 void X11Window::updateCaption() 2377 { 2378 setCaption(cap_normal, true); 2379 } 2380 2381 void X11Window::fetchIconicName() 2382 { 2383 QString s; 2384 if (info->iconName() && info->iconName()[0] != '\0') { 2385 s = QString::fromUtf8(info->iconName()); 2386 } else { 2387 s = readNameProperty(window(), XCB_ATOM_WM_ICON_NAME); 2388 } 2389 if (s != cap_iconic) { 2390 bool was_set = !cap_iconic.isEmpty(); 2391 cap_iconic = s; 2392 if (!cap_suffix.isEmpty()) { 2393 if (!cap_iconic.isEmpty()) { // Keep the same suffix in iconic name if it's set 2394 info->setVisibleIconName(QString(s + cap_suffix).toUtf8().constData()); 2395 } else if (was_set) { 2396 info->setVisibleIconName(""); 2397 } 2398 } 2399 } 2400 } 2401 2402 void X11Window::getMotifHints() 2403 { 2404 const bool wasClosable = isCloseable(); 2405 const bool wasNoBorder = m_motif.noBorder(); 2406 if (m_managed) { // only on property change, initial read is prefetched 2407 m_motif.fetch(); 2408 } 2409 m_motif.read(); 2410 if (m_motif.hasDecoration() && m_motif.noBorder() != wasNoBorder) { 2411 // If we just got a hint telling us to hide decorations, we do so. 2412 if (m_motif.noBorder()) { 2413 noborder = rules()->checkNoBorder(true); 2414 // If the Motif hint is now telling us to show decorations, we only do so if the app didn't 2415 // instruct us to hide decorations in some other way, though. 2416 } else if (!app_noborder) { 2417 noborder = rules()->checkNoBorder(false); 2418 } 2419 } 2420 2421 // mminimize; - Ignore, bogus - E.g. shading or sending to another desktop is "minimizing" too 2422 // mmaximize; - Ignore, bogus - Maximizing is basically just resizing 2423 const bool closabilityChanged = wasClosable != isCloseable(); 2424 if (isManaged()) { 2425 updateDecoration(true); // Check if noborder state has changed 2426 } 2427 if (closabilityChanged) { 2428 Q_EMIT closeableChanged(isCloseable()); 2429 } 2430 } 2431 2432 void X11Window::getIcons() 2433 { 2434 if (isUnmanaged()) { 2435 return; 2436 } 2437 // First read icons from the window itself 2438 const QString themedIconName = iconFromDesktopFile(); 2439 if (!themedIconName.isEmpty()) { 2440 setIcon(QIcon::fromTheme(themedIconName)); 2441 return; 2442 } 2443 QIcon icon; 2444 auto readIcon = [this, &icon](int size, bool scale = true) { 2445 const QPixmap pix = KX11Extras::icon(window(), size, size, scale, KX11Extras::NETWM | KX11Extras::WMHints, info); 2446 if (!pix.isNull()) { 2447 icon.addPixmap(pix); 2448 } 2449 }; 2450 readIcon(16); 2451 readIcon(32); 2452 readIcon(48, false); 2453 readIcon(64, false); 2454 readIcon(128, false); 2455 if (icon.isNull()) { 2456 // Then try window group 2457 icon = group()->icon(); 2458 } 2459 if (icon.isNull() && isTransient()) { 2460 // Then mainwindows 2461 auto mainwindows = mainWindows(); 2462 for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd() && icon.isNull(); ++it) { 2463 if (!(*it)->icon().isNull()) { 2464 icon = (*it)->icon(); 2465 break; 2466 } 2467 } 2468 } 2469 if (icon.isNull()) { 2470 // And if nothing else, load icon from classhint or xapp icon 2471 icon.addPixmap(KX11Extras::icon(window(), 32, 32, true, KX11Extras::ClassHint | KX11Extras::XApp, info)); 2472 icon.addPixmap(KX11Extras::icon(window(), 16, 16, true, KX11Extras::ClassHint | KX11Extras::XApp, info)); 2473 icon.addPixmap(KX11Extras::icon(window(), 64, 64, false, KX11Extras::ClassHint | KX11Extras::XApp, info)); 2474 icon.addPixmap(KX11Extras::icon(window(), 128, 128, false, KX11Extras::ClassHint | KX11Extras::XApp, info)); 2475 } 2476 setIcon(icon); 2477 } 2478 2479 /** 2480 * Returns \c true if X11Client wants to throttle resizes; otherwise returns \c false. 2481 */ 2482 bool X11Window::wantsSyncCounter() const 2483 { 2484 if (!waylandServer()) { 2485 return true; 2486 } 2487 // When the frame window is resized, the attached buffer will be destroyed by 2488 // Xwayland, causing unexpected invalid previous and current window pixmaps. 2489 // With the addition of multiple window buffers in Xwayland 1.21, X11 clients 2490 // are no longer able to destroy the buffer after it's been committed and not 2491 // released by the compositor yet. 2492 static const quint32 xwaylandVersion = xcb_get_setup(kwinApp()->x11Connection())->release_number; 2493 return xwaylandVersion >= 12100000; 2494 } 2495 2496 void X11Window::getSyncCounter() 2497 { 2498 if (!Xcb::Extensions::self()->isSyncAvailable()) { 2499 return; 2500 } 2501 if (!wantsSyncCounter()) { 2502 return; 2503 } 2504 2505 Xcb::Property syncProp(false, window(), atoms->net_wm_sync_request_counter, XCB_ATOM_CARDINAL, 0, 1); 2506 const xcb_sync_counter_t counter = syncProp.value<xcb_sync_counter_t>(XCB_NONE); 2507 if (counter != XCB_NONE) { 2508 m_syncRequest.counter = counter; 2509 m_syncRequest.value.hi = 0; 2510 m_syncRequest.value.lo = 0; 2511 auto *c = kwinApp()->x11Connection(); 2512 xcb_sync_set_counter(c, m_syncRequest.counter, m_syncRequest.value); 2513 if (m_syncRequest.alarm == XCB_NONE) { 2514 const uint32_t mask = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE_TYPE | XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_EVENTS; 2515 const uint32_t values[] = { 2516 m_syncRequest.counter, 2517 XCB_SYNC_VALUETYPE_RELATIVE, 2518 XCB_SYNC_TESTTYPE_POSITIVE_TRANSITION, 2519 1}; 2520 m_syncRequest.alarm = xcb_generate_id(c); 2521 auto cookie = xcb_sync_create_alarm_checked(c, m_syncRequest.alarm, mask, values); 2522 UniqueCPtr<xcb_generic_error_t> error(xcb_request_check(c, cookie)); 2523 if (error) { 2524 m_syncRequest.alarm = XCB_NONE; 2525 } else { 2526 xcb_sync_change_alarm_value_list_t value; 2527 memset(&value, 0, sizeof(value)); 2528 value.value.hi = 0; 2529 value.value.lo = 1; 2530 value.delta.hi = 0; 2531 value.delta.lo = 1; 2532 xcb_sync_change_alarm_aux(c, m_syncRequest.alarm, XCB_SYNC_CA_DELTA | XCB_SYNC_CA_VALUE, &value); 2533 } 2534 } 2535 } 2536 } 2537 2538 /** 2539 * Send the client a _NET_SYNC_REQUEST 2540 */ 2541 void X11Window::sendSyncRequest() 2542 { 2543 if (m_syncRequest.counter == XCB_NONE || m_syncRequest.isPending) { 2544 return; // do NOT, NEVER send a sync request when there's one on the stack. the clients will just stop respoding. FOREVER! ... 2545 } 2546 2547 if (!m_syncRequest.failsafeTimeout) { 2548 m_syncRequest.failsafeTimeout = new QTimer(this); 2549 connect(m_syncRequest.failsafeTimeout, &QTimer::timeout, this, [this]() { 2550 // client does not respond to XSYNC requests in reasonable time, remove support 2551 if (!ready_for_painting) { 2552 // failed on initial pre-show request 2553 setReadyForPainting(); 2554 return; 2555 } 2556 // failed during resize 2557 m_syncRequest.isPending = false; 2558 m_syncRequest.interactiveResize = false; 2559 m_syncRequest.counter = XCB_NONE; 2560 m_syncRequest.alarm = XCB_NONE; 2561 delete m_syncRequest.timeout; 2562 delete m_syncRequest.failsafeTimeout; 2563 m_syncRequest.timeout = nullptr; 2564 m_syncRequest.failsafeTimeout = nullptr; 2565 m_syncRequest.lastTimestamp = XCB_CURRENT_TIME; 2566 }); 2567 m_syncRequest.failsafeTimeout->setSingleShot(true); 2568 } 2569 // if there's no response within 10 seconds, sth. went wrong and we remove XSYNC support from this client. 2570 // see events.cpp X11Window::syncEvent() 2571 m_syncRequest.failsafeTimeout->start(ready_for_painting ? 10000 : 1000); 2572 2573 // We increment before the notify so that after the notify 2574 // syncCounterSerial will equal the value we are expecting 2575 // in the acknowledgement 2576 const uint32_t oldLo = m_syncRequest.value.lo; 2577 m_syncRequest.value.lo++; 2578 if (oldLo > m_syncRequest.value.lo) { 2579 m_syncRequest.value.hi++; 2580 } 2581 if (m_syncRequest.lastTimestamp >= xTime()) { 2582 kwinApp()->updateXTime(); 2583 } 2584 2585 // Send the message to client 2586 sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_sync_request, 2587 m_syncRequest.value.lo, m_syncRequest.value.hi); 2588 m_syncRequest.isPending = true; 2589 m_syncRequest.interactiveResize = isInteractiveResize(); 2590 m_syncRequest.lastTimestamp = xTime(); 2591 } 2592 2593 bool X11Window::wantsInput() const 2594 { 2595 return rules()->checkAcceptFocus(acceptsFocus() || info->supportsProtocol(NET::TakeFocusProtocol)); 2596 } 2597 2598 bool X11Window::acceptsFocus() const 2599 { 2600 return info->input(); 2601 } 2602 2603 void X11Window::setBlockingCompositing(bool block) 2604 { 2605 const bool blocks = rules()->checkBlockCompositing(block && options->windowsBlockCompositing()); 2606 if (blocks) { 2607 blockCompositing(); 2608 } else { 2609 unblockCompositing(); 2610 } 2611 } 2612 2613 void X11Window::blockCompositing() 2614 { 2615 if (blocks_compositing) { 2616 return; 2617 } 2618 blocks_compositing = true; 2619 Compositor::self()->inhibit(this); 2620 } 2621 2622 void X11Window::unblockCompositing() 2623 { 2624 if (!blocks_compositing) { 2625 return; 2626 } 2627 blocks_compositing = false; 2628 Compositor::self()->uninhibit(this); 2629 } 2630 2631 void X11Window::updateAllowedActions(bool force) 2632 { 2633 if (!isManaged() && !force) { 2634 return; 2635 } 2636 NET::Actions old_allowed_actions = NET::Actions(allowed_actions); 2637 allowed_actions = NET::Actions(); 2638 if (isMovable()) { 2639 allowed_actions |= NET::ActionMove; 2640 } 2641 if (isResizable()) { 2642 allowed_actions |= NET::ActionResize; 2643 } 2644 if (isMinimizable()) { 2645 allowed_actions |= NET::ActionMinimize; 2646 } 2647 if (isShadeable()) { 2648 allowed_actions |= NET::ActionShade; 2649 } 2650 // Sticky state not supported 2651 if (isMaximizable()) { 2652 allowed_actions |= NET::ActionMax; 2653 } 2654 if (isFullScreenable()) { 2655 allowed_actions |= NET::ActionFullScreen; 2656 } 2657 allowed_actions |= NET::ActionChangeDesktop; // Always (Pagers shouldn't show Docks etc.) 2658 if (isCloseable()) { 2659 allowed_actions |= NET::ActionClose; 2660 } 2661 if (old_allowed_actions == allowed_actions) { 2662 return; 2663 } 2664 // TODO: This could be delayed and compressed - It's only for pagers etc. anyway 2665 info->setAllowedActions(allowed_actions); 2666 // ONLY if relevant features have changed (and the window didn't just get/loose moveresize for maximization state changes) 2667 const NET::Actions relevant = ~(NET::ActionMove | NET::ActionResize); 2668 if ((allowed_actions & relevant) != (old_allowed_actions & relevant)) { 2669 if ((allowed_actions & NET::ActionMinimize) != (old_allowed_actions & NET::ActionMinimize)) { 2670 Q_EMIT minimizeableChanged(allowed_actions & NET::ActionMinimize); 2671 } 2672 if ((allowed_actions & NET::ActionShade) != (old_allowed_actions & NET::ActionShade)) { 2673 Q_EMIT shadeableChanged(allowed_actions & NET::ActionShade); 2674 } 2675 if ((allowed_actions & NET::ActionMax) != (old_allowed_actions & NET::ActionMax)) { 2676 Q_EMIT maximizeableChanged(allowed_actions & NET::ActionMax); 2677 } 2678 if ((allowed_actions & NET::ActionClose) != (old_allowed_actions & NET::ActionClose)) { 2679 Q_EMIT closeableChanged(allowed_actions & NET::ActionClose); 2680 } 2681 } 2682 } 2683 2684 Xcb::StringProperty X11Window::fetchActivities() const 2685 { 2686 #if KWIN_BUILD_ACTIVITIES 2687 return Xcb::StringProperty(window(), atoms->activities); 2688 #else 2689 return Xcb::StringProperty(); 2690 #endif 2691 } 2692 2693 void X11Window::readActivities(Xcb::StringProperty &property) 2694 { 2695 #if KWIN_BUILD_ACTIVITIES 2696 QString prop = QString::fromUtf8(property); 2697 activitiesDefined = !prop.isEmpty(); 2698 2699 if (prop == Activities::nullUuid()) { 2700 // copied from setOnAllActivities to avoid a redundant XChangeProperty. 2701 if (!m_activityList.isEmpty()) { 2702 m_activityList.clear(); 2703 updateActivities(true); 2704 } 2705 return; 2706 } 2707 if (prop.isEmpty()) { 2708 // note: this makes it *act* like it's on all activities but doesn't set the property to 'ALL' 2709 if (!m_activityList.isEmpty()) { 2710 m_activityList.clear(); 2711 updateActivities(true); 2712 } 2713 return; 2714 } 2715 2716 const QStringList newActivitiesList = prop.split(u','); 2717 2718 if (newActivitiesList == m_activityList) { 2719 return; // expected change, it's ok. 2720 } 2721 2722 setOnActivities(newActivitiesList); 2723 #endif 2724 } 2725 2726 void X11Window::checkActivities() 2727 { 2728 #if KWIN_BUILD_ACTIVITIES 2729 Xcb::StringProperty property = fetchActivities(); 2730 readActivities(property); 2731 #endif 2732 } 2733 2734 void X11Window::setSessionActivityOverride(bool needed) 2735 { 2736 sessionActivityOverride = needed; 2737 updateActivities(false); 2738 } 2739 2740 Xcb::StringProperty X11Window::fetchPreferredColorScheme() const 2741 { 2742 return Xcb::StringProperty(m_client, atoms->kde_color_sheme); 2743 } 2744 2745 QString X11Window::readPreferredColorScheme(Xcb::StringProperty &property) const 2746 { 2747 return rules()->checkDecoColor(QString::fromUtf8(property)); 2748 } 2749 2750 QString X11Window::preferredColorScheme() const 2751 { 2752 Xcb::StringProperty property = fetchPreferredColorScheme(); 2753 return readPreferredColorScheme(property); 2754 } 2755 2756 bool X11Window::isClient() const 2757 { 2758 return !m_unmanaged; 2759 } 2760 2761 bool X11Window::isUnmanaged() const 2762 { 2763 return m_unmanaged; 2764 } 2765 2766 bool X11Window::isOutline() const 2767 { 2768 return m_outline; 2769 } 2770 2771 NET::WindowType X11Window::windowType() const 2772 { 2773 if (m_unmanaged) { 2774 return info->windowType(SUPPORTED_UNMANAGED_WINDOW_TYPES_MASK); 2775 } 2776 2777 NET::WindowType wt = info->windowType(SUPPORTED_MANAGED_WINDOW_TYPES_MASK); 2778 // hacks here 2779 if (wt == NET::Unknown) { // this is more or less suggested in NETWM spec 2780 wt = isTransient() ? NET::Dialog : NET::Normal; 2781 } 2782 return wt; 2783 } 2784 2785 void X11Window::cancelFocusOutTimer() 2786 { 2787 if (m_focusOutTimer) { 2788 m_focusOutTimer->stop(); 2789 } 2790 } 2791 2792 xcb_window_t X11Window::frameId() const 2793 { 2794 return m_frame; 2795 } 2796 2797 xcb_window_t X11Window::window() const 2798 { 2799 return m_client; 2800 } 2801 2802 xcb_window_t X11Window::wrapperId() const 2803 { 2804 return m_wrapper; 2805 } 2806 2807 QPointF X11Window::framePosToClientPos(const QPointF &point) const 2808 { 2809 qreal x = point.x(); 2810 qreal y = point.y(); 2811 2812 if (isDecorated()) { 2813 x += borderLeft(); 2814 y += borderTop(); 2815 } else { 2816 x -= m_clientFrameExtents.left(); 2817 y -= m_clientFrameExtents.top(); 2818 } 2819 2820 return QPointF(x, y); 2821 } 2822 2823 QPointF X11Window::clientPosToFramePos(const QPointF &point) const 2824 { 2825 qreal x = point.x(); 2826 qreal y = point.y(); 2827 2828 if (isDecorated()) { 2829 x -= borderLeft(); 2830 y -= borderTop(); 2831 } else { 2832 x += m_clientFrameExtents.left(); 2833 y += m_clientFrameExtents.top(); 2834 } 2835 2836 return QPointF(x, y); 2837 } 2838 2839 QSizeF X11Window::frameSizeToClientSize(const QSizeF &size) const 2840 { 2841 qreal width = size.width(); 2842 qreal height = size.height(); 2843 2844 if (isDecorated()) { 2845 width -= borderLeft() + borderRight(); 2846 height -= borderTop() + borderBottom(); 2847 } else { 2848 width += m_clientFrameExtents.left() + m_clientFrameExtents.right(); 2849 height += m_clientFrameExtents.top() + m_clientFrameExtents.bottom(); 2850 } 2851 2852 return QSizeF(width, height); 2853 } 2854 2855 QSizeF X11Window::clientSizeToFrameSize(const QSizeF &size) const 2856 { 2857 qreal width = size.width(); 2858 qreal height = size.height(); 2859 2860 if (isDecorated()) { 2861 width += borderLeft() + borderRight(); 2862 height += borderTop() + borderBottom(); 2863 } else { 2864 width -= m_clientFrameExtents.left() + m_clientFrameExtents.right(); 2865 height -= m_clientFrameExtents.top() + m_clientFrameExtents.bottom(); 2866 } 2867 2868 return QSizeF(width, height); 2869 } 2870 2871 QRectF X11Window::frameRectToBufferRect(const QRectF &rect) const 2872 { 2873 if (!waylandServer() && isDecorated()) { 2874 return rect; 2875 } 2876 return frameRectToClientRect(rect); 2877 } 2878 2879 /** 2880 * Returns the position of the wrapper window relative to the frame window. On X11, it 2881 * is the same as QPoint(borderLeft(), borderTop()). On Wayland, it's QPoint(0, 0). 2882 */ 2883 QPointF X11Window::wrapperPos() const 2884 { 2885 return m_clientGeometry.topLeft() - m_bufferGeometry.topLeft(); 2886 } 2887 2888 /** 2889 * Returns the natural size of the window, if the window is not shaded it's the same 2890 * as size(). 2891 */ 2892 QSizeF X11Window::implicitSize() const 2893 { 2894 return clientSizeToFrameSize(m_client.geometry().size()); 2895 } 2896 2897 pid_t X11Window::pid() const 2898 { 2899 return info->pid(); 2900 } 2901 2902 QString X11Window::windowRole() const 2903 { 2904 return QString::fromLatin1(info->windowRole()); 2905 } 2906 2907 Xcb::Property X11Window::fetchShowOnScreenEdge() const 2908 { 2909 return Xcb::Property(false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1); 2910 } 2911 2912 void X11Window::readShowOnScreenEdge(Xcb::Property &property) 2913 { 2914 const uint32_t value = property.value<uint32_t>(ElectricNone); 2915 ElectricBorder border = ElectricNone; 2916 switch (value & 0xFF) { 2917 case 0: 2918 border = ElectricTop; 2919 break; 2920 case 1: 2921 border = ElectricRight; 2922 break; 2923 case 2: 2924 border = ElectricBottom; 2925 break; 2926 case 3: 2927 border = ElectricLeft; 2928 break; 2929 } 2930 if (border != ElectricNone) { 2931 disconnect(m_edgeGeometryTrackingConnection); 2932 2933 auto reserveScreenEdge = [this, border]() { 2934 if (workspace()->screenEdges()->reserve(this, border)) { 2935 setHidden(true); 2936 } else { 2937 setHidden(false); 2938 } 2939 }; 2940 2941 reserveScreenEdge(); 2942 m_edgeGeometryTrackingConnection = connect(this, &X11Window::frameGeometryChanged, this, reserveScreenEdge); 2943 } else if (!property.isNull() && property->type != XCB_ATOM_NONE) { 2944 // property value is incorrect, delete the property 2945 // so that the client knows that it is not hidden 2946 xcb_delete_property(kwinApp()->x11Connection(), window(), atoms->kde_screen_edge_show); 2947 } else { 2948 // restore 2949 disconnect(m_edgeGeometryTrackingConnection); 2950 2951 setHidden(false); 2952 2953 workspace()->screenEdges()->reserve(this, ElectricNone); 2954 } 2955 } 2956 2957 void X11Window::updateShowOnScreenEdge() 2958 { 2959 Xcb::Property property = fetchShowOnScreenEdge(); 2960 readShowOnScreenEdge(property); 2961 } 2962 2963 void X11Window::showOnScreenEdge() 2964 { 2965 setHidden(false); 2966 xcb_delete_property(kwinApp()->x11Connection(), window(), atoms->kde_screen_edge_show); 2967 } 2968 2969 bool X11Window::belongsToSameApplication(const Window *other, SameApplicationChecks checks) const 2970 { 2971 const X11Window *c2 = dynamic_cast<const X11Window *>(other); 2972 if (!c2) { 2973 return false; 2974 } 2975 return X11Window::belongToSameApplication(this, c2, checks); 2976 } 2977 2978 QSizeF X11Window::resizeIncrements() const 2979 { 2980 return m_geometryHints.resizeIncrements(); 2981 } 2982 2983 Xcb::StringProperty X11Window::fetchApplicationMenuServiceName() const 2984 { 2985 return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_service_name); 2986 } 2987 2988 void X11Window::readApplicationMenuServiceName(Xcb::StringProperty &property) 2989 { 2990 updateApplicationMenuServiceName(QString::fromUtf8(property)); 2991 } 2992 2993 void X11Window::checkApplicationMenuServiceName() 2994 { 2995 Xcb::StringProperty property = fetchApplicationMenuServiceName(); 2996 readApplicationMenuServiceName(property); 2997 } 2998 2999 Xcb::StringProperty X11Window::fetchApplicationMenuObjectPath() const 3000 { 3001 return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_object_path); 3002 } 3003 3004 void X11Window::readApplicationMenuObjectPath(Xcb::StringProperty &property) 3005 { 3006 updateApplicationMenuObjectPath(QString::fromUtf8(property)); 3007 } 3008 3009 void X11Window::checkApplicationMenuObjectPath() 3010 { 3011 Xcb::StringProperty property = fetchApplicationMenuObjectPath(); 3012 readApplicationMenuObjectPath(property); 3013 } 3014 3015 void X11Window::handleSync() 3016 { 3017 setReadyForPainting(); 3018 m_syncRequest.isPending = false; 3019 if (m_syncRequest.failsafeTimeout) { 3020 m_syncRequest.failsafeTimeout->stop(); 3021 } 3022 3023 // Sync request can be acknowledged shortly after finishing resize. 3024 if (m_syncRequest.interactiveResize) { 3025 m_syncRequest.interactiveResize = false; 3026 if (m_syncRequest.timeout) { 3027 m_syncRequest.timeout->stop(); 3028 } 3029 performInteractiveResize(); 3030 updateWindowPixmap(); 3031 } 3032 } 3033 3034 void X11Window::performInteractiveResize() 3035 { 3036 resize(moveResizeGeometry().size()); 3037 } 3038 3039 bool X11Window::belongToSameApplication(const X11Window *c1, const X11Window *c2, SameApplicationChecks checks) 3040 { 3041 bool same_app = false; 3042 3043 // tests that definitely mean they belong together 3044 if (c1 == c2) { 3045 same_app = true; 3046 } else if (c1->isTransient() && c2->hasTransient(c1, true)) { 3047 same_app = true; // c1 has c2 as mainwindow 3048 } else if (c2->isTransient() && c1->hasTransient(c2, true)) { 3049 same_app = true; // c2 has c1 as mainwindow 3050 } else if (c1->group() == c2->group()) { 3051 same_app = true; // same group 3052 } else if (c1->wmClientLeader() == c2->wmClientLeader() 3053 && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), 3054 && c2->wmClientLeader() != c2->window()) { // don't use in this test then 3055 same_app = true; // same client leader 3056 3057 // tests that mean they most probably don't belong together 3058 } else if ((c1->pid() != c2->pid() && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) 3059 || c1->wmClientMachine(false) != c2->wmClientMachine(false)) { 3060 ; // different processes 3061 } else if (c1->wmClientLeader() != c2->wmClientLeader() 3062 && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), 3063 && c2->wmClientLeader() != c2->window() // don't use in this test then 3064 && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) { 3065 ; // different client leader 3066 } else if (c1->resourceClass() != c2->resourceClass()) { 3067 ; // different apps 3068 } else if (!sameAppWindowRoleMatch(c1, c2, checks.testFlag(SameApplicationCheck::RelaxedForActive)) 3069 && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) { 3070 ; // "different" apps 3071 } else if (c1->pid() == 0 || c2->pid() == 0) { 3072 ; // old apps that don't have _NET_WM_PID, consider them different 3073 // if they weren't found to match above 3074 } else { 3075 same_app = true; // looks like it's the same app 3076 } 3077 3078 return same_app; 3079 } 3080 3081 // Non-transient windows with window role containing '#' are always 3082 // considered belonging to different applications (unless 3083 // the window role is exactly the same). KMainWindow sets 3084 // window role this way by default, and different KMainWindow 3085 // usually "are" different application from user's point of view. 3086 // This help with no-focus-stealing for e.g. konqy reusing. 3087 // On the other hand, if one of the windows is active, they are 3088 // considered belonging to the same application. This is for 3089 // the cases when opening new mainwindow directly from the application, 3090 // e.g. 'Open New Window' in konqy ( active_hack == true ). 3091 bool X11Window::sameAppWindowRoleMatch(const X11Window *c1, const X11Window *c2, bool active_hack) 3092 { 3093 if (c1->isTransient()) { 3094 while (const X11Window *t = dynamic_cast<const X11Window *>(c1->transientFor())) { 3095 c1 = t; 3096 } 3097 if (c1->groupTransient()) { 3098 return c1->group() == c2->group(); 3099 } 3100 #if 0 3101 // if a group transient is in its own group, it didn't possibly have a group, 3102 // and therefore should be considered belonging to the same app like 3103 // all other windows from the same app 3104 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; 3105 #endif 3106 } 3107 if (c2->isTransient()) { 3108 while (const X11Window *t = dynamic_cast<const X11Window *>(c2->transientFor())) { 3109 c2 = t; 3110 } 3111 if (c2->groupTransient()) { 3112 return c1->group() == c2->group(); 3113 } 3114 #if 0 3115 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; 3116 #endif 3117 } 3118 int pos1 = c1->windowRole().indexOf('#'); 3119 int pos2 = c2->windowRole().indexOf('#'); 3120 if ((pos1 >= 0 && pos2 >= 0)) { 3121 if (!active_hack) { // without the active hack for focus stealing prevention, 3122 return c1 == c2; // different mainwindows are always different apps 3123 } 3124 if (!c1->isActive() && !c2->isActive()) { 3125 return c1 == c2; 3126 } else { 3127 return true; 3128 } 3129 } 3130 return true; 3131 } 3132 3133 /* 3134 3135 Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3 3136 3137 WM_TRANSIENT_FOR is basically means "this is my mainwindow". 3138 For NET::Unknown windows, transient windows are considered to be NET::Dialog 3139 windows, for compatibility with non-NETWM clients. KWin may adjust the value 3140 of this property in some cases (window pointing to itself or creating a loop, 3141 keeping NET::Splash windows above other windows from the same app, etc.). 3142 3143 X11Window::transient_for_id is the value of the WM_TRANSIENT_FOR property, after 3144 possibly being adjusted by KWin. X11Window::transient_for points to the Client 3145 this Client is transient for, or is NULL. If X11Window::transient_for_id is 3146 poiting to the root window, the window is considered to be transient 3147 for the whole window group, as suggested in NETWM 7.3. 3148 3149 In the case of group transient window, X11Window::transient_for is NULL, 3150 and X11Window::groupTransient() returns true. Such window is treated as 3151 if it were transient for every window in its window group that has been 3152 mapped _before_ it (or, to be exact, was added to the same group before it). 3153 Otherwise two group transients can create loops, which can lead very very 3154 nasty things (bug #67914 and all its dupes). 3155 3156 X11Window::original_transient_for_id is the value of the property, which 3157 may be different if X11Window::transient_for_id if e.g. forcing NET::Splash 3158 to be kept on top of its window group, or when the mainwindow is not mapped 3159 yet, in which case the window is temporarily made group transient, 3160 and when the mainwindow is mapped, transiency is re-evaluated. 3161 3162 This can get a bit complicated with with e.g. two Konqueror windows created 3163 by the same process. They should ideally appear like two independent applications 3164 to the user. This should be accomplished by all windows in the same process 3165 having the same window group (needs to be changed in Qt at the moment), and 3166 using non-group transients poiting to their relevant mainwindow for toolwindows 3167 etc. KWin should handle both group and non-group transient dialogs well. 3168 3169 In other words: 3170 - non-transient windows : isTransient() == false 3171 - normal transients : transientFor() != NULL 3172 - group transients : groupTransient() == true 3173 3174 - list of mainwindows : mainClients() (call once and loop over the result) 3175 - list of transients : transients() 3176 - every window in the group : group()->members() 3177 */ 3178 3179 Xcb::TransientFor X11Window::fetchTransient() const 3180 { 3181 return Xcb::TransientFor(window()); 3182 } 3183 3184 void X11Window::readTransientProperty(Xcb::TransientFor &transientFor) 3185 { 3186 xcb_window_t new_transient_for_id = XCB_WINDOW_NONE; 3187 if (transientFor.getTransientFor(&new_transient_for_id)) { 3188 m_originalTransientForId = new_transient_for_id; 3189 new_transient_for_id = verifyTransientFor(new_transient_for_id, true); 3190 } else { 3191 m_originalTransientForId = XCB_WINDOW_NONE; 3192 new_transient_for_id = verifyTransientFor(XCB_WINDOW_NONE, false); 3193 } 3194 setTransient(new_transient_for_id); 3195 } 3196 3197 void X11Window::readTransient() 3198 { 3199 if (isUnmanaged()) { 3200 return; 3201 } 3202 Xcb::TransientFor transientFor = fetchTransient(); 3203 readTransientProperty(transientFor); 3204 } 3205 3206 void X11Window::setTransient(xcb_window_t new_transient_for_id) 3207 { 3208 if (new_transient_for_id != m_transientForId) { 3209 removeFromMainClients(); 3210 X11Window *transient_for = nullptr; 3211 m_transientForId = new_transient_for_id; 3212 if (m_transientForId != XCB_WINDOW_NONE && !groupTransient()) { 3213 transient_for = workspace()->findClient(Predicate::WindowMatch, m_transientForId); 3214 Q_ASSERT(transient_for != nullptr); // verifyTransient() had to check this 3215 transient_for->addTransient(this); 3216 } // checkGroup() will check 'check_active_modal' 3217 setTransientFor(transient_for); 3218 checkGroup(nullptr, true); // force, because transiency has changed 3219 updateLayer(); 3220 workspace()->resetUpdateToolWindowsTimer(); 3221 Q_EMIT transientChanged(); 3222 } 3223 } 3224 3225 void X11Window::removeFromMainClients() 3226 { 3227 if (transientFor()) { 3228 transientFor()->removeTransient(this); 3229 } 3230 if (groupTransient()) { 3231 for (auto it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) { 3232 (*it)->removeTransient(this); 3233 } 3234 } 3235 } 3236 3237 // *sigh* this transiency handling is madness :( 3238 // This one is called when destroying/releasing a window. 3239 // It makes sure this client is removed from all grouping 3240 // related lists. 3241 void X11Window::cleanGrouping() 3242 { 3243 // We want to break parent-child relationships, but preserve stacking 3244 // order constraints at the same time for window closing animations. 3245 3246 if (transientFor()) { 3247 transientFor()->removeTransientFromList(this); 3248 setTransientFor(nullptr); 3249 } 3250 3251 if (groupTransient()) { 3252 const auto members = group()->members(); 3253 for (Window *member : members) { 3254 member->removeTransientFromList(this); 3255 } 3256 } 3257 3258 const auto children = transients(); 3259 for (Window *transient : children) { 3260 removeTransientFromList(transient); 3261 transient->setTransientFor(nullptr); 3262 } 3263 3264 group()->removeMember(this); 3265 in_group = nullptr; 3266 m_transientForId = XCB_WINDOW_NONE; 3267 } 3268 3269 // Make sure that no group transient is considered transient 3270 // for a window that is (directly or indirectly) transient for it 3271 // (including another group transients). 3272 // Non-group transients not causing loops are checked in verifyTransientFor(). 3273 void X11Window::checkGroupTransients() 3274 { 3275 for (auto it1 = group()->members().constBegin(); it1 != group()->members().constEnd(); ++it1) { 3276 if (!(*it1)->groupTransient()) { // check all group transients in the group 3277 continue; // TODO optimize to check only the changed ones? 3278 } 3279 for (auto it2 = group()->members().constBegin(); it2 != group()->members().constEnd(); ++it2) { // group transients can be transient only for others in the group, 3280 // so don't make them transient for the ones that are transient for it 3281 if (*it1 == *it2) { 3282 continue; 3283 } 3284 for (Window *cl = (*it2)->transientFor(); cl != nullptr; cl = cl->transientFor()) { 3285 if (cl == *it1) { 3286 // don't use removeTransient(), that would modify *it2 too 3287 (*it2)->removeTransientFromList(*it1); 3288 continue; 3289 } 3290 } 3291 // if *it1 and *it2 are both group transients, and are transient for each other, 3292 // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later, 3293 // and should be therefore on top of *it1 3294 // TODO This could possibly be optimized, it also requires hasTransient() to check for loops. 3295 if ((*it2)->groupTransient() && (*it1)->hasTransient(*it2, true) && (*it2)->hasTransient(*it1, true)) { 3296 (*it2)->removeTransientFromList(*it1); 3297 } 3298 // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3 3299 // is added, make it transient only for W2, not for W1, because it's already indirectly 3300 // transient for it - the indirect transiency actually shouldn't break anything, 3301 // but it can lead to exponentially expensive operations (#95231) 3302 // TODO this is pretty slow as well 3303 for (auto it3 = group()->members().constBegin(); it3 != group()->members().constEnd(); ++it3) { 3304 if (*it1 == *it2 || *it2 == *it3 || *it1 == *it3) { 3305 continue; 3306 } 3307 if ((*it2)->hasTransient(*it1, false) && (*it3)->hasTransient(*it1, false)) { 3308 if ((*it2)->hasTransient(*it3, true)) { 3309 (*it2)->removeTransientFromList(*it1); 3310 } 3311 if ((*it3)->hasTransient(*it2, true)) { 3312 (*it3)->removeTransientFromList(*it1); 3313 } 3314 } 3315 } 3316 } 3317 } 3318 } 3319 3320 /** 3321 * Check that the window is not transient for itself, and similar nonsense. 3322 */ 3323 xcb_window_t X11Window::verifyTransientFor(xcb_window_t new_transient_for, bool set) 3324 { 3325 xcb_window_t new_property_value = new_transient_for; 3326 // make sure splashscreens are shown above all their app's windows, even though 3327 // they're in Normal layer 3328 if (isSplash() && new_transient_for == XCB_WINDOW_NONE) { 3329 new_transient_for = kwinApp()->x11RootWindow(); 3330 } 3331 if (new_transient_for == XCB_WINDOW_NONE) { 3332 if (set) { // sometimes WM_TRANSIENT_FOR is set to None, instead of root window 3333 new_property_value = new_transient_for = kwinApp()->x11RootWindow(); 3334 } else { 3335 return XCB_WINDOW_NONE; 3336 } 3337 } 3338 if (new_transient_for == window()) { // pointing to self 3339 // also fix the property itself 3340 qCWarning(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself."; 3341 new_property_value = new_transient_for = kwinApp()->x11RootWindow(); 3342 } 3343 // The transient_for window may be embedded in another application, 3344 // so kwin cannot see it. Try to find the managed client for the 3345 // window and fix the transient_for property if possible. 3346 xcb_window_t before_search = new_transient_for; 3347 while (new_transient_for != XCB_WINDOW_NONE 3348 && new_transient_for != kwinApp()->x11RootWindow() 3349 && !workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { 3350 Xcb::Tree tree(new_transient_for); 3351 if (tree.isNull()) { 3352 break; 3353 } 3354 new_transient_for = tree->parent; 3355 } 3356 if (X11Window *new_transient_for_client = workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { 3357 if (new_transient_for != before_search) { 3358 qCDebug(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window " 3359 << before_search << ", child of " << new_transient_for_client << ", adjusting."; 3360 new_property_value = new_transient_for; // also fix the property 3361 } 3362 } else { 3363 new_transient_for = before_search; // nice try 3364 } 3365 // loop detection 3366 // group transients cannot cause loops, because they're considered transient only for non-transient 3367 // windows in the group 3368 int count = 20; 3369 xcb_window_t loop_pos = new_transient_for; 3370 while (loop_pos != XCB_WINDOW_NONE && loop_pos != kwinApp()->x11RootWindow()) { 3371 X11Window *pos = workspace()->findClient(Predicate::WindowMatch, loop_pos); 3372 if (pos == nullptr) { 3373 break; 3374 } 3375 loop_pos = pos->m_transientForId; 3376 if (--count == 0 || pos == this) { 3377 qCWarning(KWIN_CORE) << "Client " << this << " caused WM_TRANSIENT_FOR loop."; 3378 new_transient_for = kwinApp()->x11RootWindow(); 3379 } 3380 } 3381 if (new_transient_for != kwinApp()->x11RootWindow() 3382 && workspace()->findClient(Predicate::WindowMatch, new_transient_for) == nullptr) { 3383 // it's transient for a specific window, but that window is not mapped 3384 new_transient_for = kwinApp()->x11RootWindow(); 3385 } 3386 if (new_property_value != m_originalTransientForId) { 3387 Xcb::setTransientFor(window(), new_property_value); 3388 } 3389 return new_transient_for; 3390 } 3391 3392 void X11Window::addTransient(Window *cl) 3393 { 3394 Window::addTransient(cl); 3395 if (workspace()->mostRecentlyActivatedWindow() == this && cl->isModal()) { 3396 check_active_modal = true; 3397 } 3398 } 3399 3400 // A new window has been mapped. Check if it's not a mainwindow for this already existing window. 3401 void X11Window::checkTransient(xcb_window_t w) 3402 { 3403 if (m_originalTransientForId != w) { 3404 return; 3405 } 3406 w = verifyTransientFor(w, true); 3407 setTransient(w); 3408 } 3409 3410 // returns true if cl is the transient_for window for this client, 3411 // or recursively the transient_for window 3412 bool X11Window::hasTransient(const Window *cl, bool indirect) const 3413 { 3414 if (const X11Window *c = dynamic_cast<const X11Window *>(cl)) { 3415 // checkGroupTransients() uses this to break loops, so hasTransient() must detect them 3416 QList<const X11Window *> set; 3417 return hasTransientInternal(c, indirect, set); 3418 } 3419 return false; 3420 } 3421 3422 bool X11Window::hasTransientInternal(const X11Window *cl, bool indirect, QList<const X11Window *> &set) const 3423 { 3424 if (const X11Window *t = dynamic_cast<const X11Window *>(cl->transientFor())) { 3425 if (t == this) { 3426 return true; 3427 } 3428 if (!indirect) { 3429 return false; 3430 } 3431 if (set.contains(cl)) { 3432 return false; 3433 } 3434 set.append(cl); 3435 return hasTransientInternal(t, indirect, set); 3436 } 3437 if (!cl->isTransient()) { 3438 return false; 3439 } 3440 if (group() != cl->group()) { 3441 return false; 3442 } 3443 // cl is group transient, search from top 3444 if (transients().contains(cl)) { 3445 return true; 3446 } 3447 if (!indirect) { 3448 return false; 3449 } 3450 if (set.contains(this)) { 3451 return false; 3452 } 3453 set.append(this); 3454 for (auto it = transients().constBegin(); it != transients().constEnd(); ++it) { 3455 const X11Window *c = qobject_cast<const X11Window *>(*it); 3456 if (!c) { 3457 continue; 3458 } 3459 if (c->hasTransientInternal(cl, indirect, set)) { 3460 return true; 3461 } 3462 } 3463 return false; 3464 } 3465 3466 QList<Window *> X11Window::mainWindows() const 3467 { 3468 if (!isTransient()) { 3469 return QList<Window *>(); 3470 } 3471 if (const Window *t = transientFor()) { 3472 return QList<Window *>{const_cast<Window *>(t)}; 3473 } 3474 QList<Window *> result; 3475 Q_ASSERT(group()); 3476 for (auto it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) { 3477 if ((*it)->hasTransient(this, false)) { 3478 result.append(*it); 3479 } 3480 } 3481 return result; 3482 } 3483 3484 Window *X11Window::findModal(bool allow_itself) 3485 { 3486 for (auto it = transients().constBegin(); it != transients().constEnd(); ++it) { 3487 if (Window *ret = (*it)->findModal(true)) { 3488 return ret; 3489 } 3490 } 3491 if (isModal() && allow_itself) { 3492 return this; 3493 } 3494 return nullptr; 3495 } 3496 3497 // X11Window::window_group only holds the contents of the hint, 3498 // but it should be used only to find the group, not for anything else 3499 // Argument is only when some specific group needs to be set. 3500 void X11Window::checkGroup(Group *set_group, bool force) 3501 { 3502 Group *old_group = in_group; 3503 if (old_group != nullptr) { 3504 old_group->ref(); // turn off automatic deleting 3505 } 3506 if (set_group != nullptr) { 3507 if (set_group != in_group) { 3508 if (in_group != nullptr) { 3509 in_group->removeMember(this); 3510 } 3511 in_group = set_group; 3512 in_group->addMember(this); 3513 } 3514 } else if (info->groupLeader() != XCB_WINDOW_NONE) { 3515 Group *new_group = workspace()->findGroup(info->groupLeader()); 3516 X11Window *t = qobject_cast<X11Window *>(transientFor()); 3517 if (t != nullptr && t->group() != new_group) { 3518 // move the window to the right group (e.g. a dialog provided 3519 // by different app, but transient for this one, so make it part of that group) 3520 new_group = t->group(); 3521 } 3522 if (new_group == nullptr) { // doesn't exist yet 3523 new_group = new Group(info->groupLeader()); 3524 } 3525 if (new_group != in_group) { 3526 if (in_group != nullptr) { 3527 in_group->removeMember(this); 3528 } 3529 in_group = new_group; 3530 in_group->addMember(this); 3531 } 3532 } else { 3533 if (X11Window *t = qobject_cast<X11Window *>(transientFor())) { 3534 // doesn't have window group set, but is transient for something 3535 // so make it part of that group 3536 Group *new_group = t->group(); 3537 if (new_group != in_group) { 3538 if (in_group != nullptr) { 3539 in_group->removeMember(this); 3540 } 3541 in_group = t->group(); 3542 in_group->addMember(this); 3543 } 3544 } else if (groupTransient()) { 3545 // group transient which actually doesn't have a group :( 3546 // try creating group with other windows with the same client leader 3547 Group *new_group = workspace()->findClientLeaderGroup(this); 3548 if (new_group == nullptr) { 3549 new_group = new Group(XCB_WINDOW_NONE); 3550 } 3551 if (new_group != in_group) { 3552 if (in_group != nullptr) { 3553 in_group->removeMember(this); 3554 } 3555 in_group = new_group; 3556 in_group->addMember(this); 3557 } 3558 } else { // Not transient without a group, put it in its client leader group. 3559 // This might be stupid if grouping was used for e.g. taskbar grouping 3560 // or minimizing together the whole group, but as long as it is used 3561 // only for dialogs it's better to keep windows from one app in one group. 3562 Group *new_group = workspace()->findClientLeaderGroup(this); 3563 if (in_group != nullptr && in_group != new_group) { 3564 in_group->removeMember(this); 3565 in_group = nullptr; 3566 } 3567 if (new_group == nullptr) { 3568 new_group = new Group(XCB_WINDOW_NONE); 3569 } 3570 if (in_group != new_group) { 3571 in_group = new_group; 3572 in_group->addMember(this); 3573 } 3574 } 3575 } 3576 if (in_group != old_group || force) { 3577 for (auto it = transients().constBegin(); it != transients().constEnd();) { 3578 auto *c = *it; 3579 // group transients in the old group are no longer transient for it 3580 if (c->groupTransient() && c->group() != group()) { 3581 removeTransientFromList(c); 3582 it = transients().constBegin(); // restart, just in case something more has changed with the list 3583 } else { 3584 ++it; 3585 } 3586 } 3587 if (groupTransient()) { 3588 // no longer transient for ones in the old group 3589 if (old_group != nullptr) { 3590 for (auto it = old_group->members().constBegin(); it != old_group->members().constEnd(); ++it) { 3591 (*it)->removeTransient(this); 3592 } 3593 } 3594 // and make transient for all in the new group 3595 for (auto it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) { 3596 if (*it == this) { 3597 break; // this means the window is only transient for windows mapped before it 3598 } 3599 (*it)->addTransient(this); 3600 } 3601 } 3602 // group transient splashscreens should be transient even for windows 3603 // in group mapped later 3604 for (auto it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) { 3605 if (!(*it)->isSplash()) { 3606 continue; 3607 } 3608 if (!(*it)->groupTransient()) { 3609 continue; 3610 } 3611 if (*it == this || hasTransient(*it, true)) { // TODO indirect? 3612 continue; 3613 } 3614 addTransient(*it); 3615 } 3616 } 3617 if (old_group != nullptr) { 3618 old_group->deref(); // can be now deleted if empty 3619 } 3620 checkGroupTransients(); 3621 checkActiveModal(); 3622 updateLayer(); 3623 } 3624 3625 // used by Workspace::findClientLeaderGroup() 3626 void X11Window::changeClientLeaderGroup(Group *gr) 3627 { 3628 // transientFor() != NULL are in the group of their mainwindow, so keep them there 3629 if (transientFor() != nullptr) { 3630 return; 3631 } 3632 // also don't change the group for window which have group set 3633 if (info->groupLeader()) { 3634 return; 3635 } 3636 checkGroup(gr); // change group 3637 } 3638 3639 bool X11Window::check_active_modal = false; 3640 3641 void X11Window::checkActiveModal() 3642 { 3643 // if the active window got new modal transient, activate it. 3644 // cannot be done in AddTransient(), because there may temporarily 3645 // exist loops, breaking findModal 3646 X11Window *check_modal = dynamic_cast<X11Window *>(workspace()->mostRecentlyActivatedWindow()); 3647 if (check_modal != nullptr && check_modal->check_active_modal) { 3648 X11Window *new_modal = dynamic_cast<X11Window *>(check_modal->findModal()); 3649 if (new_modal != nullptr && new_modal != check_modal) { 3650 if (!new_modal->isManaged()) { 3651 return; // postpone check until end of manage() 3652 } 3653 workspace()->activateWindow(new_modal); 3654 } 3655 check_modal->check_active_modal = false; 3656 } 3657 } 3658 3659 QSizeF X11Window::constrainClientSize(const QSizeF &size, SizeMode mode) const 3660 { 3661 qreal w = size.width(); 3662 qreal h = size.height(); 3663 3664 if (w < 1) { 3665 w = 1; 3666 } 3667 if (h < 1) { 3668 h = 1; 3669 } 3670 3671 // basesize, minsize, maxsize, paspect and resizeinc have all values defined, 3672 // even if they're not set in flags - see getWmNormalHints() 3673 QSizeF min_size = minSize(); 3674 QSizeF max_size = maxSize(); 3675 if (isDecorated()) { 3676 QSizeF decominsize(0, 0); 3677 QSizeF border_size(borderLeft() + borderRight(), borderTop() + borderBottom()); 3678 if (border_size.width() > decominsize.width()) { // just in case 3679 decominsize.setWidth(border_size.width()); 3680 } 3681 if (border_size.height() > decominsize.height()) { 3682 decominsize.setHeight(border_size.height()); 3683 } 3684 if (decominsize.width() > min_size.width()) { 3685 min_size.setWidth(decominsize.width()); 3686 } 3687 if (decominsize.height() > min_size.height()) { 3688 min_size.setHeight(decominsize.height()); 3689 } 3690 } 3691 w = std::min(max_size.width(), w); 3692 h = std::min(max_size.height(), h); 3693 w = std::max(min_size.width(), w); 3694 h = std::max(min_size.height(), h); 3695 3696 if (!rules()->checkStrictGeometry(!isFullScreen())) { 3697 // Disobey increments and aspect by explicit rule. 3698 return QSizeF(w, h); 3699 } 3700 3701 qreal width_inc = m_geometryHints.resizeIncrements().width(); 3702 qreal height_inc = m_geometryHints.resizeIncrements().height(); 3703 qreal basew_inc = m_geometryHints.baseSize().width(); 3704 qreal baseh_inc = m_geometryHints.baseSize().height(); 3705 if (!m_geometryHints.hasBaseSize()) { 3706 basew_inc = m_geometryHints.minSize().width(); 3707 baseh_inc = m_geometryHints.minSize().height(); 3708 } 3709 3710 w = std::floor((w - basew_inc) / width_inc) * width_inc + basew_inc; 3711 h = std::floor((h - baseh_inc) / height_inc) * height_inc + baseh_inc; 3712 3713 // code for aspect ratios based on code from FVWM 3714 /* 3715 * The math looks like this: 3716 * 3717 * minAspectX dwidth maxAspectX 3718 * ---------- <= ------- <= ---------- 3719 * minAspectY dheight maxAspectY 3720 * 3721 * If that is multiplied out, then the width and height are 3722 * invalid in the following situations: 3723 * 3724 * minAspectX * dheight > minAspectY * dwidth 3725 * maxAspectX * dheight < maxAspectY * dwidth 3726 * 3727 */ 3728 if (m_geometryHints.hasAspect()) { 3729 double min_aspect_w = m_geometryHints.minAspect().width(); // use doubles, because the values can be MAX_INT 3730 double min_aspect_h = m_geometryHints.minAspect().height(); // and multiplying would go wrong otherwise 3731 double max_aspect_w = m_geometryHints.maxAspect().width(); 3732 double max_aspect_h = m_geometryHints.maxAspect().height(); 3733 // According to ICCCM 4.1.2.3 PMinSize should be a fallback for PBaseSize for size increments, 3734 // but not for aspect ratio. Since this code comes from FVWM, handles both at the same time, 3735 // and I have no idea how it works, let's hope nobody relies on that. 3736 const QSizeF baseSize = m_geometryHints.baseSize(); 3737 w -= baseSize.width(); 3738 h -= baseSize.height(); 3739 qreal max_width = max_size.width() - baseSize.width(); 3740 qreal min_width = min_size.width() - baseSize.width(); 3741 qreal max_height = max_size.height() - baseSize.height(); 3742 qreal min_height = min_size.height() - baseSize.height(); 3743 #define ASPECT_CHECK_GROW_W \ 3744 if (min_aspect_w * h > min_aspect_h * w) { \ 3745 int delta = int(min_aspect_w * h / min_aspect_h - w) / width_inc * width_inc; \ 3746 if (w + delta <= max_width) \ 3747 w += delta; \ 3748 } 3749 #define ASPECT_CHECK_SHRINK_H_GROW_W \ 3750 if (min_aspect_w * h > min_aspect_h * w) { \ 3751 int delta = int(h - w * min_aspect_h / min_aspect_w) / height_inc * height_inc; \ 3752 if (h - delta >= min_height) \ 3753 h -= delta; \ 3754 else { \ 3755 int delta = int(min_aspect_w * h / min_aspect_h - w) / width_inc * width_inc; \ 3756 if (w + delta <= max_width) \ 3757 w += delta; \ 3758 } \ 3759 } 3760 #define ASPECT_CHECK_GROW_H \ 3761 if (max_aspect_w * h < max_aspect_h * w) { \ 3762 int delta = int(w * max_aspect_h / max_aspect_w - h) / height_inc * height_inc; \ 3763 if (h + delta <= max_height) \ 3764 h += delta; \ 3765 } 3766 #define ASPECT_CHECK_SHRINK_W_GROW_H \ 3767 if (max_aspect_w * h < max_aspect_h * w) { \ 3768 int delta = int(w - max_aspect_w * h / max_aspect_h) / width_inc * width_inc; \ 3769 if (w - delta >= min_width) \ 3770 w -= delta; \ 3771 else { \ 3772 int delta = int(w * max_aspect_h / max_aspect_w - h) / height_inc * height_inc; \ 3773 if (h + delta <= max_height) \ 3774 h += delta; \ 3775 } \ 3776 } 3777 switch (mode) { 3778 case SizeModeAny: 3779 #if 0 // make SizeModeAny equal to SizeModeFixedW - prefer keeping fixed width, 3780 // so that changing aspect ratio to a different value and back keeps the same size (#87298) 3781 { 3782 ASPECT_CHECK_SHRINK_H_GROW_W 3783 ASPECT_CHECK_SHRINK_W_GROW_H 3784 ASPECT_CHECK_GROW_H 3785 ASPECT_CHECK_GROW_W 3786 break; 3787 } 3788 #endif 3789 case SizeModeFixedW: { 3790 // the checks are order so that attempts to modify height are first 3791 ASPECT_CHECK_GROW_H 3792 ASPECT_CHECK_SHRINK_H_GROW_W 3793 ASPECT_CHECK_SHRINK_W_GROW_H 3794 ASPECT_CHECK_GROW_W 3795 break; 3796 } 3797 case SizeModeFixedH: { 3798 ASPECT_CHECK_GROW_W 3799 ASPECT_CHECK_SHRINK_W_GROW_H 3800 ASPECT_CHECK_SHRINK_H_GROW_W 3801 ASPECT_CHECK_GROW_H 3802 break; 3803 } 3804 case SizeModeMax: { 3805 // first checks that try to shrink 3806 ASPECT_CHECK_SHRINK_H_GROW_W 3807 ASPECT_CHECK_SHRINK_W_GROW_H 3808 ASPECT_CHECK_GROW_W 3809 ASPECT_CHECK_GROW_H 3810 break; 3811 } 3812 } 3813 #undef ASPECT_CHECK_SHRINK_H_GROW_W 3814 #undef ASPECT_CHECK_SHRINK_W_GROW_H 3815 #undef ASPECT_CHECK_GROW_W 3816 #undef ASPECT_CHECK_GROW_H 3817 w += baseSize.width(); 3818 h += baseSize.height(); 3819 } 3820 3821 return QSizeF(w, h); 3822 } 3823 3824 void X11Window::getResourceClass() 3825 { 3826 setResourceClass(QString::fromLatin1(info->windowClassName()), QString::fromLatin1(info->windowClassClass())); 3827 } 3828 3829 /** 3830 * Gets the client's normal WM hints and reconfigures itself respectively. 3831 */ 3832 void X11Window::getWmNormalHints() 3833 { 3834 if (isUnmanaged()) { 3835 return; 3836 } 3837 const bool hadFixedAspect = m_geometryHints.hasAspect(); 3838 // roundtrip to X server 3839 m_geometryHints.fetch(); 3840 m_geometryHints.read(); 3841 3842 if (!hadFixedAspect && m_geometryHints.hasAspect()) { 3843 // align to eventual new constraints 3844 maximize(max_mode); 3845 } 3846 if (isManaged()) { 3847 // update to match restrictions 3848 QSizeF new_size = clientSizeToFrameSize(constrainClientSize(clientSize())); 3849 if (new_size != size() && !isFullScreen()) { 3850 QRectF origClientGeometry = m_clientGeometry; 3851 moveResize(resizeWithChecks(moveResizeGeometry(), new_size)); 3852 if ((!isSpecialWindow() || isToolbar()) && !isFullScreen()) { 3853 // try to keep the window in its xinerama screen if possible, 3854 // if that fails at least keep it visible somewhere 3855 QRectF area = workspace()->clientArea(MovementArea, this, moveResizeOutput()); 3856 if (area.contains(origClientGeometry)) { 3857 keepInArea(area); 3858 } 3859 area = workspace()->clientArea(WorkArea, this, moveResizeOutput()); 3860 if (area.contains(origClientGeometry)) { 3861 keepInArea(area); 3862 } 3863 } 3864 } 3865 } 3866 updateAllowedActions(); // affects isResizeable() 3867 } 3868 3869 QSizeF X11Window::minSize() const 3870 { 3871 return rules()->checkMinSize(m_geometryHints.minSize()); 3872 } 3873 3874 QSizeF X11Window::maxSize() const 3875 { 3876 return rules()->checkMaxSize(m_geometryHints.maxSize()); 3877 } 3878 3879 QSizeF X11Window::basicUnit() const 3880 { 3881 return m_geometryHints.resizeIncrements(); 3882 } 3883 3884 /** 3885 * Auxiliary function to inform the client about the current window 3886 * configuration. 3887 */ 3888 void X11Window::sendSyntheticConfigureNotify() 3889 { 3890 // Every X11 event is 32 bytes (see man xcb_send_event), so XCB will copy 3891 // 32 unconditionally. Use a union to ensure we don't disclose stack memory. 3892 union { 3893 xcb_configure_notify_event_t event; 3894 char buffer[32]; 3895 } u; 3896 static_assert(sizeof(u.event) < 32, "wouldn't need the union otherwise"); 3897 memset(&u, 0, sizeof(u)); 3898 xcb_configure_notify_event_t &c = u.event; 3899 u.event.response_type = XCB_CONFIGURE_NOTIFY; 3900 u.event.event = window(); 3901 u.event.window = window(); 3902 u.event.x = Xcb::toXNative(m_clientGeometry.x()); 3903 u.event.y = Xcb::toXNative(m_clientGeometry.y()); 3904 u.event.width = Xcb::toXNative(m_clientGeometry.width()); 3905 u.event.height = Xcb::toXNative(m_clientGeometry.height()); 3906 u.event.border_width = 0; 3907 u.event.above_sibling = XCB_WINDOW_NONE; 3908 u.event.override_redirect = 0; 3909 xcb_send_event(kwinApp()->x11Connection(), true, c.event, XCB_EVENT_MASK_STRUCTURE_NOTIFY, reinterpret_cast<const char *>(&u)); 3910 xcb_flush(kwinApp()->x11Connection()); 3911 } 3912 3913 void X11Window::handleXwaylandScaleChanged() 3914 { 3915 // while KWin implicitly considers the window already resized when the scale changes, 3916 // this is needed to make Xwayland actually resize it as well 3917 resize(moveResizeGeometry().size()); 3918 } 3919 3920 QPointF X11Window::gravityAdjustment(xcb_gravity_t gravity) const 3921 { 3922 qreal dx = 0; 3923 qreal dy = 0; 3924 3925 // dx, dy specify how the client window moves to make space for the frame. 3926 // In general we have to compute the reference point and from that figure 3927 // out how much we need to shift the client, however given that we ignore 3928 // the border width attribute and the extents of the server-side decoration 3929 // are known in advance, we can simplify the math quite a bit and express 3930 // the required window gravity adjustment in terms of border sizes. 3931 switch (gravity) { 3932 case XCB_GRAVITY_NORTH_WEST: // move down right 3933 default: 3934 dx = borderLeft(); 3935 dy = borderTop(); 3936 break; 3937 case XCB_GRAVITY_NORTH: // move right 3938 dx = 0; 3939 dy = borderTop(); 3940 break; 3941 case XCB_GRAVITY_NORTH_EAST: // move down left 3942 dx = -borderRight(); 3943 dy = borderTop(); 3944 break; 3945 case XCB_GRAVITY_WEST: // move right 3946 dx = borderLeft(); 3947 dy = 0; 3948 break; 3949 case XCB_GRAVITY_CENTER: 3950 dx = (borderLeft() - borderRight()) / 2; 3951 dy = (borderTop() - borderBottom()) / 2; 3952 break; 3953 case XCB_GRAVITY_STATIC: // don't move 3954 dx = 0; 3955 dy = 0; 3956 break; 3957 case XCB_GRAVITY_EAST: // move left 3958 dx = -borderRight(); 3959 dy = 0; 3960 break; 3961 case XCB_GRAVITY_SOUTH_WEST: // move up right 3962 dx = borderLeft(); 3963 dy = -borderBottom(); 3964 break; 3965 case XCB_GRAVITY_SOUTH: // move up 3966 dx = 0; 3967 dy = -borderBottom(); 3968 break; 3969 case XCB_GRAVITY_SOUTH_EAST: // move up left 3970 dx = -borderRight(); 3971 dy = -borderBottom(); 3972 break; 3973 } 3974 3975 return QPoint(dx, dy); 3976 } 3977 3978 const QPointF X11Window::calculateGravitation(bool invert) const 3979 { 3980 const QPointF adjustment = gravityAdjustment(m_geometryHints.windowGravity()); 3981 3982 // translate from client movement to frame movement 3983 const qreal dx = adjustment.x() - borderLeft(); 3984 const qreal dy = adjustment.y() - borderTop(); 3985 3986 if (!invert) { 3987 return QPointF(x() + dx, y() + dy); 3988 } else { 3989 return QPointF(x() - dx, y() - dy); 3990 } 3991 } 3992 3993 // co-ordinate are in kwin logical 3994 void X11Window::configureRequest(int value_mask, qreal rx, qreal ry, qreal rw, qreal rh, int gravity, bool from_tool) 3995 { 3996 const int configurePositionMask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; 3997 const int configureSizeMask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; 3998 const int configureGeometryMask = configurePositionMask | configureSizeMask; 3999 4000 // "maximized" is a user setting -> we do not allow the client to resize itself 4001 // away from this & against the users explicit wish 4002 qCDebug(KWIN_CORE) << this << bool(value_mask & configureGeometryMask) << bool(maximizeMode() & MaximizeVertical) << bool(maximizeMode() & MaximizeHorizontal); 4003 4004 // we want to (partially) ignore the request when the window is somehow maximized or quicktiled 4005 bool ignore = !app_noborder && (quickTileMode() != QuickTileMode(QuickTileFlag::None) || maximizeMode() != MaximizeRestore); 4006 // however, the user shall be able to force obedience despite and also disobedience in general 4007 ignore = rules()->checkIgnoreGeometry(ignore); 4008 if (!ignore) { // either we're not max'd / q'tiled or the user allowed the client to break that - so break it. 4009 updateQuickTileMode(QuickTileFlag::None); 4010 max_mode = MaximizeRestore; 4011 Q_EMIT quickTileModeChanged(); 4012 } else if (!app_noborder && quickTileMode() == QuickTileMode(QuickTileFlag::None) && (maximizeMode() == MaximizeVertical || maximizeMode() == MaximizeHorizontal)) { 4013 // ignoring can be, because either we do, or the user does explicitly not want it. 4014 // for partially maximized windows we want to allow configures in the other dimension. 4015 // so we've to ask the user again - to know whether we just ignored for the partial maximization. 4016 // the problem here is, that the user can explicitly permit configure requests - even for maximized windows! 4017 // we cannot distinguish that from passing "false" for partially maximized windows. 4018 ignore = rules()->checkIgnoreGeometry(false); 4019 if (!ignore) { // the user is not interested, so we fix up dimensions 4020 if (maximizeMode() == MaximizeVertical) { 4021 value_mask &= ~(XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT); 4022 } 4023 if (maximizeMode() == MaximizeHorizontal) { 4024 value_mask &= ~(XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_WIDTH); 4025 } 4026 if (!(value_mask & configureGeometryMask)) { 4027 ignore = true; // the modification turned the request void 4028 } 4029 } 4030 } 4031 4032 if (ignore) { 4033 qCDebug(KWIN_CORE) << "DENIED"; 4034 return; // nothing to (left) to do for use - bugs #158974, #252314, #321491 4035 } 4036 4037 qCDebug(KWIN_CORE) << "PERMITTED" << this << bool(value_mask & configureGeometryMask); 4038 4039 if (gravity == 0) { // default (nonsense) value for the argument 4040 gravity = m_geometryHints.windowGravity(); 4041 } 4042 if (value_mask & configurePositionMask) { 4043 QPointF new_pos = framePosToClientPos(pos()); 4044 new_pos -= gravityAdjustment(xcb_gravity_t(gravity)); 4045 if (value_mask & XCB_CONFIG_WINDOW_X) { 4046 new_pos.setX(rx); 4047 } 4048 if (value_mask & XCB_CONFIG_WINDOW_Y) { 4049 new_pos.setY(ry); 4050 } 4051 new_pos += gravityAdjustment(xcb_gravity_t(gravity)); 4052 new_pos = clientPosToFramePos(new_pos); 4053 4054 qreal nw = clientSize().width(); 4055 qreal nh = clientSize().height(); 4056 if (value_mask & XCB_CONFIG_WINDOW_WIDTH) { 4057 nw = rw; 4058 } 4059 if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) { 4060 nh = rh; 4061 } 4062 const QSizeF requestedClientSize = constrainClientSize(QSizeF(nw, nh)); 4063 QSizeF requestedFrameSize = clientSizeToFrameSize(requestedClientSize); 4064 requestedFrameSize = rules()->checkSize(requestedFrameSize); 4065 new_pos = rules()->checkPosition(new_pos); 4066 4067 Output *newOutput = workspace()->outputAt(QRectF(new_pos, requestedFrameSize).center()); 4068 if (newOutput != rules()->checkOutput(newOutput)) { 4069 return; // not allowed by rule 4070 } 4071 4072 QRectF origClientGeometry = m_clientGeometry; 4073 GeometryUpdatesBlocker blocker(this); 4074 move(new_pos); 4075 resize(requestedFrameSize); 4076 QRectF area = workspace()->clientArea(WorkArea, this, moveResizeOutput()); 4077 if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen() 4078 && area.contains(origClientGeometry)) { 4079 keepInArea(area); 4080 } 4081 4082 // this is part of the kicker-xinerama-hack... it should be 4083 // safe to remove when kicker gets proper ExtendedStrut support; 4084 // see Workspace::updateClientArea() and 4085 // X11Window::adjustedClientArea() 4086 if (hasStrut()) { 4087 workspace()->updateClientArea(); 4088 } 4089 } 4090 4091 if (value_mask & configureSizeMask && !(value_mask & configurePositionMask)) { // pure resize 4092 qreal nw = clientSize().width(); 4093 qreal nh = clientSize().height(); 4094 if (value_mask & XCB_CONFIG_WINDOW_WIDTH) { 4095 nw = rw; 4096 } 4097 if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) { 4098 nh = rh; 4099 } 4100 4101 const QSizeF requestedClientSize = constrainClientSize(QSizeF(nw, nh)); 4102 QSizeF requestedFrameSize = clientSizeToFrameSize(requestedClientSize); 4103 requestedFrameSize = rules()->checkSize(requestedFrameSize); 4104 4105 if (requestedFrameSize != size()) { // don't restore if some app sets its own size again 4106 QRectF origClientGeometry = m_clientGeometry; 4107 GeometryUpdatesBlocker blocker(this); 4108 moveResize(resizeWithChecks(moveResizeGeometry(), requestedFrameSize, xcb_gravity_t(gravity))); 4109 if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()) { 4110 // try to keep the window in its xinerama screen if possible, 4111 // if that fails at least keep it visible somewhere 4112 QRectF area = workspace()->clientArea(MovementArea, this, moveResizeOutput()); 4113 if (area.contains(origClientGeometry)) { 4114 keepInArea(area); 4115 } 4116 area = workspace()->clientArea(WorkArea, this, moveResizeOutput()); 4117 if (area.contains(origClientGeometry)) { 4118 keepInArea(area); 4119 } 4120 } 4121 } 4122 } 4123 // No need to send synthetic configure notify event here, either it's sent together 4124 // with geometry change, or there's no need to send it. 4125 // Handling of the real ConfigureRequest event forces sending it, as there it's necessary. 4126 } 4127 4128 QRectF X11Window::resizeWithChecks(const QRectF &geometry, qreal w, qreal h, xcb_gravity_t gravity) 4129 { 4130 Q_ASSERT(!shade_geometry_change); 4131 if (isShade()) { 4132 if (h == borderTop() + borderBottom()) { 4133 qCWarning(KWIN_CORE) << "Shaded geometry passed for size:"; 4134 } 4135 } 4136 qreal newx = geometry.x(); 4137 qreal newy = geometry.y(); 4138 QRectF area = workspace()->clientArea(WorkArea, this, geometry.center()); 4139 // don't allow growing larger than workarea 4140 if (w > area.width()) { 4141 w = area.width(); 4142 } 4143 if (h > area.height()) { 4144 h = area.height(); 4145 } 4146 QSizeF tmp = constrainFrameSize(QSizeF(w, h)); // checks size constraints, including min/max size 4147 w = tmp.width(); 4148 h = tmp.height(); 4149 if (gravity == 0) { 4150 gravity = m_geometryHints.windowGravity(); 4151 } 4152 switch (gravity) { 4153 case XCB_GRAVITY_NORTH_WEST: // top left corner doesn't move 4154 default: 4155 break; 4156 case XCB_GRAVITY_NORTH: // middle of top border doesn't move 4157 newx = (newx + geometry.width() / 2) - (w / 2); 4158 break; 4159 case XCB_GRAVITY_NORTH_EAST: // top right corner doesn't move 4160 newx = newx + geometry.width() - w; 4161 break; 4162 case XCB_GRAVITY_WEST: // middle of left border doesn't move 4163 newy = (newy + geometry.height() / 2) - (h / 2); 4164 break; 4165 case XCB_GRAVITY_CENTER: // middle point doesn't move 4166 newx = (newx + geometry.width() / 2) - (w / 2); 4167 newy = (newy + geometry.height() / 2) - (h / 2); 4168 break; 4169 case XCB_GRAVITY_STATIC: // top left corner of _client_ window doesn't move 4170 // since decoration doesn't change, equal to NorthWestGravity 4171 break; 4172 case XCB_GRAVITY_EAST: // // middle of right border doesn't move 4173 newx = newx + geometry.width() - w; 4174 newy = (newy + geometry.height() / 2) - (h / 2); 4175 break; 4176 case XCB_GRAVITY_SOUTH_WEST: // bottom left corner doesn't move 4177 newy = newy + geometry.height() - h; 4178 break; 4179 case XCB_GRAVITY_SOUTH: // middle of bottom border doesn't move 4180 newx = (newx + geometry.width() / 2) - (w / 2); 4181 newy = newy + geometry.height() - h; 4182 break; 4183 case XCB_GRAVITY_SOUTH_EAST: // bottom right corner doesn't move 4184 newx = newx + geometry.width() - w; 4185 newy = newy + geometry.height() - h; 4186 break; 4187 } 4188 return QRectF{newx, newy, w, h}; 4189 } 4190 4191 // _NET_MOVERESIZE_WINDOW 4192 // note co-ordinates are kwin logical 4193 void X11Window::NETMoveResizeWindow(int flags, qreal x, qreal y, qreal width, qreal height) 4194 { 4195 int gravity = flags & 0xff; 4196 int value_mask = 0; 4197 if (flags & (1 << 8)) { 4198 value_mask |= XCB_CONFIG_WINDOW_X; 4199 } 4200 if (flags & (1 << 9)) { 4201 value_mask |= XCB_CONFIG_WINDOW_Y; 4202 } 4203 if (flags & (1 << 10)) { 4204 value_mask |= XCB_CONFIG_WINDOW_WIDTH; 4205 } 4206 if (flags & (1 << 11)) { 4207 value_mask |= XCB_CONFIG_WINDOW_HEIGHT; 4208 } 4209 configureRequest(value_mask, x, y, width, height, gravity, true); 4210 } 4211 4212 // _GTK_SHOW_WINDOW_MENU 4213 void X11Window::GTKShowWindowMenu(qreal x_root, qreal y_root) 4214 { 4215 QPoint globalPos(x_root, y_root); 4216 workspace()->showWindowMenu(QRect(globalPos, globalPos), this); 4217 } 4218 4219 bool X11Window::isMovable() const 4220 { 4221 if (isUnmanaged()) { 4222 return false; 4223 } 4224 if (!hasNETSupport() && !m_motif.move()) { 4225 return false; 4226 } 4227 if (isFullScreen()) { 4228 return false; 4229 } 4230 if (isSpecialWindow() && !isSplash() && !isToolbar()) { // allow moving of splashscreens :) 4231 return false; 4232 } 4233 if (rules()->checkPosition(invalidPoint) != invalidPoint) { // forced position 4234 return false; 4235 } 4236 return true; 4237 } 4238 4239 bool X11Window::isMovableAcrossScreens() const 4240 { 4241 if (isUnmanaged()) { 4242 return false; 4243 } 4244 if (!hasNETSupport() && !m_motif.move()) { 4245 return false; 4246 } 4247 if (isSpecialWindow() && !isSplash() && !isToolbar()) { // allow moving of splashscreens :) 4248 return false; 4249 } 4250 if (rules()->checkPosition(invalidPoint) != invalidPoint) { // forced position 4251 return false; 4252 } 4253 return true; 4254 } 4255 4256 bool X11Window::isResizable() const 4257 { 4258 if (isUnmanaged()) { 4259 return false; 4260 } 4261 if (!hasNETSupport() && !m_motif.resize()) { 4262 return false; 4263 } 4264 if (isFullScreen()) { 4265 return false; 4266 } 4267 if (isSpecialWindow() || isSplash() || isToolbar()) { 4268 return false; 4269 } 4270 if (rules()->checkSize(QSize()).isValid()) { // forced size 4271 return false; 4272 } 4273 const Gravity gravity = interactiveMoveResizeGravity(); 4274 if ((gravity == Gravity::Top || gravity == Gravity::TopLeft || gravity == Gravity::TopRight || gravity == Gravity::Left || gravity == Gravity::BottomLeft) && rules()->checkPosition(invalidPoint) != invalidPoint) { 4275 return false; 4276 } 4277 4278 QSizeF min = minSize(); 4279 QSizeF max = maxSize(); 4280 return min.width() < max.width() || min.height() < max.height(); 4281 } 4282 4283 bool X11Window::isMaximizable() const 4284 { 4285 if (isUnmanaged()) { 4286 return false; 4287 } 4288 if (!isResizable() || isToolbar()) { // SELI isToolbar() ? 4289 return false; 4290 } 4291 if (isAppletPopup()) { 4292 return false; 4293 } 4294 if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore && rules()->checkMaximize(MaximizeFull) != MaximizeRestore) { 4295 return true; 4296 } 4297 return false; 4298 } 4299 4300 /** 4301 * Reimplemented to inform the client about the new window position. 4302 */ 4303 void X11Window::moveResizeInternal(const QRectF &rect, MoveResizeMode mode) 4304 { 4305 // Ok, the shading geometry stuff. Generally, code doesn't care about shaded geometry, 4306 // simply because there are too many places dealing with geometry. Those places 4307 // ignore shaded state and use normal geometry, which they usually should get 4308 // from adjustedSize(). Such geometry comes here, and if the window is shaded, 4309 // the geometry is used only for client_size, since that one is not used when 4310 // shading. Then the frame geometry is adjusted for the shaded geometry. 4311 // This gets more complicated in the case the code does only something like 4312 // setGeometry( geometry()) - geometry() will return the shaded frame geometry. 4313 // Such code is wrong and should be changed to handle the case when the window is shaded, 4314 // for example using X11Window::clientSize() 4315 4316 if (isUnmanaged()) { 4317 qCWarning(KWIN_CORE) << "Cannot move or resize unmanaged window" << this; 4318 return; 4319 } 4320 4321 QRectF frameGeometry = Xcb::fromXNative(Xcb::toXNative(rect)); 4322 4323 if (shade_geometry_change) { 4324 ; // nothing 4325 } else if (isShade()) { 4326 if (frameGeometry.height() == borderTop() + borderBottom()) { 4327 qCDebug(KWIN_CORE) << "Shaded geometry passed for size:"; 4328 } else { 4329 m_clientGeometry = frameRectToClientRect(frameGeometry); 4330 frameGeometry.setHeight(borderTop() + borderBottom()); 4331 } 4332 } else { 4333 m_clientGeometry = frameRectToClientRect(frameGeometry); 4334 } 4335 m_frameGeometry = frameGeometry; 4336 m_bufferGeometry = frameRectToBufferRect(frameGeometry); 4337 4338 if (pendingMoveResizeMode() == MoveResizeMode::None && m_lastBufferGeometry == m_bufferGeometry && m_lastFrameGeometry == m_frameGeometry && m_lastClientGeometry == m_clientGeometry) { 4339 return; 4340 } 4341 4342 m_output = workspace()->outputAt(frameGeometry.center()); 4343 if (areGeometryUpdatesBlocked()) { 4344 setPendingMoveResizeMode(mode); 4345 return; 4346 } 4347 4348 Q_EMIT frameGeometryAboutToChange(); 4349 const QRectF oldBufferGeometry = m_lastBufferGeometry; 4350 const QRectF oldFrameGeometry = m_lastFrameGeometry; 4351 const QRectF oldClientGeometry = m_lastClientGeometry; 4352 const Output *oldOutput = m_lastOutput; 4353 4354 updateServerGeometry(); 4355 updateWindowRules(Rules::Position | Rules::Size); 4356 4357 m_lastBufferGeometry = m_bufferGeometry; 4358 m_lastFrameGeometry = m_frameGeometry; 4359 m_lastClientGeometry = m_clientGeometry; 4360 m_lastOutput = m_output; 4361 4362 if (isActive()) { 4363 workspace()->setActiveOutput(output()); 4364 } 4365 workspace()->updateStackingOrder(); 4366 4367 if (oldBufferGeometry != m_bufferGeometry) { 4368 Q_EMIT bufferGeometryChanged(oldBufferGeometry); 4369 } 4370 if (oldClientGeometry != m_clientGeometry) { 4371 Q_EMIT clientGeometryChanged(oldClientGeometry); 4372 } 4373 if (oldFrameGeometry != m_frameGeometry) { 4374 Q_EMIT frameGeometryChanged(oldFrameGeometry); 4375 } 4376 if (oldOutput != m_output) { 4377 Q_EMIT outputChanged(); 4378 } 4379 Q_EMIT shapeChanged(); 4380 } 4381 4382 void X11Window::updateServerGeometry() 4383 { 4384 const QRectF oldBufferGeometry = m_lastBufferGeometry; 4385 4386 // Compute the old client rect, the client geometry is always inside the buffer geometry. 4387 const QRectF oldClientRect = m_lastClientGeometry.translated(-m_lastBufferGeometry.topLeft()); 4388 const QRectF clientRect = m_clientGeometry.translated(-m_bufferGeometry.topLeft()); 4389 4390 if (oldBufferGeometry.size() != m_bufferGeometry.size() || oldClientRect != clientRect) { 4391 resizeDecoration(); 4392 // If the client is being interactively resized, then the frame window, the wrapper window, 4393 // and the client window have correct geometry at this point, so we don't have to configure 4394 // them again. 4395 if (m_frame.geometry() != m_bufferGeometry) { 4396 m_frame.setGeometry(m_bufferGeometry); 4397 } 4398 if (!isShade()) { 4399 if (m_wrapper.geometry() != clientRect) { 4400 m_wrapper.setGeometry(clientRect); 4401 } 4402 if (m_client.geometry() != QRectF(QPointF(0, 0), clientRect.size())) { 4403 m_client.setGeometry(QRectF(QPointF(0, 0), clientRect.size())); 4404 } 4405 // SELI - won't this be too expensive? 4406 // THOMAS - yes, but gtk+ clients will not resize without ... 4407 sendSyntheticConfigureNotify(); 4408 } 4409 updateShape(); 4410 } else { 4411 m_frame.move(m_bufferGeometry.topLeft()); 4412 sendSyntheticConfigureNotify(); 4413 // Unconditionally move the input window: it won't affect rendering 4414 m_decoInputExtent.move(pos().toPoint() + inputPos()); 4415 } 4416 } 4417 4418 static bool changeMaximizeRecursion = false; 4419 void X11Window::maximize(MaximizeMode mode) 4420 { 4421 if (isUnmanaged()) { 4422 qCWarning(KWIN_CORE) << "Cannot change maximized state of unmanaged window" << this; 4423 return; 4424 } 4425 4426 if (changeMaximizeRecursion) { 4427 return; 4428 } 4429 4430 if (!isResizable() || isToolbar()) { // SELI isToolbar() ? 4431 return; 4432 } 4433 if (!isMaximizable()) { 4434 return; 4435 } 4436 4437 QRectF clientArea; 4438 if (isElectricBorderMaximizing()) { 4439 clientArea = workspace()->clientArea(MaximizeArea, this, Cursors::self()->mouse()->pos()); 4440 } else { 4441 clientArea = workspace()->clientArea(MaximizeArea, this, moveResizeOutput()); 4442 } 4443 4444 MaximizeMode old_mode = max_mode; 4445 4446 // if the client insist on a fix aspect ratio, we check whether the maximizing will get us 4447 // out of screen bounds and take that as a "full maximization with aspect check" then 4448 if (m_geometryHints.hasAspect() && // fixed aspect 4449 (mode == MaximizeVertical || mode == MaximizeHorizontal) && // ondimensional maximization 4450 rules()->checkStrictGeometry(true)) { // obey aspect 4451 const QSize minAspect = m_geometryHints.minAspect(); 4452 const QSize maxAspect = m_geometryHints.maxAspect(); 4453 if (mode == MaximizeVertical || (old_mode & MaximizeVertical)) { 4454 const double fx = minAspect.width(); // use doubles, because the values can be MAX_INT 4455 const double fy = maxAspect.height(); // use doubles, because the values can be MAX_INT 4456 if (fx * clientArea.height() / fy > clientArea.width()) { // too big 4457 mode = old_mode & MaximizeHorizontal ? MaximizeRestore : MaximizeFull; 4458 } 4459 } else { // mode == MaximizeHorizontal 4460 const double fx = maxAspect.width(); 4461 const double fy = minAspect.height(); 4462 if (fy * clientArea.width() / fx > clientArea.height()) { // too big 4463 mode = old_mode & MaximizeVertical ? MaximizeRestore : MaximizeFull; 4464 } 4465 } 4466 } 4467 4468 mode = rules()->checkMaximize(mode); 4469 if (max_mode == mode) { 4470 return; 4471 } 4472 4473 blockGeometryUpdates(true); 4474 4475 // maximing one way and unmaximizing the other way shouldn't happen, 4476 // so restore first and then maximize the other way 4477 if ((old_mode == MaximizeVertical && mode == MaximizeHorizontal) 4478 || (old_mode == MaximizeHorizontal && mode == MaximizeVertical)) { 4479 maximize(MaximizeRestore); // restore 4480 } 4481 4482 Q_EMIT maximizedAboutToChange(mode); 4483 max_mode = mode; 4484 4485 // save sizes for restoring, if maximalizing 4486 QSizeF sz; 4487 if (isShade()) { 4488 sz = implicitSize(); 4489 } else { 4490 sz = size(); 4491 } 4492 4493 if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) { 4494 QRectF savedGeometry = geometryRestore(); 4495 if (!(old_mode & MaximizeVertical)) { 4496 savedGeometry.setTop(y()); 4497 savedGeometry.setHeight(sz.height()); 4498 } 4499 if (!(old_mode & MaximizeHorizontal)) { 4500 savedGeometry.setLeft(x()); 4501 savedGeometry.setWidth(sz.width()); 4502 } 4503 setGeometryRestore(savedGeometry); 4504 } 4505 4506 // call into decoration update borders 4507 if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && max_mode == KWin::MaximizeFull)) { 4508 changeMaximizeRecursion = true; 4509 const auto c = decoration()->client(); 4510 if ((max_mode & MaximizeVertical) != (old_mode & MaximizeVertical)) { 4511 Q_EMIT c->maximizedVerticallyChanged(max_mode & MaximizeVertical); 4512 } 4513 if ((max_mode & MaximizeHorizontal) != (old_mode & MaximizeHorizontal)) { 4514 Q_EMIT c->maximizedHorizontallyChanged(max_mode & MaximizeHorizontal); 4515 } 4516 if ((max_mode == MaximizeFull) != (old_mode == MaximizeFull)) { 4517 Q_EMIT c->maximizedChanged(max_mode == MaximizeFull); 4518 } 4519 changeMaximizeRecursion = false; 4520 } 4521 4522 if (options->borderlessMaximizedWindows()) { 4523 // triggers a maximize change. 4524 // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry 4525 changeMaximizeRecursion = true; 4526 setNoBorder(rules()->checkNoBorder(app_noborder || (m_motif.hasDecoration() && m_motif.noBorder()) || max_mode == MaximizeFull)); 4527 changeMaximizeRecursion = false; 4528 } 4529 4530 // Conditional quick tiling exit points 4531 if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { 4532 if (old_mode == MaximizeFull && !clientArea.contains(geometryRestore().center())) { 4533 // Not restoring on the same screen 4534 // TODO: The following doesn't work for some reason 4535 // quick_tile_mode = QuickTileFlag::None; // And exit quick tile mode manually 4536 } else if ((old_mode == MaximizeVertical && max_mode == MaximizeRestore) || (old_mode == MaximizeFull && max_mode == MaximizeHorizontal)) { 4537 // Modifying geometry of a tiled window 4538 updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry 4539 } 4540 } 4541 4542 switch (max_mode) { 4543 4544 case MaximizeVertical: { 4545 if (old_mode & MaximizeHorizontal) { // actually restoring from MaximizeFull 4546 if (geometryRestore().width() == 0 || !clientArea.contains(geometryRestore().center())) { 4547 // needs placement 4548 resize(constrainFrameSize(QSize(width() * 2 / 3, clientArea.height()), SizeModeFixedH)); 4549 workspace()->placement()->placeSmart(this, clientArea); 4550 } else { 4551 moveResize(QRectF(QPointF(geometryRestore().x(), clientArea.top()), 4552 constrainFrameSize(QSize(geometryRestore().width(), clientArea.height()), SizeModeFixedH))); 4553 } 4554 } else { 4555 QRectF r(x(), clientArea.top(), width(), clientArea.height()); 4556 r.setTopLeft(rules()->checkPosition(r.topLeft())); 4557 r.setSize(constrainFrameSize(r.size(), SizeModeFixedH)); 4558 moveResize(r); 4559 } 4560 info->setState(NET::MaxVert, NET::Max); 4561 break; 4562 } 4563 4564 case MaximizeHorizontal: { 4565 if (old_mode & MaximizeVertical) { // actually restoring from MaximizeFull 4566 if (geometryRestore().height() == 0 || !clientArea.contains(geometryRestore().center())) { 4567 // needs placement 4568 resize(constrainFrameSize(QSize(clientArea.width(), height() * 2 / 3), SizeModeFixedW)); 4569 workspace()->placement()->placeSmart(this, clientArea); 4570 } else { 4571 moveResize(QRectF(QPoint(clientArea.left(), geometryRestore().y()), 4572 constrainFrameSize(QSize(clientArea.width(), geometryRestore().height()), SizeModeFixedW))); 4573 } 4574 } else { 4575 QRectF r(clientArea.left(), y(), clientArea.width(), height()); 4576 r.setTopLeft(rules()->checkPosition(r.topLeft())); 4577 r.setSize(constrainFrameSize(r.size(), SizeModeFixedW)); 4578 moveResize(r); 4579 } 4580 info->setState(NET::MaxHoriz, NET::Max); 4581 break; 4582 } 4583 4584 case MaximizeRestore: { 4585 QRectF restore = moveResizeGeometry(); 4586 // when only partially maximized, geom_restore may not have the other dimension remembered 4587 if (old_mode & MaximizeVertical) { 4588 restore.setTop(geometryRestore().top()); 4589 restore.setBottom(geometryRestore().bottom()); 4590 } 4591 if (old_mode & MaximizeHorizontal) { 4592 restore.setLeft(geometryRestore().left()); 4593 restore.setRight(geometryRestore().right()); 4594 } 4595 if (!restore.isValid()) { 4596 QSize s = QSize(clientArea.width() * 2 / 3, clientArea.height() * 2 / 3); 4597 if (geometryRestore().width() > 0) { 4598 s.setWidth(geometryRestore().width()); 4599 } 4600 if (geometryRestore().height() > 0) { 4601 s.setHeight(geometryRestore().height()); 4602 } 4603 resize(constrainFrameSize(s)); 4604 workspace()->placement()->placeSmart(this, clientArea); 4605 restore = moveResizeGeometry(); 4606 if (geometryRestore().width() > 0) { 4607 restore.moveLeft(geometryRestore().x()); 4608 } 4609 if (geometryRestore().height() > 0) { 4610 restore.moveTop(geometryRestore().y()); 4611 } 4612 setGeometryRestore(restore); // relevant for mouse pos calculation, bug #298646 4613 } 4614 if (m_geometryHints.hasAspect()) { 4615 restore.setSize(constrainFrameSize(restore.size(), SizeModeAny)); 4616 } 4617 moveResize(restore); 4618 if (!clientArea.contains(geometryRestore().center())) { // Not restoring to the same screen 4619 workspace()->placement()->place(this, clientArea); 4620 } 4621 info->setState(NET::States(), NET::Max); 4622 updateQuickTileMode(QuickTileFlag::None); 4623 break; 4624 } 4625 4626 case MaximizeFull: { 4627 QRectF r(clientArea); 4628 r.setTopLeft(rules()->checkPosition(r.topLeft())); 4629 r.setSize(constrainFrameSize(r.size(), SizeModeMax)); 4630 if (r.size() != clientArea.size()) { // to avoid off-by-one errors... 4631 if (isElectricBorderMaximizing() && r.width() < clientArea.width()) { 4632 r.moveLeft(std::max(clientArea.left(), Cursors::self()->mouse()->pos().x() - r.width() / 2)); 4633 r.moveRight(std::min(clientArea.right(), r.right())); 4634 } else { 4635 r.moveCenter(clientArea.center()); 4636 const bool closeHeight = r.height() > 97 * clientArea.height() / 100; 4637 const bool closeWidth = r.width() > 97 * clientArea.width() / 100; 4638 const bool overHeight = r.height() > clientArea.height(); 4639 const bool overWidth = r.width() > clientArea.width(); 4640 if (closeWidth || closeHeight) { 4641 Qt::Edge titlePos = titlebarPosition(); 4642 const QRectF screenArea = workspace()->clientArea(ScreenArea, this, clientArea.center()); 4643 if (closeHeight) { 4644 bool tryBottom = titlePos == Qt::BottomEdge; 4645 if ((overHeight && titlePos == Qt::TopEdge) || screenArea.top() == clientArea.top()) { 4646 r.setTop(clientArea.top()); 4647 } else { 4648 tryBottom = true; 4649 } 4650 if (tryBottom && (overHeight || screenArea.bottom() == clientArea.bottom())) { 4651 r.setBottom(clientArea.bottom()); 4652 } 4653 } 4654 if (closeWidth) { 4655 bool tryLeft = titlePos == Qt::LeftEdge; 4656 if ((overWidth && titlePos == Qt::RightEdge) || screenArea.right() == clientArea.right()) { 4657 r.setRight(clientArea.right()); 4658 } else { 4659 tryLeft = true; 4660 } 4661 if (tryLeft && (overWidth || screenArea.left() == clientArea.left())) { 4662 r.setLeft(clientArea.left()); 4663 } 4664 } 4665 } 4666 } 4667 r.moveTopLeft(rules()->checkPosition(r.topLeft())); 4668 } 4669 // The above code tries to center align the window followed by setting top and bottom 4670 // it's possible that we're in between two pixels 4671 r = Xcb::nativeFloor(r); 4672 4673 moveResize(r); 4674 if (options->electricBorderMaximize() && r.top() == clientArea.top()) { 4675 updateQuickTileMode(QuickTileFlag::Maximize); 4676 } else { 4677 updateQuickTileMode(QuickTileFlag::None); 4678 } 4679 setTile(nullptr); 4680 info->setState(NET::Max, NET::Max); 4681 break; 4682 } 4683 default: 4684 break; 4685 } 4686 4687 blockGeometryUpdates(false); 4688 updateAllowedActions(); 4689 updateWindowRules(Rules::MaximizeVert | Rules::MaximizeHoriz | Rules::Position | Rules::Size); 4690 Q_EMIT quickTileModeChanged(); 4691 4692 if (max_mode != old_mode) { 4693 Q_EMIT maximizedChanged(); 4694 } 4695 } 4696 4697 void X11Window::setFullScreen(bool set) 4698 { 4699 set = rules()->checkFullScreen(set); 4700 4701 const bool wasFullscreen = isFullScreen(); 4702 if (wasFullscreen == set) { 4703 return; 4704 } 4705 if (!isFullScreenable()) { 4706 return; 4707 } 4708 4709 setShade(ShadeNone); 4710 4711 if (wasFullscreen) { 4712 workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event 4713 } else { 4714 setFullscreenGeometryRestore(moveResizeGeometry()); 4715 } 4716 4717 if (set) { 4718 m_fullscreenMode = FullScreenNormal; 4719 workspace()->raiseWindow(this); 4720 } else { 4721 m_fullscreenMode = FullScreenNone; 4722 } 4723 4724 StackingUpdatesBlocker blocker1(workspace()); 4725 GeometryUpdatesBlocker blocker2(this); 4726 4727 // active fullscreens get different layer 4728 updateLayer(); 4729 4730 info->setState(isFullScreen() ? NET::FullScreen : NET::States(), NET::FullScreen); 4731 updateDecoration(false, false); 4732 4733 if (set) { 4734 if (info->fullscreenMonitors().isSet()) { 4735 moveResize(fullscreenMonitorsArea(info->fullscreenMonitors())); 4736 } else { 4737 moveResize(workspace()->clientArea(FullScreenArea, this, moveResizeOutput())); 4738 } 4739 } else { 4740 Q_ASSERT(!fullscreenGeometryRestore().isNull()); 4741 moveResize(QRectF(fullscreenGeometryRestore().topLeft(), constrainFrameSize(fullscreenGeometryRestore().size()))); 4742 } 4743 4744 updateWindowRules(Rules::Fullscreen | Rules::Position | Rules::Size); 4745 updateAllowedActions(false); 4746 Q_EMIT fullScreenChanged(); 4747 } 4748 4749 void X11Window::updateFullscreenMonitors(NETFullscreenMonitors topology) 4750 { 4751 const int outputCount = workspace()->outputs().count(); 4752 4753 // qDebug() << "incoming request with top: " << topology.top << " bottom: " << topology.bottom 4754 // << " left: " << topology.left << " right: " << topology.right 4755 // << ", we have: " << nscreens << " screens."; 4756 4757 if (topology.top >= outputCount || topology.bottom >= outputCount || topology.left >= outputCount || topology.right >= outputCount) { 4758 qCWarning(KWIN_CORE) << "fullscreenMonitors update failed. request higher than number of screens."; 4759 return; 4760 } 4761 4762 info->setFullscreenMonitors(topology); 4763 if (isFullScreen()) { 4764 moveResize(fullscreenMonitorsArea(topology)); 4765 } 4766 } 4767 4768 /** 4769 * Calculates the bounding rectangle defined by the 4 monitor indices indicating the 4770 * top, bottom, left, and right edges of the window when the fullscreen state is enabled. 4771 */ 4772 QRect X11Window::fullscreenMonitorsArea(NETFullscreenMonitors requestedTopology) const 4773 { 4774 QRect total; 4775 4776 if (auto output = workspace()->xineramaIndexToOutput(requestedTopology.top)) { 4777 total = total.united(output->geometry()); 4778 } 4779 if (auto output = workspace()->xineramaIndexToOutput(requestedTopology.bottom)) { 4780 total = total.united(output->geometry()); 4781 } 4782 if (auto output = workspace()->xineramaIndexToOutput(requestedTopology.left)) { 4783 total = total.united(output->geometry()); 4784 } 4785 if (auto output = workspace()->xineramaIndexToOutput(requestedTopology.right)) { 4786 total = total.united(output->geometry()); 4787 } 4788 4789 return total; 4790 } 4791 4792 bool X11Window::doStartInteractiveMoveResize() 4793 { 4794 if (kwinApp()->operationMode() == Application::OperationModeX11) { 4795 bool has_grab = false; 4796 // This reportedly improves smoothness of the moveresize operation, 4797 // something with Enter/LeaveNotify events, looks like XFree performance problem or something *shrug* 4798 // (https://lists.kde.org/?t=107302193400001&r=1&w=2) 4799 QRectF r = workspace()->clientArea(FullArea, this, moveResizeOutput()); 4800 m_moveResizeGrabWindow.create(Xcb::toXNative(r), XCB_WINDOW_CLASS_INPUT_ONLY, 0, nullptr, kwinApp()->x11RootWindow()); 4801 m_moveResizeGrabWindow.map(); 4802 m_moveResizeGrabWindow.raise(); 4803 kwinApp()->updateXTime(); 4804 const xcb_grab_pointer_cookie_t cookie = xcb_grab_pointer_unchecked(kwinApp()->x11Connection(), false, m_moveResizeGrabWindow, 4805 XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW, 4806 XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, m_moveResizeGrabWindow, Cursors::self()->mouse()->x11Cursor(cursor()), xTime()); 4807 UniqueCPtr<xcb_grab_pointer_reply_t> pointerGrab(xcb_grab_pointer_reply(kwinApp()->x11Connection(), cookie, nullptr)); 4808 if (pointerGrab && pointerGrab->status == XCB_GRAB_STATUS_SUCCESS) { 4809 has_grab = true; 4810 } 4811 if (!has_grab && grabXKeyboard(frameId())) { 4812 has_grab = move_resize_has_keyboard_grab = true; 4813 } 4814 if (!has_grab) { // at least one grab is necessary in order to be able to finish move/resize 4815 m_moveResizeGrabWindow.reset(); 4816 return false; 4817 } 4818 } 4819 return true; 4820 } 4821 4822 void X11Window::leaveInteractiveMoveResize() 4823 { 4824 if (kwinApp()->operationMode() == Application::OperationModeX11) { 4825 if (move_resize_has_keyboard_grab) { 4826 ungrabXKeyboard(); 4827 } 4828 move_resize_has_keyboard_grab = false; 4829 xcb_ungrab_pointer(kwinApp()->x11Connection(), xTime()); 4830 m_moveResizeGrabWindow.reset(); 4831 } 4832 Window::leaveInteractiveMoveResize(); 4833 } 4834 4835 bool X11Window::isWaitingForInteractiveMoveResizeSync() const 4836 { 4837 return m_syncRequest.isPending && m_syncRequest.interactiveResize; 4838 } 4839 4840 void X11Window::doInteractiveResizeSync(const QRectF &rect) 4841 { 4842 setMoveResizeGeometry(rect); 4843 4844 if (!m_syncRequest.timeout) { 4845 m_syncRequest.timeout = new QTimer(this); 4846 connect(m_syncRequest.timeout, &QTimer::timeout, this, &X11Window::handleSyncTimeout); 4847 m_syncRequest.timeout->setSingleShot(true); 4848 } 4849 4850 if (m_syncRequest.counter != XCB_NONE) { 4851 m_syncRequest.timeout->start(250); 4852 sendSyncRequest(); 4853 } else { 4854 // For clients not supporting the XSYNC protocol, we limit the resizes to 30Hz 4855 // to take pointless load from X11 and the client, the mouse is still moved at 4856 // full speed and no human can control faster resizes anyway. 4857 m_syncRequest.isPending = true; 4858 m_syncRequest.interactiveResize = true; 4859 m_syncRequest.timeout->start(33); 4860 } 4861 4862 const QRectF moveResizeClientGeometry = frameRectToClientRect(moveResizeGeometry()); 4863 const QRectF moveResizeBufferGeometry = frameRectToBufferRect(moveResizeGeometry()); 4864 4865 // According to the Composite extension spec, a window will get a new pixmap allocated each time 4866 // it is mapped or resized. Given that we redirect frame windows and not client windows, we have 4867 // to resize the frame window in order to forcefully reallocate offscreen storage. If we don't do 4868 // this, then we might render partially updated client window. I know, it sucks. 4869 m_frame.setGeometry(moveResizeBufferGeometry); 4870 m_wrapper.setGeometry(moveResizeClientGeometry.translated(-moveResizeBufferGeometry.topLeft())); 4871 m_client.setGeometry(QRectF(QPointF(0, 0), moveResizeClientGeometry.size())); 4872 } 4873 4874 void X11Window::handleSyncTimeout() 4875 { 4876 if (m_syncRequest.counter == XCB_NONE) { // client w/o XSYNC support. allow the next resize event 4877 m_syncRequest.isPending = false; // NEVER do this for clients with a valid counter 4878 m_syncRequest.interactiveResize = false; // (leads to sync request races in some clients) 4879 } 4880 performInteractiveResize(); 4881 } 4882 4883 NETExtendedStrut X11Window::strut() const 4884 { 4885 NETExtendedStrut ext = info->extendedStrut(); 4886 NETStrut str = info->strut(); 4887 const QSize displaySize = workspace()->geometry().size(); 4888 if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 4889 && (str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0)) { 4890 // build extended from simple 4891 if (str.left != 0) { 4892 ext.left_width = str.left; 4893 ext.left_start = 0; 4894 ext.left_end = displaySize.height(); 4895 } 4896 if (str.right != 0) { 4897 ext.right_width = str.right; 4898 ext.right_start = 0; 4899 ext.right_end = displaySize.height(); 4900 } 4901 if (str.top != 0) { 4902 ext.top_width = str.top; 4903 ext.top_start = 0; 4904 ext.top_end = displaySize.width(); 4905 } 4906 if (str.bottom != 0) { 4907 ext.bottom_width = str.bottom; 4908 ext.bottom_start = 0; 4909 ext.bottom_end = displaySize.width(); 4910 } 4911 } 4912 return ext; 4913 } 4914 4915 StrutRect X11Window::strutRect(StrutArea area) const 4916 { 4917 Q_ASSERT(area != StrutAreaAll); // Not valid 4918 const QSize displaySize = workspace()->geometry().size(); 4919 NETExtendedStrut strutArea = strut(); 4920 switch (area) { 4921 case StrutAreaTop: 4922 if (strutArea.top_width != 0) { 4923 return StrutRect(QRect( 4924 strutArea.top_start, 0, 4925 strutArea.top_end - strutArea.top_start, strutArea.top_width), 4926 StrutAreaTop); 4927 } 4928 break; 4929 case StrutAreaRight: 4930 if (strutArea.right_width != 0) { 4931 return StrutRect(QRect( 4932 displaySize.width() - strutArea.right_width, strutArea.right_start, 4933 strutArea.right_width, strutArea.right_end - strutArea.right_start), 4934 StrutAreaRight); 4935 } 4936 break; 4937 case StrutAreaBottom: 4938 if (strutArea.bottom_width != 0) { 4939 return StrutRect(QRect( 4940 strutArea.bottom_start, displaySize.height() - strutArea.bottom_width, 4941 strutArea.bottom_end - strutArea.bottom_start, strutArea.bottom_width), 4942 StrutAreaBottom); 4943 } 4944 break; 4945 case StrutAreaLeft: 4946 if (strutArea.left_width != 0) { 4947 return StrutRect(QRect( 4948 0, strutArea.left_start, 4949 strutArea.left_width, strutArea.left_end - strutArea.left_start), 4950 StrutAreaLeft); 4951 } 4952 break; 4953 default: 4954 Q_UNREACHABLE(); // Not valid 4955 } 4956 return StrutRect(); // Null rect 4957 } 4958 4959 bool X11Window::hasStrut() const 4960 { 4961 NETExtendedStrut ext = strut(); 4962 if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0) { 4963 return false; 4964 } 4965 return true; 4966 } 4967 4968 void X11Window::applyWindowRules() 4969 { 4970 Window::applyWindowRules(); 4971 updateAllowedActions(); 4972 setBlockingCompositing(info->isBlockingCompositing()); 4973 } 4974 4975 bool X11Window::supportsWindowRules() const 4976 { 4977 return !isUnmanaged(); 4978 } 4979 4980 void X11Window::updateWindowRules(Rules::Types selection) 4981 { 4982 if (!isManaged()) { // not fully setup yet 4983 return; 4984 } 4985 Window::updateWindowRules(selection); 4986 } 4987 4988 void X11Window::damageNotifyEvent() 4989 { 4990 Q_ASSERT(kwinApp()->operationMode() == Application::OperationModeX11); 4991 4992 if (!readyForPainting()) { // avoid "setReadyForPainting()" function calling overhead 4993 if (m_syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now 4994 setReadyForPainting(); 4995 } 4996 } 4997 4998 SurfaceItemX11 *item = static_cast<SurfaceItemX11 *>(surfaceItem()); 4999 if (item) { 5000 item->processDamage(); 5001 } 5002 } 5003 5004 void X11Window::discardWindowPixmap() 5005 { 5006 if (auto item = surfaceItem()) { 5007 item->discardPixmap(); 5008 } 5009 } 5010 5011 void X11Window::updateWindowPixmap() 5012 { 5013 if (auto item = surfaceItem()) { 5014 item->updatePixmap(); 5015 } 5016 } 5017 5018 void X11Window::associate() 5019 { 5020 auto handleMapped = [this]() { 5021 if (syncRequest().counter == XCB_NONE) { // cannot detect complete redraw, consider done now 5022 setReadyForPainting(); 5023 } 5024 }; 5025 5026 if (surface()->isMapped()) { 5027 handleMapped(); 5028 } else { 5029 // Queued connection because we want to mark the window ready for painting after 5030 // the associated surface item has processed the new surface state. 5031 connect(surface(), &SurfaceInterface::mapped, this, handleMapped, Qt::QueuedConnection); 5032 } 5033 5034 m_pendingSurfaceId = 0; 5035 } 5036 5037 QWindow *X11Window::findInternalWindow() const 5038 { 5039 const QWindowList windows = kwinApp()->topLevelWindows(); 5040 for (QWindow *w : windows) { 5041 if (w->handle() && w->winId() == window()) { 5042 return w; 5043 } 5044 } 5045 return nullptr; 5046 } 5047 5048 void X11Window::checkOutput() 5049 { 5050 setOutput(workspace()->outputAt(frameGeometry().center())); 5051 } 5052 5053 void X11Window::getWmOpaqueRegion() 5054 { 5055 const auto rects = info->opaqueRegion(); 5056 QRegion new_opaque_region; 5057 for (const auto &r : rects) { 5058 new_opaque_region += Xcb::fromXNative(QRect(r.pos.x, r.pos.y, r.size.width, r.size.height)).toRect(); 5059 } 5060 opaque_region = new_opaque_region; 5061 } 5062 5063 QList<QRectF> X11Window::shapeRegion() const 5064 { 5065 if (m_shapeRegionIsValid) { 5066 return m_shapeRegion; 5067 } 5068 5069 const QRectF bufferGeometry = this->bufferGeometry(); 5070 5071 if (is_shape) { 5072 auto cookie = xcb_shape_get_rectangles_unchecked(kwinApp()->x11Connection(), frameId(), XCB_SHAPE_SK_BOUNDING); 5073 UniqueCPtr<xcb_shape_get_rectangles_reply_t> reply(xcb_shape_get_rectangles_reply(kwinApp()->x11Connection(), cookie, nullptr)); 5074 if (reply) { 5075 m_shapeRegion.clear(); 5076 const xcb_rectangle_t *rects = xcb_shape_get_rectangles_rectangles(reply.get()); 5077 const int rectCount = xcb_shape_get_rectangles_rectangles_length(reply.get()); 5078 for (int i = 0; i < rectCount; ++i) { 5079 QRectF region = Xcb::fromXNative(QRect(rects[i].x, rects[i].y, rects[i].width, rects[i].height)).toAlignedRect(); 5080 // make sure the shape is sane (X is async, maybe even XShape is broken) 5081 region = region.intersected(QRectF(QPointF(0, 0), bufferGeometry.size())); 5082 5083 m_shapeRegion += region; 5084 } 5085 } else { 5086 m_shapeRegion.clear(); 5087 } 5088 } else { 5089 m_shapeRegion = {QRectF(0, 0, bufferGeometry.width(), bufferGeometry.height())}; 5090 } 5091 5092 m_shapeRegionIsValid = true; 5093 return m_shapeRegion; 5094 } 5095 5096 void X11Window::discardShapeRegion() 5097 { 5098 m_shapeRegionIsValid = false; 5099 m_shapeRegion.clear(); 5100 } 5101 5102 Xcb::Property X11Window::fetchWmClientLeader() const 5103 { 5104 return Xcb::Property(false, window(), atoms->wm_client_leader, XCB_ATOM_WINDOW, 0, 10000); 5105 } 5106 5107 void X11Window::readWmClientLeader(Xcb::Property &prop) 5108 { 5109 m_wmClientLeader = prop.value<xcb_window_t>(window()); 5110 } 5111 5112 void X11Window::getWmClientLeader() 5113 { 5114 if (isUnmanaged()) { 5115 return; 5116 } 5117 auto prop = fetchWmClientLeader(); 5118 readWmClientLeader(prop); 5119 } 5120 5121 int X11Window::desktopId() const 5122 { 5123 return m_desktops.isEmpty() ? -1 : m_desktops.last()->x11DesktopNumber(); 5124 } 5125 5126 /** 5127 * Returns sessionId for this window, 5128 * taken either from its window or from the leader window. 5129 */ 5130 QByteArray X11Window::sessionId() const 5131 { 5132 QByteArray result = Xcb::StringProperty(window(), atoms->sm_client_id); 5133 if (result.isEmpty() && m_wmClientLeader && m_wmClientLeader != window()) { 5134 result = Xcb::StringProperty(m_wmClientLeader, atoms->sm_client_id); 5135 } 5136 return result; 5137 } 5138 5139 /** 5140 * Returns command property for this window, 5141 * taken either from its window or from the leader window. 5142 */ 5143 QString X11Window::wmCommand() 5144 { 5145 QByteArray result = Xcb::StringProperty(window(), XCB_ATOM_WM_COMMAND); 5146 if (result.isEmpty() && m_wmClientLeader && m_wmClientLeader != window()) { 5147 result = Xcb::StringProperty(m_wmClientLeader, XCB_ATOM_WM_COMMAND); 5148 } 5149 result.replace(0, ' '); 5150 return result; 5151 } 5152 5153 /** 5154 * Returns client leader window for this client. 5155 * Returns the client window itself if no leader window is defined. 5156 */ 5157 xcb_window_t X11Window::wmClientLeader() const 5158 { 5159 if (m_wmClientLeader != XCB_WINDOW_NONE) { 5160 return m_wmClientLeader; 5161 } 5162 return window(); 5163 } 5164 5165 void X11Window::getWmClientMachine() 5166 { 5167 clientMachine()->resolve(window(), wmClientLeader()); 5168 } 5169 5170 Xcb::Property X11Window::fetchSkipCloseAnimation() const 5171 { 5172 return Xcb::Property(false, window(), atoms->kde_skip_close_animation, XCB_ATOM_CARDINAL, 0, 1); 5173 } 5174 5175 void X11Window::readSkipCloseAnimation(Xcb::Property &property) 5176 { 5177 setSkipCloseAnimation(property.toBool()); 5178 } 5179 5180 void X11Window::getSkipCloseAnimation() 5181 { 5182 Xcb::Property property = fetchSkipCloseAnimation(); 5183 readSkipCloseAnimation(property); 5184 } 5185 5186 } // namespace 5187 5188 #include "moc_x11window.cpp"