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