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