1 /*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6 SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
7
8 SPDX-License-Identifier: GPL-2.0-or-later
9 */
10 // own
11 #include "x11client.h"
12 // kwin
13 #include "abstract_output.h"
14 #ifdef KWIN_BUILD_ACTIVITIES
15 #include "activities.h"
16 #endif
17 #include "atoms.h"
18 #include "client_machine.h"
19 #include "composite.h"
20 #include "cursor.h"
21 #include "deleted.h"
22 #include "effects.h"
23 #include "focuschain.h"
24 #include "geometrytip.h"
25 #include "group.h"
26 #include "netinfo.h"
27 #include "platform.h"
28 #include "screenedge.h"
29 #include "shadow.h"
30 #include "surfaceitem_x11.h"
31 #include "virtualdesktops.h"
32 #include "workspace.h"
33 #include "decorations/decorationbridge.h"
34 #include "decorations/decoratedclient.h"
35 #include <KDecoration2/Decoration>
36 #include <KDecoration2/DecoratedClient>
37 // KDE
38 #include <KLocalizedString>
39 #include <KStartupInfo>
40 #include <KWindowSystem>
41 #include <KColorScheme>
42 // Qt
43 #include <QApplication>
44 #include <QDebug>
45 #include <QDir>
46 #include <QFile>
47 #include <QFileInfo>
48 #include <QMouseEvent>
49 #include <QProcess>
50 // xcb
51 #include <xcb/xcb_icccm.h>
52 // system
53 #include <unistd.h>
54 // c++
55 #include <csignal>
56
57 // Put all externs before the namespace statement to allow the linker
58 // to resolve them properly
59
60 namespace KWin
61 {
62
63 const long ClientWinMask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE |
64 XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
65 XCB_EVENT_MASK_KEYMAP_STATE |
66 XCB_EVENT_MASK_BUTTON_MOTION |
67 XCB_EVENT_MASK_POINTER_MOTION | // need this, too!
68 XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
69 XCB_EVENT_MASK_FOCUS_CHANGE |
70 XCB_EVENT_MASK_EXPOSURE |
71 XCB_EVENT_MASK_STRUCTURE_NOTIFY |
72 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
73
74 // window types that are supported as normal windows (i.e. KWin actually manages them)
75 const NET::WindowTypes SUPPORTED_MANAGED_WINDOW_TYPES_MASK = NET::NormalMask | NET::DesktopMask | NET::DockMask
76 | NET::ToolbarMask | NET::MenuMask | NET::DialogMask /*| NET::OverrideMask*/ | NET::TopMenuMask
77 | NET::UtilityMask | NET::SplashMask | NET::NotificationMask | NET::OnScreenDisplayMask
78 | NET::CriticalNotificationMask;
79
X11DecorationRenderer(Decoration::DecoratedClientImpl * client)80 X11DecorationRenderer::X11DecorationRenderer(Decoration::DecoratedClientImpl *client)
81 : DecorationRenderer(client)
82 , m_scheduleTimer(new QTimer(this))
83 , m_gc(XCB_NONE)
84 {
85 // Delay any rendering to end of event cycle to catch multiple updates per cycle.
86 m_scheduleTimer->setSingleShot(true);
87 m_scheduleTimer->setInterval(0);
88 connect(m_scheduleTimer, &QTimer::timeout, this, &X11DecorationRenderer::update);
89 connect(this, &X11DecorationRenderer::damaged, m_scheduleTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
90 }
91
~X11DecorationRenderer()92 X11DecorationRenderer::~X11DecorationRenderer()
93 {
94 if (m_gc != XCB_NONE) {
95 xcb_free_gc(connection(), m_gc);
96 }
97 }
98
update()99 void X11DecorationRenderer::update()
100 {
101 if (!damage().isEmpty()) {
102 render(damage());
103 resetDamage();
104 }
105 }
106
render(const QRegion & region)107 void X11DecorationRenderer::render(const QRegion ®ion)
108 {
109 if (!client()) {
110 return;
111 }
112 xcb_connection_t *c = kwinApp()->x11Connection();
113 if (m_gc == XCB_NONE) {
114 m_gc = xcb_generate_id(c);
115 xcb_create_gc(c, m_gc, client()->client()->frameId(), 0, nullptr);
116 }
117
118 QRect left, top, right, bottom;
119 client()->client()->layoutDecorationRects(left, top, right, bottom);
120
121 const QRect geometry = region.boundingRect();
122 left = left.intersected(geometry);
123 top = top.intersected(geometry);
124 right = right.intersected(geometry);
125 bottom = bottom.intersected(geometry);
126
127 auto renderPart = [this, c](const QRect &geo) {
128 if (!geo.isValid()) {
129 return;
130 }
131 QImage image = renderToImage(geo);
132 xcb_put_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, client()->client()->frameId(), m_gc,
133 image.width(), image.height(), geo.x(), geo.y(), 0, client()->client()->depth(),
134 image.sizeInBytes(), image.constBits());
135 };
136 renderPart(left);
137 renderPart(top);
138 renderPart(right);
139 renderPart(bottom);
140
141 xcb_flush(c);
142 resetImageSizesDirty();
143 }
144
145 // Creating a client:
146 // - only by calling Workspace::createClient()
147 // - it creates a new client and calls manage() for it
148 //
149 // Destroying a client:
150 // - destroyClient() - only when the window itself has been destroyed
151 // - releaseWindow() - the window is kept, only the client itself is destroyed
152
153 /**
154 * \class Client x11client.h
155 * \brief The Client class encapsulates a window decoration frame.
156 */
157
158 /**
159 * This ctor is "dumb" - it only initializes data. All the real initialization
160 * is done in manage().
161 */
X11Client()162 X11Client::X11Client()
163 : AbstractClient()
164 , m_client()
165 , m_wrapper()
166 , m_frame()
167 , m_activityUpdatesBlocked(false)
168 , m_blockedActivityUpdatesRequireTransients(false)
169 , m_moveResizeGrabWindow()
170 , move_resize_has_keyboard_grab(false)
171 , m_managed(false)
172 , m_transientForId(XCB_WINDOW_NONE)
173 , m_originalTransientForId(XCB_WINDOW_NONE)
174 , shade_below(nullptr)
175 , m_motif(atoms->motif_wm_hints)
176 , blocks_compositing(false)
177 , m_colormap(XCB_COLORMAP_NONE)
178 , in_group(nullptr)
179 , ping_timer(nullptr)
180 , m_killHelperPID(0)
181 , m_pingTimestamp(XCB_TIME_CURRENT_TIME)
182 , m_userTime(XCB_TIME_CURRENT_TIME) // Not known yet
183 , allowed_actions()
184 , shade_geometry_change(false)
185 , sm_stacking_order(-1)
186 , activitiesDefined(false)
187 , sessionActivityOverride(false)
188 , needsXWindowMove(false)
189 , m_decoInputExtent()
190 , m_focusOutTimer(nullptr)
191 {
192 // TODO: Do all as initialization
193 m_syncRequest.counter = m_syncRequest.alarm = XCB_NONE;
194 m_syncRequest.timeout = m_syncRequest.failsafeTimeout = nullptr;
195 m_syncRequest.lastTimestamp = xTime();
196 m_syncRequest.isPending = false;
197
198 // Set the initial mapping state
199 mapping_state = Withdrawn;
200
201 info = nullptr;
202
203 m_fullscreenMode = FullScreenNone;
204 hidden = false;
205 noborder = false;
206 app_noborder = false;
207 ignore_focus_stealing = false;
208 check_active_modal = false;
209
210 max_mode = MaximizeRestore;
211
212 connect(clientMachine(), &ClientMachine::localhostChanged, this, &X11Client::updateCaption);
213 connect(options, &Options::configChanged, this, &X11Client::updateMouseGrab);
214 connect(options, &Options::condensedTitleChanged, this, &X11Client::updateCaption);
215
216 connect(this, &X11Client::moveResizeCursorChanged, this, [this] (CursorShape cursor) {
217 xcb_cursor_t nativeCursor = Cursors::self()->mouse()->x11Cursor(cursor);
218 m_frame.defineCursor(nativeCursor);
219 if (m_decoInputExtent.isValid())
220 m_decoInputExtent.defineCursor(nativeCursor);
221 if (isInteractiveMoveResize()) {
222 // changing window attributes doesn't change cursor if there's pointer grab active
223 xcb_change_active_pointer_grab(connection(), nativeCursor, xTime(),
224 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);
225 }
226 });
227
228 // SELI TODO: Initialize xsizehints??
229 }
230
231 /**
232 * "Dumb" destructor.
233 */
~X11Client()234 X11Client::~X11Client()
235 {
236 if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive
237 ::kill(m_killHelperPID, SIGTERM);
238 m_killHelperPID = 0;
239 }
240 if (m_syncRequest.alarm != XCB_NONE) {
241 xcb_sync_destroy_alarm(connection(), m_syncRequest.alarm);
242 }
243 Q_ASSERT(!isInteractiveMoveResize());
244 Q_ASSERT(m_client == XCB_WINDOW_NONE);
245 Q_ASSERT(m_wrapper == XCB_WINDOW_NONE);
246 Q_ASSERT(m_frame == XCB_WINDOW_NONE);
247 Q_ASSERT(!check_active_modal);
248 }
249
250 // Use destroyClient() or releaseWindow(), Client instances cannot be deleted directly
deleteClient(X11Client * c)251 void X11Client::deleteClient(X11Client *c)
252 {
253 delete c;
254 }
255
256 /**
257 * Releases the window. The client has done its job and the window is still existing.
258 */
releaseWindow(bool on_shutdown)259 void X11Client::releaseWindow(bool on_shutdown)
260 {
261 markAsZombie();
262 cleanTabBox();
263 Deleted* del = nullptr;
264 if (!on_shutdown) {
265 del = Deleted::create(this);
266 }
267 if (isInteractiveMoveResize())
268 Q_EMIT clientFinishUserMovedResized(this);
269 Q_EMIT windowClosed(this, del);
270 finishCompositing();
271 RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules
272 StackingUpdatesBlocker blocker(workspace());
273 if (isInteractiveMoveResize())
274 leaveInteractiveMoveResize();
275 finishWindowRules();
276 blockGeometryUpdates();
277 if (isOnCurrentDesktop() && isShown(true))
278 addWorkspaceRepaint(visibleGeometry());
279 // Grab X during the release to make removing of properties, setting to withdrawn state
280 // and repareting to root an atomic operation (https://lists.kde.org/?l=kde-devel&m=116448102901184&w=2)
281 grabXServer();
282 exportMappingState(XCB_ICCCM_WM_STATE_WITHDRAWN);
283 setModal(false); // Otherwise its mainwindow wouldn't get focus
284 hidden = true; // So that it's not considered visible anymore (can't use hideClient(), it would set flags)
285 if (!on_shutdown)
286 workspace()->clientHidden(this);
287 m_frame.unmap(); // Destroying decoration would cause ugly visual effect
288 destroyDecoration();
289 cleanGrouping();
290 workspace()->removeX11Client(this);
291 if (!on_shutdown) {
292 // Only when the window is being unmapped, not when closing down KWin (NETWM sections 5.5,5.7)
293 info->setDesktop(0);
294 info->setState(NET::States(), info->state()); // Reset all state flags
295 }
296 xcb_connection_t *c = connection();
297 m_client.deleteProperty(atoms->kde_net_wm_user_creation_time);
298 m_client.deleteProperty(atoms->net_frame_extents);
299 m_client.deleteProperty(atoms->kde_net_wm_frame_strut);
300 m_client.reparent(rootWindow(), m_bufferGeometry.x(), m_bufferGeometry.y());
301 xcb_change_save_set(c, XCB_SET_MODE_DELETE, m_client);
302 m_client.selectInput(XCB_EVENT_MASK_NO_EVENT);
303 if (on_shutdown)
304 // Map the window, so it can be found after another WM is started
305 m_client.map();
306 // TODO: Preserve minimized, shaded etc. state?
307 else // Make sure it's not mapped if the app unmapped it (#65279). The app
308 // may do map+unmap before we initially map the window by calling rawShow() from manage().
309 m_client.unmap();
310 m_client.reset();
311 m_wrapper.reset();
312 m_frame.reset();
313 unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry
314 if (!on_shutdown) {
315 disownDataPassedToDeleted();
316 del->unrefWindow();
317 }
318 deleteClient(this);
319 ungrabXServer();
320 }
321
322 /**
323 * Like releaseWindow(), but this one is called when the window has been already destroyed
324 * (E.g. The application closed it)
325 */
destroyClient()326 void X11Client::destroyClient()
327 {
328 markAsZombie();
329 cleanTabBox();
330 Deleted* del = Deleted::create(this);
331 if (isInteractiveMoveResize())
332 Q_EMIT clientFinishUserMovedResized(this);
333 Q_EMIT windowClosed(this, del);
334 finishCompositing(ReleaseReason::Destroyed);
335 RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules
336 StackingUpdatesBlocker blocker(workspace());
337 if (isInteractiveMoveResize())
338 leaveInteractiveMoveResize();
339 finishWindowRules();
340 blockGeometryUpdates();
341 if (isOnCurrentDesktop() && isShown(true))
342 addWorkspaceRepaint(visibleGeometry());
343 setModal(false);
344 hidden = true; // So that it's not considered visible anymore
345 workspace()->clientHidden(this);
346 destroyDecoration();
347 cleanGrouping();
348 workspace()->removeX11Client(this);
349 m_client.reset(); // invalidate
350 m_wrapper.reset();
351 m_frame.reset();
352 unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry
353 disownDataPassedToDeleted();
354 del->unrefWindow();
355 deleteClient(this);
356 }
357
358 /**
359 * Manages the clients. This means handling the very first maprequest:
360 * reparenting, initial geometry, initial state, placement, etc.
361 * Returns false if KWin is not going to manage this window.
362 */
manage(xcb_window_t w,bool isMapped)363 bool X11Client::manage(xcb_window_t w, bool isMapped)
364 {
365 StackingUpdatesBlocker stacking_blocker(workspace());
366
367 Xcb::WindowAttributes attr(w);
368 Xcb::WindowGeometry windowGeometry(w);
369 if (attr.isNull() || windowGeometry.isNull()) {
370 return false;
371 }
372
373 // From this place on, manage() must not return false
374 blockGeometryUpdates();
375
376 embedClient(w, attr->visual, attr->colormap, windowGeometry->depth);
377
378 m_visual = attr->visual;
379 bit_depth = windowGeometry->depth;
380
381 // SELI TODO: Order all these things in some sane manner
382
383 const NET::Properties properties =
384 NET::WMDesktop |
385 NET::WMState |
386 NET::WMWindowType |
387 NET::WMStrut |
388 NET::WMName |
389 NET::WMIconGeometry |
390 NET::WMIcon |
391 NET::WMPid |
392 NET::WMIconName;
393 const NET::Properties2 properties2 =
394 NET::WM2BlockCompositing |
395 NET::WM2WindowClass |
396 NET::WM2WindowRole |
397 NET::WM2UserTime |
398 NET::WM2StartupId |
399 NET::WM2ExtendedStrut |
400 NET::WM2Opacity |
401 NET::WM2FullscreenMonitors |
402 NET::WM2GroupLeader |
403 NET::WM2Urgency |
404 NET::WM2Input |
405 NET::WM2Protocols |
406 NET::WM2InitialMappingState |
407 NET::WM2IconPixmap |
408 NET::WM2OpaqueRegion |
409 NET::WM2DesktopFileName |
410 NET::WM2GTKFrameExtents;
411
412 auto wmClientLeaderCookie = fetchWmClientLeader();
413 auto skipCloseAnimationCookie = fetchSkipCloseAnimation();
414 auto showOnScreenEdgeCookie = fetchShowOnScreenEdge();
415 auto colorSchemeCookie = fetchPreferredColorScheme();
416 auto firstInTabBoxCookie = fetchFirstInTabBox();
417 auto transientCookie = fetchTransient();
418 auto activitiesCookie = fetchActivities();
419 auto applicationMenuServiceNameCookie = fetchApplicationMenuServiceName();
420 auto applicationMenuObjectPathCookie = fetchApplicationMenuObjectPath();
421
422 m_geometryHints.init(window());
423 m_motif.init(window());
424 info = new WinInfo(this, m_client, rootWindow(), properties, properties2);
425
426 if (isDesktop() && bit_depth == 32) {
427 // force desktop windows to be opaque. It's a desktop after all, there is no window below
428 bit_depth = 24;
429 }
430
431 // If it's already mapped, ignore hint
432 bool init_minimize = !isMapped && (info->initialMappingState() == NET::Iconic);
433
434 m_colormap = attr->colormap;
435
436 getResourceClass();
437 readWmClientLeader(wmClientLeaderCookie);
438 getWmClientMachine();
439 getSyncCounter();
440 // First only read the caption text, so that setupWindowRules() can use it for matching,
441 // and only then really set the caption using setCaption(), which checks for duplicates etc.
442 // and also relies on rules already existing
443 cap_normal = readName();
444 setupWindowRules(false);
445 setCaption(cap_normal, true);
446
447 connect(this, &X11Client::windowClassChanged, this, &X11Client::evaluateWindowRules);
448
449 if (Xcb::Extensions::self()->isShapeAvailable())
450 xcb_shape_select_input(connection(), window(), true);
451 detectShape(window());
452 detectNoBorder();
453 fetchIconicName();
454 setClientFrameExtents(info->gtkFrameExtents());
455
456 // Needs to be done before readTransient() because of reading the group
457 checkGroup();
458 updateUrgency();
459 updateAllowedActions(); // Group affects isMinimizable()
460
461 setModal((info->state() & NET::Modal) != 0); // Needs to be valid before handling groups
462 readTransientProperty(transientCookie);
463 setDesktopFileName(rules()->checkDesktopFile(QByteArray(info->desktopFileName()), true).toUtf8());
464 getIcons();
465 connect(this, &X11Client::desktopFileNameChanged, this, &X11Client::getIcons);
466
467 m_geometryHints.read();
468 getMotifHints();
469 getWmOpaqueRegion();
470 readSkipCloseAnimation(skipCloseAnimationCookie);
471
472 // TODO: Try to obey all state information from info->state()
473
474 setOriginalSkipTaskbar((info->state() & NET::SkipTaskbar) != 0);
475 setSkipPager((info->state() & NET::SkipPager) != 0);
476 setSkipSwitcher((info->state() & NET::SkipSwitcher) != 0);
477 readFirstInTabBox(firstInTabBoxCookie);
478
479 setupCompositing();
480
481 KStartupInfoId asn_id;
482 KStartupInfoData asn_data;
483 bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data);
484
485 // Make sure that the input window is created before we update the stacking order
486 updateInputWindow();
487 updateLayer();
488
489 SessionInfo* session = workspace()->takeSessionInfo(this);
490 if (session) {
491 init_minimize = session->minimized;
492 noborder = session->noBorder;
493 }
494
495 setShortcut(rules()->checkShortcut(session ? session->shortcut : QString(), true));
496
497 init_minimize = rules()->checkMinimize(init_minimize, !isMapped);
498 noborder = rules()->checkNoBorder(noborder, !isMapped);
499
500 readActivities(activitiesCookie);
501
502 // Initial desktop placement
503 std::optional<QVector<VirtualDesktop *>> initialDesktops;
504 if (session) {
505 if (session->onAllDesktops) {
506 initialDesktops = QVector<VirtualDesktop *>{};
507 } else {
508 VirtualDesktop *desktop = VirtualDesktopManager::self()->desktopForX11Id(session->desktop);
509 if (desktop) {
510 initialDesktops = QVector<VirtualDesktop *>{desktop};
511 }
512 }
513 setOnActivities(session->activities);
514 } else {
515 // If this window is transient, ensure that it is opened on the
516 // same window as its parent. this is necessary when an application
517 // starts up on a different desktop than is currently displayed
518 if (isTransient()) {
519 auto mainclients = mainClients();
520 bool on_current = false;
521 bool on_all = false;
522 AbstractClient* maincl = nullptr;
523 // This is slightly duplicated from Placement::placeOnMainWindow()
524 for (auto it = mainclients.constBegin();
525 it != mainclients.constEnd();
526 ++it) {
527 if (mainclients.count() > 1 && // A group-transient
528 (*it)->isSpecialWindow() && // Don't consider toolbars etc when placing
529 !(info->state() & NET::Modal)) // except when it's modal (blocks specials as well)
530 continue;
531 maincl = *it;
532 if ((*it)->isOnCurrentDesktop())
533 on_current = true;
534 if ((*it)->isOnAllDesktops())
535 on_all = true;
536 }
537 if (on_all) {
538 initialDesktops = QVector<VirtualDesktop *>{};
539 } else if (on_current) {
540 initialDesktops = QVector<VirtualDesktop *>{VirtualDesktopManager::self()->currentDesktop()};
541 } else if (maincl) {
542 initialDesktops = maincl->desktops();
543 }
544
545 if (maincl)
546 setOnActivities(maincl->activities());
547 } else { // a transient shall appear on its leader and not drag that around
548 int desktopId = 0;
549 if (info->desktop()) {
550 desktopId = info->desktop(); // Window had the initial desktop property, force it
551 }
552 if (desktopId == 0 && asn_valid && asn_data.desktop() != 0) {
553 desktopId = asn_data.desktop();
554 }
555 if (desktopId) {
556 if (desktopId == NET::OnAllDesktops) {
557 initialDesktops = QVector<VirtualDesktop *>{};
558 } else {
559 VirtualDesktop *desktop = VirtualDesktopManager::self()->desktopForX11Id(desktopId);
560 if (desktop) {
561 initialDesktops = QVector<VirtualDesktop *>{desktop};
562 }
563 }
564 }
565 }
566 #ifdef KWIN_BUILD_ACTIVITIES
567 if (Activities::self() && !isMapped && !skipTaskbar() && isNormalWindow() && !activitiesDefined) {
568 //a new, regular window, when we're not recovering from a crash,
569 //and it hasn't got an activity. let's try giving it the current one.
570 //TODO: decide whether to keep this before the 4.6 release
571 //TODO: if we are keeping it (at least as an option), replace noborder checking
572 //with a public API for setting windows to be on all activities.
573 //something like KWindowSystem::setOnAllActivities or
574 //KActivityConsumer::setOnAllActivities
575 setOnActivity(Activities::self()->current(), true);
576 }
577 #endif
578 }
579
580 // If initialDesktops has no value, it means that the client doesn't prefer any
581 // desktop so place it on the current virtual desktop.
582 if (!initialDesktops.has_value()) {
583 if (isDesktop()) {
584 initialDesktops = QVector<VirtualDesktop *>{};
585 } else {
586 initialDesktops = QVector<VirtualDesktop *>{VirtualDesktopManager::self()->currentDesktop()};
587 }
588 }
589 setDesktops(rules()->checkDesktops(*initialDesktops, !isMapped));
590 info->setDesktop(desktop());
591 workspace()->updateOnAllDesktopsOfTransients(this); // SELI TODO
592 //onAllDesktopsChange(); // Decoration doesn't exist here yet
593
594 QStringList activitiesList;
595 activitiesList = rules()->checkActivity(activitiesList, !isMapped);
596 if (!activitiesList.isEmpty())
597 setOnActivities(activitiesList);
598
599 QRect geom(windowGeometry.rect());
600 bool placementDone = false;
601
602 if (session)
603 geom = session->geometry;
604
605 QRect area;
606 bool partial_keep_in_area = isMapped || session;
607 if (isMapped || session) {
608 area = workspace()->clientArea(FullArea, this, geom.center());
609 checkOffscreenPosition(&geom, area);
610 } else {
611 AbstractOutput *output = nullptr;
612 if (asn_data.xinerama() != -1) {
613 output = kwinApp()->platform()->findOutput(asn_data.xinerama());
614 }
615 if (!output) {
616 output = workspace()->activeOutput();
617 }
618 output = rules()->checkOutput(output, !isMapped);
619 area = workspace()->clientArea(PlacementArea, this, output->geometry().center());
620 }
621
622 if (isDesktop())
623 // KWin doesn't manage desktop windows
624 placementDone = true;
625
626 bool usePosition = false;
627 if (isMapped || session || placementDone)
628 placementDone = true; // Use geometry
629 else if (isTransient() && !isUtility() && !isDialog() && !isSplash())
630 usePosition = true;
631 else if (isTransient() && !hasNETSupport())
632 usePosition = true;
633 else if (isDialog() && hasNETSupport()) {
634 // If the dialog is actually non-NETWM transient window, don't try to apply placement to it,
635 // it breaks with too many things (xmms, display)
636 if (mainClients().count() >= 1) {
637 #if 1
638 // #78082 - Ok, it seems there are after all some cases when an application has a good
639 // reason to specify a position for its dialog. Too bad other WMs have never bothered
640 // with placement for dialogs, so apps always specify positions for their dialogs,
641 // including such silly positions like always centered on the screen or under mouse.
642 // Using ignoring requested position in window-specific settings helps, and now
643 // there's also _NET_WM_FULL_PLACEMENT.
644 usePosition = true;
645 #else
646 ; // Force using placement policy
647 #endif
648 } else
649 usePosition = true;
650 } else if (isSplash())
651 ; // Force using placement policy
652 else
653 usePosition = true;
654 if (!rules()->checkIgnoreGeometry(!usePosition, true)) {
655 if (m_geometryHints.hasPosition()) {
656 placementDone = true;
657 // Disobey xinerama placement option for now (#70943)
658 area = workspace()->clientArea(PlacementArea, this, geom.center());
659 }
660 }
661
662 if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom()))
663 placementDone = false; // Weird, do not trust.
664
665 if (placementDone) {
666 QPoint position = geom.topLeft();
667 // Session contains the position of the frame geometry before gravitating.
668 if (!session) {
669 position = clientPosToFramePos(position);
670 }
671 move(position);
672 }
673
674 // Create client group if the window will have a decoration
675 bool dontKeepInArea = false;
676 setColorScheme(readPreferredColorScheme(colorSchemeCookie));
677
678 readApplicationMenuServiceName(applicationMenuServiceNameCookie);
679 readApplicationMenuObjectPath(applicationMenuObjectPathCookie);
680
681 updateDecoration(false); // Also gravitates
682 // TODO: Is CentralGravity right here, when resizing is done after gravitating?
683 const QSize constrainedClientSize = constrainClientSize(geom.size());
684 resize(rules()->checkSize(clientSizeToFrameSize(constrainedClientSize), !isMapped));
685
686 QPoint forced_pos = rules()->checkPosition(invalidPoint, !isMapped);
687 if (forced_pos != invalidPoint) {
688 move(forced_pos);
689 placementDone = true;
690 // Don't keep inside workarea if the window has specially configured position
691 partial_keep_in_area = true;
692 area = workspace()->clientArea(FullArea, this, geom.center());
693 }
694 if (!placementDone) {
695 // Placement needs to be after setting size
696 Placement::self()->place(this, area);
697 // The client may have been moved to another screen, update placement area.
698 area = workspace()->clientArea(PlacementArea, this);
699 dontKeepInArea = true;
700 placementDone = true;
701 }
702
703 // bugs #285967, #286146, #183694
704 // geometry() now includes the requested size and the decoration and is at the correct screen/position (hopefully)
705 // Maximization for oversized windows must happen NOW.
706 // If we effectively pass keepInArea(), the window will resizeWithChecks() - i.e. constrained
707 // to the combo of all screen MINUS all struts on the edges
708 // If only one screen struts, this will affect screens as a side-effect, the window is artificailly shrinked
709 // below the screen size and as result no more maximized what breaks KMainWindow's stupid width+1, height+1 hack
710 // TODO: get KMainWindow a correct state storage what will allow to store the restore size as well.
711
712 if (!session) { // has a better handling of this
713 setGeometryRestore(frameGeometry()); // Remember restore geometry
714 if (isMaximizable() && (width() >= area.width() || height() >= area.height())) {
715 // Window is too large for the screen, maximize in the
716 // directions necessary
717 const QSize ss = workspace()->clientArea(ScreenArea, this, area.center()).size();
718 const QRect fsa = workspace()->clientArea(FullArea, this, geom.center());
719 const QSize cs = clientSize();
720 int pseudo_max = ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) |
721 ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0);
722 if (width() >= area.width())
723 pseudo_max |= MaximizeHorizontal;
724 if (height() >= area.height())
725 pseudo_max |= MaximizeVertical;
726
727 // heuristics:
728 // if decorated client is smaller than the entire screen, the user might want to move it around (multiscreen)
729 // in this case, if the decorated client is bigger than the screen (+1), we don't take this as an
730 // attempt for maximization, but just constrain the size (the window simply wants to be bigger)
731 // NOTICE
732 // i intended a second check on cs < area.size() ("the managed client ("minus border") is smaller
733 // than the workspace") but gtk / gimp seems to store it's size including the decoration,
734 // thus a former maximized window wil become non-maximized
735 bool keepInFsArea = false;
736 if (width() < fsa.width() && (cs.width() > ss.width()+1)) {
737 pseudo_max &= ~MaximizeHorizontal;
738 keepInFsArea = true;
739 }
740 if (height() < fsa.height() && (cs.height() > ss.height()+1)) {
741 pseudo_max &= ~MaximizeVertical;
742 keepInFsArea = true;
743 }
744
745 if (pseudo_max != MaximizeRestore) {
746 maximize((MaximizeMode)pseudo_max);
747 // from now on, care about maxmode, since the maximization call will override mode for fix aspects
748 dontKeepInArea |= (max_mode == MaximizeFull);
749 QRect savedGeometry; // Use placement when unmaximizing ...
750 if (!(max_mode & MaximizeVertical)) {
751 savedGeometry.setY(y()); // ...but only for horizontal direction
752 savedGeometry.setHeight(height());
753 }
754 if (!(max_mode & MaximizeHorizontal)) {
755 savedGeometry.setX(x()); // ...but only for vertical direction
756 savedGeometry.setWidth(width());
757 }
758 setGeometryRestore(savedGeometry);
759 }
760 if (keepInFsArea)
761 keepInArea(fsa, partial_keep_in_area);
762 }
763 }
764
765 if ((!isSpecialWindow() || isToolbar()) && isMovable() && !dontKeepInArea)
766 keepInArea(area, partial_keep_in_area);
767
768 updateShape();
769
770 // CT: Extra check for stupid jdk 1.3.1. But should make sense in general
771 // if client has initial state set to Iconic and is transient with a parent
772 // window that is not Iconic, set init_state to Normal
773 if (init_minimize && isTransient()) {
774 auto mainclients = mainClients();
775 for (auto it = mainclients.constBegin();
776 it != mainclients.constEnd();
777 ++it)
778 if ((*it)->isShown(true))
779 init_minimize = false; // SELI TODO: Even e.g. for NET::Utility?
780 }
781 // If a dialog is shown for minimized window, minimize it too
782 if (!init_minimize && isTransient() && mainClients().count() > 0 &&
783 workspace()->sessionManager()->state() != SessionState::Saving) {
784 bool visible_parent = false;
785 // Use allMainClients(), to include also main clients of group transients
786 // that have been optimized out in X11Client::checkGroupTransients()
787 auto mainclients = allMainClients();
788 for (auto it = mainclients.constBegin();
789 it != mainclients.constEnd();
790 ++it)
791 if ((*it)->isShown(true))
792 visible_parent = true;
793 if (!visible_parent) {
794 init_minimize = true;
795 demandAttention();
796 }
797 }
798
799 if (init_minimize)
800 minimize(true); // No animation
801
802 // Other settings from the previous session
803 if (session) {
804 // Session restored windows are not considered to be new windows WRT rules,
805 // I.e. obey only forcing rules
806 setKeepAbove(session->keepAbove);
807 setKeepBelow(session->keepBelow);
808 setOriginalSkipTaskbar(session->skipTaskbar);
809 setSkipPager(session->skipPager);
810 setSkipSwitcher(session->skipSwitcher);
811 setShade(session->shaded ? ShadeNormal : ShadeNone);
812 setOpacity(session->opacity);
813 setGeometryRestore(session->restore);
814 if (session->maximized != MaximizeRestore) {
815 maximize(MaximizeMode(session->maximized));
816 }
817 if (session->fullscreen != FullScreenNone) {
818 setFullScreen(true, false);
819 setFullscreenGeometryRestore(session->fsrestore);
820 }
821 QRect checkedGeometryRestore = geometryRestore();
822 checkOffscreenPosition(&checkedGeometryRestore, area);
823 setGeometryRestore(checkedGeometryRestore);
824 QRect checkedFullscreenGeometryRestore = fullscreenGeometryRestore();
825 checkOffscreenPosition(&checkedFullscreenGeometryRestore, area);
826 setFullscreenGeometryRestore(checkedFullscreenGeometryRestore);
827 } else {
828 // Window may want to be maximized
829 // done after checking that the window isn't larger than the workarea, so that
830 // the restore geometry from the checks above takes precedence, and window
831 // isn't restored larger than the workarea
832 MaximizeMode maxmode = static_cast<MaximizeMode>(
833 ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) |
834 ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0));
835 MaximizeMode forced_maxmode = rules()->checkMaximize(maxmode, !isMapped);
836
837 // Either hints were set to maximize, or is forced to maximize,
838 // or is forced to non-maximize and hints were set to maximize
839 if (forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore)
840 maximize(forced_maxmode);
841
842 // Read other initial states
843 setShade(rules()->checkShade(info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped));
844 setKeepAbove(rules()->checkKeepAbove(info->state() & NET::KeepAbove, !isMapped));
845 setKeepBelow(rules()->checkKeepBelow(info->state() & NET::KeepBelow, !isMapped));
846 setOriginalSkipTaskbar(rules()->checkSkipTaskbar(info->state() & NET::SkipTaskbar, !isMapped));
847 setSkipPager(rules()->checkSkipPager(info->state() & NET::SkipPager, !isMapped));
848 setSkipSwitcher(rules()->checkSkipSwitcher(info->state() & NET::SkipSwitcher, !isMapped));
849 if (info->state() & NET::DemandsAttention)
850 demandAttention();
851 if (info->state() & NET::Modal)
852 setModal(true);
853 setOpacity(info->opacityF());
854
855 setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped), false);
856 }
857
858 updateAllowedActions(true);
859
860 // Set initial user time directly
861 m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : nullptr, asn_valid ? &asn_data : nullptr, session);
862 group()->updateUserTime(m_userTime); // And do what X11Client::updateUserTime() does
863
864 // This should avoid flicker, because real restacking is done
865 // only after manage() finishes because of blocking, but the window is shown sooner
866 m_frame.lower();
867 if (session && session->stackingOrder != -1) {
868 sm_stacking_order = session->stackingOrder;
869 workspace()->restoreSessionStackingOrder(this);
870 }
871
872 if (Compositor::compositing())
873 // Sending ConfigureNotify is done when setting mapping state below,
874 // Getting the first sync response means window is ready for compositing
875 sendSyncRequest();
876 else
877 ready_for_painting = true; // set to true in case compositing is turned on later. bug #160393
878
879 if (isShown(true)) {
880 bool allow;
881 if (session)
882 allow = session->active &&
883 (!workspace()->wasUserInteraction() || workspace()->activeClient() == nullptr ||
884 workspace()->activeClient()->isDesktop());
885 else
886 allow = workspace()->allowClientActivation(this, userTime(), false);
887
888 const bool isSessionSaving = workspace()->sessionManager()->state() == SessionState::Saving;
889
890 // If session saving, force showing new windows (i.e. "save file?" dialogs etc.)
891 // also force if activation is allowed
892 if( !isOnCurrentDesktop() && !isMapped && !session && ( allow || isSessionSaving ))
893 VirtualDesktopManager::self()->setCurrent( desktop());
894
895 // If the window is on an inactive activity during session saving, temporarily force it to show.
896 if( !isMapped && !session && isSessionSaving && !isOnCurrentActivity()) {
897 setSessionActivityOverride( true );
898 Q_FOREACH( AbstractClient* c, mainClients()) {
899 if (X11Client *mc = dynamic_cast<X11Client *>(c)) {
900 mc->setSessionActivityOverride(true);
901 }
902 }
903 }
904
905 if (isOnCurrentDesktop() && !isMapped && !allow && (!session || session->stackingOrder < 0))
906 workspace()->restackClientUnderActive(this);
907
908 updateVisibility();
909
910 if (!isMapped) {
911 if (allow && isOnCurrentDesktop()) {
912 if (!isSpecialWindow())
913 if (options->focusPolicyIsReasonable() && wantsTabFocus())
914 workspace()->requestFocus(this);
915 } else if (!session && !isSpecialWindow())
916 demandAttention();
917 }
918 } else
919 updateVisibility();
920 Q_ASSERT(mapping_state != Withdrawn);
921 m_managed = true;
922 blockGeometryUpdates(false);
923
924 if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) {
925 // No known user time, set something old
926 m_userTime = xTime() - 1000000;
927 if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) // Let's be paranoid
928 m_userTime = xTime() - 1000000 + 10;
929 }
930
931 //sendSyntheticConfigureNotify(); // Done when setting mapping state
932
933 delete session;
934
935 discardTemporaryRules();
936 applyWindowRules(); // Just in case
937 RuleBook::self()->discardUsed(this, false); // Remove ApplyNow rules
938 updateWindowRules(Rules::All); // Was blocked while !isManaged()
939
940 setBlockingCompositing(info->isBlockingCompositing());
941 readShowOnScreenEdge(showOnScreenEdgeCookie);
942
943 // Forward all opacity values to the frame in case there'll be other CM running.
944 connect(Compositor::self(), &Compositor::compositingToggled, this,
945 [this](bool active) {
946 if (active) {
947 return;
948 }
949 if (opacity() == 1.0) {
950 return;
951 }
952 NETWinInfo info(connection(), frameId(), rootWindow(), NET::Properties(), NET::Properties2());
953 info.setOpacityF(opacity());
954 }
955 );
956
957 // TODO: there's a small problem here - isManaged() depends on the mapping state,
958 // but this client is not yet in Workspace's client list at this point, will
959 // be only done in addClient()
960 Q_EMIT clientManaging(this);
961 return true;
962 }
963
964 // Called only from manage()
embedClient(xcb_window_t w,xcb_visualid_t visualid,xcb_colormap_t colormap,uint8_t depth)965 void X11Client::embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth)
966 {
967 Q_ASSERT(m_client == XCB_WINDOW_NONE);
968 Q_ASSERT(frameId() == XCB_WINDOW_NONE);
969 Q_ASSERT(m_wrapper == XCB_WINDOW_NONE);
970 m_client.reset(w, false);
971
972 const uint32_t zero_value = 0;
973
974 xcb_connection_t *conn = connection();
975
976 // We don't want the window to be destroyed when we quit
977 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, m_client);
978
979 m_client.selectInput(zero_value);
980 m_client.unmap();
981 m_client.setBorderWidth(zero_value);
982
983 // Note: These values must match the order in the xcb_cw_t enum
984 const uint32_t cw_values[] = {
985 0, // back_pixmap
986 0, // border_pixel
987 colormap, // colormap
988 Cursors::self()->mouse()->x11Cursor(Qt::ArrowCursor)
989 };
990
991 const uint32_t cw_mask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL |
992 XCB_CW_COLORMAP | XCB_CW_CURSOR;
993
994 const uint32_t common_event_mask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE |
995 XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
996 XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
997 XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION |
998 XCB_EVENT_MASK_KEYMAP_STATE |
999 XCB_EVENT_MASK_FOCUS_CHANGE |
1000 XCB_EVENT_MASK_EXPOSURE |
1001 XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
1002
1003 const uint32_t frame_event_mask = common_event_mask | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_VISIBILITY_CHANGE;
1004 const uint32_t wrapper_event_mask = common_event_mask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
1005
1006 const uint32_t client_event_mask = XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE |
1007 XCB_EVENT_MASK_COLOR_MAP_CHANGE |
1008 XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
1009 XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE;
1010
1011 // Create the frame window
1012 xcb_window_t frame = xcb_generate_id(conn);
1013 xcb_create_window(conn, depth, frame, rootWindow(), 0, 0, 1, 1, 0,
1014 XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values);
1015 m_frame.reset(frame);
1016
1017 setWindowHandles(m_client);
1018
1019 // Create the wrapper window
1020 xcb_window_t wrapperId = xcb_generate_id(conn);
1021 xcb_create_window(conn, depth, wrapperId, frame, 0, 0, 1, 1, 0,
1022 XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values);
1023 m_wrapper.reset(wrapperId);
1024
1025 m_client.reparent(m_wrapper);
1026
1027 // We could specify the event masks when we create the windows, but the original
1028 // Xlib code didn't. Let's preserve that behavior here for now so we don't end up
1029 // receiving any unexpected events from the wrapper creation or the reparenting.
1030 m_frame.selectInput(frame_event_mask);
1031 m_wrapper.selectInput(wrapper_event_mask);
1032 m_client.selectInput(client_event_mask);
1033
1034 updateMouseGrab();
1035 }
1036
updateInputWindow()1037 void X11Client::updateInputWindow()
1038 {
1039 if (!Xcb::Extensions::self()->isShapeInputAvailable())
1040 return;
1041
1042 QRegion region;
1043
1044 if (!noBorder() && isDecorated()) {
1045 const QMargins &r = decoration()->resizeOnlyBorders();
1046 const int left = r.left();
1047 const int top = r.top();
1048 const int right = r.right();
1049 const int bottom = r.bottom();
1050 if (left != 0 || top != 0 || right != 0 || bottom != 0) {
1051 region = QRegion(-left,
1052 -top,
1053 decoration()->size().width() + left + right,
1054 decoration()->size().height() + top + bottom);
1055 region = region.subtracted(decoration()->rect());
1056 }
1057 }
1058
1059 if (region.isEmpty()) {
1060 m_decoInputExtent.reset();
1061 return;
1062 }
1063
1064 QRect bounds = region.boundingRect();
1065 input_offset = bounds.topLeft();
1066
1067 // Move the bounding rect to screen coordinates
1068 bounds.translate(frameGeometry().topLeft());
1069
1070 // Move the region to input window coordinates
1071 region.translate(-input_offset);
1072
1073 if (!m_decoInputExtent.isValid()) {
1074 const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
1075 const uint32_t values[] = {true,
1076 XCB_EVENT_MASK_ENTER_WINDOW |
1077 XCB_EVENT_MASK_LEAVE_WINDOW |
1078 XCB_EVENT_MASK_BUTTON_PRESS |
1079 XCB_EVENT_MASK_BUTTON_RELEASE |
1080 XCB_EVENT_MASK_POINTER_MOTION
1081 };
1082 m_decoInputExtent.create(bounds, XCB_WINDOW_CLASS_INPUT_ONLY, mask, values);
1083 if (mapping_state == Mapped)
1084 m_decoInputExtent.map();
1085 } else {
1086 m_decoInputExtent.setGeometry(bounds);
1087 }
1088
1089 const QVector<xcb_rectangle_t> rects = Xcb::regionToRects(region);
1090 xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED,
1091 m_decoInputExtent, 0, 0, rects.count(), rects.constData());
1092 }
1093
updateDecoration(bool check_workspace_pos,bool force)1094 void X11Client::updateDecoration(bool check_workspace_pos, bool force)
1095 {
1096 if (!force &&
1097 ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder())))
1098 return;
1099 QRect oldgeom = frameGeometry();
1100 QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom());
1101 blockGeometryUpdates(true);
1102 if (force)
1103 destroyDecoration();
1104 if (!noBorder()) {
1105 createDecoration(oldgeom);
1106 } else
1107 destroyDecoration();
1108 updateShadow();
1109 if (check_workspace_pos)
1110 checkWorkspacePosition(oldgeom, oldClientGeom);
1111 updateInputWindow();
1112 blockGeometryUpdates(false);
1113 updateFrameExtents();
1114 }
1115
createDecoration(const QRect & oldgeom)1116 void X11Client::createDecoration(const QRect& oldgeom)
1117 {
1118 KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this);
1119 if (decoration) {
1120 QMetaObject::invokeMethod(decoration, QOverload<>::of(&KDecoration2::Decoration::update), Qt::QueuedConnection);
1121 connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::updateShadow);
1122 connect(decoration, &KDecoration2::Decoration::bordersChanged,
1123 this, &X11Client::updateDecorationInputShape);
1124 connect(decoration, &KDecoration2::Decoration::resizeOnlyBordersChanged,
1125 this, &X11Client::updateDecorationInputShape);
1126 connect(decoration, &KDecoration2::Decoration::resizeOnlyBordersChanged, this, &X11Client::updateInputWindow);
1127 connect(decoration, &KDecoration2::Decoration::bordersChanged, this,
1128 [this]() {
1129 updateFrameExtents();
1130 GeometryUpdatesBlocker blocker(this);
1131 // TODO: this is obviously idempotent
1132 // calculateGravitation(true) would have to operate on the old border sizes
1133 // move(calculateGravitation(true));
1134 // move(calculateGravitation(false));
1135 QRect oldgeom = frameGeometry();
1136 resize(adjustedSize());
1137 if (!isShade())
1138 checkWorkspacePosition(oldgeom);
1139 Q_EMIT geometryShapeChanged(this, oldgeom);
1140 }
1141 );
1142 connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::widthChanged, this, &X11Client::updateInputWindow);
1143 connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::heightChanged, this, &X11Client::updateInputWindow);
1144 connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::sizeChanged,
1145 this, &X11Client::updateDecorationInputShape);
1146 }
1147 setDecoration(decoration);
1148
1149 move(calculateGravitation(false));
1150 resize(adjustedSize());
1151 maybeCreateX11DecorationRenderer();
1152 Q_EMIT geometryShapeChanged(this, oldgeom);
1153 }
1154
destroyDecoration()1155 void X11Client::destroyDecoration()
1156 {
1157 QRect oldgeom = frameGeometry();
1158 if (isDecorated()) {
1159 QPoint grav = calculateGravitation(true);
1160 setDecoration(nullptr);
1161 maybeDestroyX11DecorationRenderer();
1162 resize(adjustedSize());
1163 move(grav);
1164 if (!isZombie()) {
1165 Q_EMIT geometryShapeChanged(this, oldgeom);
1166 }
1167 }
1168 m_decoInputExtent.reset();
1169 }
1170
maybeCreateX11DecorationRenderer()1171 void X11Client::maybeCreateX11DecorationRenderer()
1172 {
1173 if (kwinApp()->operationMode() != Application::OperationModeX11) {
1174 return;
1175 }
1176 if (!Compositor::compositing() && decoratedClient()) {
1177 m_decorationRenderer.reset(new X11DecorationRenderer(decoratedClient()));
1178 decoration()->update();
1179 }
1180 }
1181
maybeDestroyX11DecorationRenderer()1182 void X11Client::maybeDestroyX11DecorationRenderer()
1183 {
1184 m_decorationRenderer.reset();
1185 }
1186
detectNoBorder()1187 void X11Client::detectNoBorder()
1188 {
1189 if (shape()) {
1190 noborder = true;
1191 app_noborder = true;
1192 return;
1193 }
1194 switch(windowType()) {
1195 case NET::Desktop :
1196 case NET::Dock :
1197 case NET::TopMenu :
1198 case NET::Splash :
1199 case NET::Notification :
1200 case NET::OnScreenDisplay :
1201 case NET::CriticalNotification :
1202 noborder = true;
1203 app_noborder = true;
1204 break;
1205 case NET::Unknown :
1206 case NET::Normal :
1207 case NET::Toolbar :
1208 case NET::Menu :
1209 case NET::Dialog :
1210 case NET::Utility :
1211 noborder = false;
1212 break;
1213 default:
1214 abort();
1215 }
1216 // NET::Override is some strange beast without clear definition, usually
1217 // just meaning "noborder", so let's treat it only as such flag, and ignore it as
1218 // a window type otherwise (SUPPORTED_WINDOW_TYPES_MASK doesn't include it)
1219 if (info->windowType(NET::OverrideMask) == NET::Override) {
1220 noborder = true;
1221 app_noborder = true;
1222 }
1223 }
1224
updateFrameExtents()1225 void X11Client::updateFrameExtents()
1226 {
1227 NETStrut strut;
1228 strut.left = borderLeft();
1229 strut.right = borderRight();
1230 strut.top = borderTop();
1231 strut.bottom = borderBottom();
1232 info->setFrameExtents(strut);
1233 }
1234
setClientFrameExtents(const NETStrut & strut)1235 void X11Client::setClientFrameExtents(const NETStrut &strut)
1236 {
1237 const QMargins clientFrameExtents(strut.left, strut.top, strut.right, strut.bottom);
1238 if (m_clientFrameExtents == clientFrameExtents) {
1239 return;
1240 }
1241
1242 const bool wasClientSideDecorated = isClientSideDecorated();
1243 m_clientFrameExtents = clientFrameExtents;
1244
1245 // We should resize the client when its custom frame extents are changed so
1246 // the logical bounds remain the same. This however means that we will send
1247 // several configure requests to the application upon restoring it from the
1248 // maximized or fullscreen state. Notice that a client-side decorated client
1249 // cannot be shaded, therefore it's okay not to use the adjusted size here.
1250 moveResize(moveResizeGeometry());
1251
1252 if (wasClientSideDecorated != isClientSideDecorated()) {
1253 Q_EMIT clientSideDecoratedChanged();
1254 }
1255
1256 // This will invalidate the window quads cache.
1257 Q_EMIT geometryShapeChanged(this, frameGeometry());
1258 }
1259
1260 /**
1261 * Resizes the decoration, and makes sure the decoration widget gets resize event
1262 * even if the size hasn't changed. This is needed to make sure the decoration
1263 * re-layouts (e.g. when maximization state changes,
1264 * the decoration may alter some borders, but the actual size
1265 * of the decoration stays the same).
1266 */
resizeDecoration()1267 void X11Client::resizeDecoration()
1268 {
1269 triggerDecorationRepaint();
1270 updateInputWindow();
1271 }
1272
userNoBorder() const1273 bool X11Client::userNoBorder() const
1274 {
1275 return noborder;
1276 }
1277
isFullScreenable() const1278 bool X11Client::isFullScreenable() const
1279 {
1280 if (!rules()->checkFullScreen(true)) {
1281 return false;
1282 }
1283 if (rules()->checkStrictGeometry(true)) {
1284 // check geometry constraints (rule to obey is set)
1285 const QRect fullScreenArea = workspace()->clientArea(FullScreenArea, this);
1286 const QSize constrainedClientSize = constrainClientSize(fullScreenArea.size());
1287 if (rules()->checkSize(constrainedClientSize) != fullScreenArea.size()) {
1288 return false; // the app wouldn't fit exactly fullscreen geometry due to its strict geometry requirements
1289 }
1290 }
1291 // don't check size constrains - some apps request fullscreen despite requesting fixed size
1292 return !isSpecialWindow(); // also better disallow only weird types to go fullscreen
1293 }
1294
noBorder() const1295 bool X11Client::noBorder() const
1296 {
1297 return userNoBorder() || isFullScreen();
1298 }
1299
userCanSetNoBorder() const1300 bool X11Client::userCanSetNoBorder() const
1301 {
1302 // Client-side decorations and server-side decorations are mutually exclusive.
1303 if (isClientSideDecorated()) {
1304 return false;
1305 }
1306
1307 return !isFullScreen() && !isShade();
1308 }
1309
setNoBorder(bool set)1310 void X11Client::setNoBorder(bool set)
1311 {
1312 if (!userCanSetNoBorder())
1313 return;
1314 set = rules()->checkNoBorder(set);
1315 if (noborder == set)
1316 return;
1317 noborder = set;
1318 updateDecoration(true, false);
1319 updateWindowRules(Rules::NoBorder);
1320 }
1321
checkNoBorder()1322 void X11Client::checkNoBorder()
1323 {
1324 setNoBorder(app_noborder);
1325 }
1326
updateShape()1327 void X11Client::updateShape()
1328 {
1329 if (shape()) {
1330 // Workaround for #19644 - Shaped windows shouldn't have decoration
1331 if (!app_noborder) {
1332 // Only when shape is detected for the first time, still let the user to override
1333 app_noborder = true;
1334 noborder = rules()->checkNoBorder(true);
1335 updateDecoration(true);
1336 }
1337 if (noBorder()) {
1338 xcb_shape_combine(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_SHAPE_SK_BOUNDING,
1339 frameId(), clientPos().x(), clientPos().y(), window());
1340 }
1341 } else if (app_noborder) {
1342 xcb_shape_mask(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, frameId(), 0, 0, XCB_PIXMAP_NONE);
1343 detectNoBorder();
1344 app_noborder = noborder;
1345 noborder = rules()->checkNoBorder(noborder || m_motif.noBorder());
1346 updateDecoration(true);
1347 }
1348
1349 // Decoration mask (i.e. 'else' here) setting is done in setMask()
1350 // when the decoration calls it or when the decoration is created/destroyed
1351 updateInputShape();
1352 if (Compositor::compositing()) {
1353 addRepaintFull();
1354 addWorkspaceRepaint(visibleGeometry()); // In case shape change removes part of this window
1355 }
1356 Q_EMIT geometryShapeChanged(this, frameGeometry());
1357 }
1358
1359 static Xcb::Window shape_helper_window(XCB_WINDOW_NONE);
1360
cleanupX11()1361 void X11Client::cleanupX11()
1362 {
1363 shape_helper_window.reset();
1364 }
1365
updateInputShape()1366 void X11Client::updateInputShape()
1367 {
1368 if (hiddenPreview()) // Sets it to none, don't change
1369 return;
1370 if (Xcb::Extensions::self()->isShapeInputAvailable()) {
1371 // There appears to be no way to find out if a window has input
1372 // shape set or not, so always propagate the input shape
1373 // (it's the same like the bounding shape by default).
1374 // Also, build the shape using a helper window, not directly
1375 // in the frame window, because the sequence set-shape-to-frame,
1376 // remove-shape-of-client, add-input-shape-of-client has the problem
1377 // that after the second step there's a hole in the input shape
1378 // until the real shape of the client is added and that can make
1379 // the window lose focus (which is a problem with mouse focus policies)
1380 // TODO: It seems there is, after all - XShapeGetRectangles() - but maybe this is better
1381 if (!shape_helper_window.isValid())
1382 shape_helper_window.create(QRect(0, 0, 1, 1));
1383 shape_helper_window.resize(m_bufferGeometry.size());
1384 xcb_connection_t *c = connection();
1385 xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING,
1386 shape_helper_window, 0, 0, frameId());
1387 xcb_shape_combine(c, XCB_SHAPE_SO_SUBTRACT, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING,
1388 shape_helper_window, clientPos().x(), clientPos().y(), window());
1389 xcb_shape_combine(c, XCB_SHAPE_SO_UNION, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT,
1390 shape_helper_window, clientPos().x(), clientPos().y(), window());
1391 xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT,
1392 frameId(), 0, 0, shape_helper_window);
1393 }
1394 }
1395
hideClient(bool hide)1396 void X11Client::hideClient(bool hide)
1397 {
1398 if (hidden == hide)
1399 return;
1400 hidden = hide;
1401 updateVisibility();
1402 }
1403
setupCompositing()1404 bool X11Client::setupCompositing()
1405 {
1406 if (!Toplevel::setupCompositing()){
1407 return false;
1408 }
1409 // If compositing is back on, stop rendering decoration in the frame window.
1410 maybeDestroyX11DecorationRenderer();
1411 updateVisibility(); // for internalKeep()
1412 return true;
1413 }
1414
finishCompositing(ReleaseReason releaseReason)1415 void X11Client::finishCompositing(ReleaseReason releaseReason)
1416 {
1417 Toplevel::finishCompositing(releaseReason);
1418 updateVisibility();
1419 // for safety in case KWin is just resizing the window
1420 resetHaveResizeEffect();
1421 // If compositing is off, render the decoration in the X11 frame window.
1422 maybeCreateX11DecorationRenderer();
1423 }
1424
1425 /**
1426 * Returns whether the window is minimizable or not
1427 */
isMinimizable() const1428 bool X11Client::isMinimizable() const
1429 {
1430 if (isSpecialWindow() && !isTransient())
1431 return false;
1432 if (!rules()->checkMinimize(true))
1433 return false;
1434
1435 if (isTransient()) {
1436 // #66868 - Let other xmms windows be minimized when the mainwindow is minimized
1437 bool shown_mainwindow = false;
1438 auto mainclients = mainClients();
1439 for (auto it = mainclients.constBegin();
1440 it != mainclients.constEnd();
1441 ++it)
1442 if ((*it)->isShown(true))
1443 shown_mainwindow = true;
1444 if (!shown_mainwindow)
1445 return true;
1446 }
1447 #if 0
1448 // This is here because kicker's taskbar doesn't provide separate entries
1449 // for windows with an explicitly given parent
1450 // TODO: perhaps this should be redone
1451 // Disabled for now, since at least modal dialogs should be minimizable
1452 // (resulting in the mainwindow being minimized too).
1453 if (transientFor() != NULL)
1454 return false;
1455 #endif
1456 if (!wantsTabFocus()) // SELI, TODO: - NET::Utility? why wantsTabFocus() - skiptaskbar? ?
1457 return false;
1458 return true;
1459 }
1460
doMinimize()1461 void X11Client::doMinimize()
1462 {
1463 if (isShade()) {
1464 // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded
1465 info->setState(isMinimized() ? NET::States() : NET::Shaded, NET::Shaded);
1466 }
1467 updateVisibility();
1468 updateAllowedActions();
1469 workspace()->updateMinimizedOfTransients(this);
1470 }
1471
iconGeometry() const1472 QRect X11Client::iconGeometry() const
1473 {
1474 NETRect r = info->iconGeometry();
1475 QRect geom(r.pos.x, r.pos.y, r.size.width, r.size.height);
1476 if (geom.isValid())
1477 return geom;
1478 else {
1479 // Check all mainwindows of this window (recursively)
1480 Q_FOREACH (AbstractClient * amainwin, mainClients()) {
1481 X11Client *mainwin = dynamic_cast<X11Client *>(amainwin);
1482 if (!mainwin) {
1483 continue;
1484 }
1485 geom = mainwin->iconGeometry();
1486 if (geom.isValid())
1487 return geom;
1488 }
1489 // No mainwindow (or their parents) with icon geometry was found
1490 return AbstractClient::iconGeometry();
1491 }
1492 }
1493
isShadeable() const1494 bool X11Client::isShadeable() const
1495 {
1496 return !isSpecialWindow() && !noBorder() && (rules()->checkShade(ShadeNormal) != rules()->checkShade(ShadeNone));
1497 }
1498
doSetShade(ShadeMode previousShadeMode)1499 void X11Client::doSetShade(ShadeMode previousShadeMode)
1500 {
1501 // TODO: All this unmapping, resizing etc. feels too much duplicated from elsewhere
1502 if (isShade()) {
1503 // shade_mode == ShadeNormal
1504 addWorkspaceRepaint(visibleGeometry());
1505 // Shade
1506 shade_geometry_change = true;
1507 QSize s(adjustedSize());
1508 s.setHeight(borderTop() + borderBottom());
1509 m_wrapper.selectInput(ClientWinMask); // Avoid getting UnmapNotify
1510 m_wrapper.unmap();
1511 m_client.unmap();
1512 m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY);
1513 exportMappingState(XCB_ICCCM_WM_STATE_ICONIC);
1514 resize(s);
1515 shade_geometry_change = false;
1516 if (previousShadeMode == ShadeHover) {
1517 if (shade_below && workspace()->stackingOrder().indexOf(shade_below) > -1)
1518 workspace()->restack(this, shade_below, true);
1519 if (isActive())
1520 workspace()->activateNextClient(this);
1521 } else if (isActive()) {
1522 workspace()->focusToNull();
1523 }
1524 } else {
1525 shade_geometry_change = true;
1526 if (decoratedClient())
1527 decoratedClient()->signalShadeChange();
1528 QSize s(adjustedSize());
1529 shade_geometry_change = false;
1530 resize(s);
1531 setGeometryRestore(frameGeometry());
1532 if ((shadeMode() == ShadeHover || shadeMode() == ShadeActivated) && rules()->checkAcceptFocus(info->input()))
1533 setActive(true);
1534 if (shadeMode() == ShadeHover) {
1535 QList<Toplevel *> order = workspace()->stackingOrder();
1536 // invalidate, since "this" could be the topmost toplevel and shade_below dangeling
1537 shade_below = nullptr;
1538 // this is likely related to the index parameter?!
1539 for (int idx = order.indexOf(this) + 1; idx < order.count(); ++idx) {
1540 shade_below = qobject_cast<X11Client *>(order.at(idx));
1541 if (shade_below) {
1542 break;
1543 }
1544 }
1545 if (shade_below && shade_below->isNormalWindow())
1546 workspace()->raiseClient(this);
1547 else
1548 shade_below = nullptr;
1549 }
1550 m_wrapper.map();
1551 m_client.map();
1552 exportMappingState(XCB_ICCCM_WM_STATE_NORMAL);
1553 if (isActive())
1554 workspace()->requestFocus(this);
1555 }
1556 info->setState(isShade() ? NET::Shaded : NET::States(), NET::Shaded);
1557 info->setState(isShown(false) ? NET::States() : NET::Hidden, NET::Hidden);
1558 updateVisibility();
1559 updateAllowedActions();
1560 discardWindowPixmap();
1561 }
1562
updateVisibility()1563 void X11Client::updateVisibility()
1564 {
1565 if (isZombie())
1566 return;
1567 if (hidden) {
1568 info->setState(NET::Hidden, NET::Hidden);
1569 setSkipTaskbar(true); // Also hide from taskbar
1570 if (Compositor::compositing() && options->hiddenPreviews() == HiddenPreviewsAlways)
1571 internalKeep();
1572 else
1573 internalHide();
1574 return;
1575 }
1576 setSkipTaskbar(originalSkipTaskbar()); // Reset from 'hidden'
1577 if (isMinimized()) {
1578 info->setState(NET::Hidden, NET::Hidden);
1579 if (Compositor::compositing() && options->hiddenPreviews() == HiddenPreviewsAlways)
1580 internalKeep();
1581 else
1582 internalHide();
1583 return;
1584 }
1585 info->setState(NET::States(), NET::Hidden);
1586 if (!isOnCurrentDesktop()) {
1587 if (Compositor::compositing() && options->hiddenPreviews() != HiddenPreviewsNever)
1588 internalKeep();
1589 else
1590 internalHide();
1591 return;
1592 }
1593 if (!isOnCurrentActivity()) {
1594 if (Compositor::compositing() && options->hiddenPreviews() != HiddenPreviewsNever)
1595 internalKeep();
1596 else
1597 internalHide();
1598 return;
1599 }
1600 internalShow();
1601 }
1602
1603
1604 /**
1605 * Sets the client window's mapping state. Possible values are
1606 * WithdrawnState, IconicState, NormalState.
1607 */
exportMappingState(int s)1608 void X11Client::exportMappingState(int s)
1609 {
1610 Q_ASSERT(m_client != XCB_WINDOW_NONE);
1611 Q_ASSERT(!isZombie() || s == XCB_ICCCM_WM_STATE_WITHDRAWN);
1612 if (s == XCB_ICCCM_WM_STATE_WITHDRAWN) {
1613 m_client.deleteProperty(atoms->wm_state);
1614 return;
1615 }
1616 Q_ASSERT(s == XCB_ICCCM_WM_STATE_NORMAL || s == XCB_ICCCM_WM_STATE_ICONIC);
1617
1618 int32_t data[2];
1619 data[0] = s;
1620 data[1] = XCB_NONE;
1621 m_client.changeProperty(atoms->wm_state, atoms->wm_state, 32, 2, data);
1622 }
1623
internalShow()1624 void X11Client::internalShow()
1625 {
1626 if (mapping_state == Mapped)
1627 return;
1628 MappingState old = mapping_state;
1629 mapping_state = Mapped;
1630 if (old == Unmapped || old == Withdrawn)
1631 map();
1632 if (old == Kept) {
1633 m_decoInputExtent.map();
1634 updateHiddenPreview();
1635 }
1636 Q_EMIT windowShown(this);
1637 }
1638
internalHide()1639 void X11Client::internalHide()
1640 {
1641 if (mapping_state == Unmapped)
1642 return;
1643 MappingState old = mapping_state;
1644 mapping_state = Unmapped;
1645 if (old == Mapped || old == Kept)
1646 unmap();
1647 if (old == Kept)
1648 updateHiddenPreview();
1649 addWorkspaceRepaint(visibleGeometry());
1650 workspace()->clientHidden(this);
1651 Q_EMIT windowHidden(this);
1652 }
1653
internalKeep()1654 void X11Client::internalKeep()
1655 {
1656 Q_ASSERT(Compositor::compositing());
1657 if (mapping_state == Kept)
1658 return;
1659 MappingState old = mapping_state;
1660 mapping_state = Kept;
1661 if (old == Unmapped || old == Withdrawn)
1662 map();
1663 m_decoInputExtent.unmap();
1664 if (isActive())
1665 workspace()->focusToNull(); // get rid of input focus, bug #317484
1666 updateHiddenPreview();
1667 addWorkspaceRepaint(visibleGeometry());
1668 workspace()->clientHidden(this);
1669 }
1670
1671 /**
1672 * Maps (shows) the client. Note that it is mapping state of the frame,
1673 * not necessarily the client window itself (i.e. a shaded window is here
1674 * considered mapped, even though it is in IconicState).
1675 */
map()1676 void X11Client::map()
1677 {
1678 // XComposite invalidates backing pixmaps on unmap (minimize, different
1679 // virtual desktop, etc.). We kept the last known good pixmap around
1680 // for use in effects, but now we want to have access to the new pixmap
1681 if (Compositor::compositing())
1682 discardWindowPixmap();
1683 m_frame.map();
1684 if (!isShade()) {
1685 m_wrapper.map();
1686 m_client.map();
1687 m_decoInputExtent.map();
1688 exportMappingState(XCB_ICCCM_WM_STATE_NORMAL);
1689 } else
1690 exportMappingState(XCB_ICCCM_WM_STATE_ICONIC);
1691 addLayerRepaint(visibleGeometry());
1692 }
1693
1694 /**
1695 * Unmaps the client. Again, this is about the frame.
1696 */
unmap()1697 void X11Client::unmap()
1698 {
1699 // Here it may look like a race condition, as some other client might try to unmap
1700 // the window between these two XSelectInput() calls. However, they're supposed to
1701 // use XWithdrawWindow(), which also sends a synthetic event to the root window,
1702 // which won't be missed, so this shouldn't be a problem. The chance the real UnmapNotify
1703 // will be missed is also very minimal, so I don't think it's needed to grab the server
1704 // here.
1705 m_wrapper.selectInput(ClientWinMask); // Avoid getting UnmapNotify
1706 m_frame.unmap();
1707 m_wrapper.unmap();
1708 m_client.unmap();
1709 m_decoInputExtent.unmap();
1710 m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY);
1711 exportMappingState(XCB_ICCCM_WM_STATE_ICONIC);
1712 }
1713
1714 /**
1715 * XComposite doesn't keep window pixmaps of unmapped windows, which means
1716 * there wouldn't be any previews of windows that are minimized or on another
1717 * virtual desktop. Therefore rawHide() actually keeps such windows mapped.
1718 * However special care needs to be taken so that such windows don't interfere.
1719 * Therefore they're put very low in the stacking order and they have input shape
1720 * set to none, which hopefully is enough. If there's no input shape available,
1721 * then it's hoped that there will be some other desktop above it *shrug*.
1722 * Using normal shape would be better, but that'd affect other things, e.g. painting
1723 * of the actual preview.
1724 */
updateHiddenPreview()1725 void X11Client::updateHiddenPreview()
1726 {
1727 if (hiddenPreview()) {
1728 workspace()->forceRestacking();
1729 if (Xcb::Extensions::self()->isShapeInputAvailable()) {
1730 xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT,
1731 XCB_CLIP_ORDERING_UNSORTED, frameId(), 0, 0, 0, nullptr);
1732 }
1733 } else {
1734 workspace()->forceRestacking();
1735 updateInputShape();
1736 }
1737 }
1738
sendClientMessage(xcb_window_t w,xcb_atom_t a,xcb_atom_t protocol,uint32_t data1,uint32_t data2,uint32_t data3)1739 void X11Client::sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1, uint32_t data2, uint32_t data3)
1740 {
1741 xcb_client_message_event_t ev;
1742 // Every X11 event is 32 bytes (see man xcb_send_event), so XCB will copy
1743 // 32 unconditionally. Add a static_assert to ensure we don't disclose
1744 // stack memory.
1745 static_assert(sizeof(ev) == 32, "Would leak stack data otherwise");
1746 memset(&ev, 0, sizeof(ev));
1747 ev.response_type = XCB_CLIENT_MESSAGE;
1748 ev.window = w;
1749 ev.type = a;
1750 ev.format = 32;
1751 ev.data.data32[0] = protocol;
1752 ev.data.data32[1] = xTime();
1753 ev.data.data32[2] = data1;
1754 ev.data.data32[3] = data2;
1755 ev.data.data32[4] = data3;
1756 uint32_t eventMask = 0;
1757 if (w == rootWindow()) {
1758 eventMask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Magic!
1759 }
1760 xcb_send_event(connection(), false, w, eventMask, reinterpret_cast<const char*>(&ev));
1761 xcb_flush(connection());
1762 }
1763
1764 /**
1765 * Returns whether the window may be closed (have a close button)
1766 */
isCloseable() const1767 bool X11Client::isCloseable() const
1768 {
1769 return rules()->checkCloseable(m_motif.close() && !isSpecialWindow());
1770 }
1771
1772 /**
1773 * Closes the window by either sending a delete_window message or using XKill.
1774 */
closeWindow()1775 void X11Client::closeWindow()
1776 {
1777 if (!isCloseable())
1778 return;
1779
1780 // Update user time, because the window may create a confirming dialog.
1781 updateUserTime();
1782
1783 if (info->supportsProtocol(NET::DeleteWindowProtocol)) {
1784 sendClientMessage(window(), atoms->wm_protocols, atoms->wm_delete_window);
1785 pingWindow();
1786 } else // Client will not react on wm_delete_window. We have not choice
1787 // but destroy his connection to the XServer.
1788 killWindow();
1789 }
1790
1791
1792 /**
1793 * Kills the window via XKill
1794 */
killWindow()1795 void X11Client::killWindow()
1796 {
1797 qCDebug(KWIN_CORE) << "X11Client::killWindow():" << caption();
1798 killProcess(false);
1799 m_client.kill(); // Always kill this client at the server
1800 destroyClient();
1801 }
1802
1803 /**
1804 * Send a ping to the window using _NET_WM_PING if possible if it
1805 * doesn't respond within a reasonable time, it will be killed.
1806 */
pingWindow()1807 void X11Client::pingWindow()
1808 {
1809 if (!info->supportsProtocol(NET::PingProtocol))
1810 return; // Can't ping :(
1811 if (options->killPingTimeout() == 0)
1812 return; // Turned off
1813 if (ping_timer != nullptr)
1814 return; // Pinging already
1815 ping_timer = new QTimer(this);
1816 connect(ping_timer, &QTimer::timeout, this,
1817 [this]() {
1818 if (unresponsive()) {
1819 qCDebug(KWIN_CORE) << "Final ping timeout, asking to kill:" << caption();
1820 ping_timer->deleteLater();
1821 ping_timer = nullptr;
1822 killProcess(true, m_pingTimestamp);
1823 return;
1824 }
1825
1826 qCDebug(KWIN_CORE) << "First ping timeout:" << caption();
1827
1828 setUnresponsive(true);
1829 ping_timer->start();
1830 }
1831 );
1832 ping_timer->setSingleShot(true);
1833 // we'll run the timer twice, at first we'll desaturate the window
1834 // and the second time we'll show the "do you want to kill" prompt
1835 ping_timer->start(options->killPingTimeout() / 2);
1836 m_pingTimestamp = xTime();
1837 rootInfo()->sendPing(window(), m_pingTimestamp);
1838 }
1839
gotPing(xcb_timestamp_t timestamp)1840 void X11Client::gotPing(xcb_timestamp_t timestamp)
1841 {
1842 // Just plain compare is not good enough because of 64bit and truncating and whatnot
1843 if (NET::timestampCompare(timestamp, m_pingTimestamp) != 0)
1844 return;
1845 delete ping_timer;
1846 ping_timer = nullptr;
1847
1848 setUnresponsive(false);
1849
1850 if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive
1851 ::kill(m_killHelperPID, SIGTERM);
1852 m_killHelperPID = 0;
1853 }
1854 }
1855
killProcess(bool ask,xcb_timestamp_t timestamp)1856 void X11Client::killProcess(bool ask, xcb_timestamp_t timestamp)
1857 {
1858 if (m_killHelperPID && !::kill(m_killHelperPID, 0)) // means the process is alive
1859 return;
1860 Q_ASSERT(!ask || timestamp != XCB_TIME_CURRENT_TIME);
1861 pid_t pid = info->pid();
1862 if (pid <= 0 || clientMachine()->hostName().isEmpty()) // Needed properties missing
1863 return;
1864 qCDebug(KWIN_CORE) << "Kill process:" << pid << "(" << clientMachine()->hostName() << ")";
1865 if (!ask) {
1866 if (!clientMachine()->isLocal()) {
1867 QStringList lst;
1868 lst << QString::fromUtf8(clientMachine()->hostName()) << QStringLiteral("kill") << QString::number(pid);
1869 QProcess::startDetached(QStringLiteral("xon"), lst);
1870 } else
1871 ::kill(pid, SIGTERM);
1872 } else {
1873 QString hostname = clientMachine()->isLocal() ? QStringLiteral("localhost") : QString::fromUtf8(clientMachine()->hostName());
1874 // execute helper from build dir or the system installed one
1875 const QFileInfo buildDirBinary{QDir{QCoreApplication::applicationDirPath()}, QStringLiteral("kwin_killer_helper")};
1876 QProcess::startDetached(buildDirBinary.exists() ? buildDirBinary.absoluteFilePath() : QStringLiteral(KWIN_KILLER_BIN),
1877 QStringList() << QStringLiteral("--pid") << QString::number(unsigned(pid)) << QStringLiteral("--hostname") << hostname
1878 << QStringLiteral("--windowname") << captionNormal()
1879 << QStringLiteral("--applicationname") << QString::fromUtf8(resourceClass())
1880 << QStringLiteral("--wid") << QString::number(window())
1881 << QStringLiteral("--timestamp") << QString::number(timestamp),
1882 QString(), &m_killHelperPID);
1883 }
1884 }
1885
doSetKeepAbove()1886 void X11Client::doSetKeepAbove()
1887 {
1888 info->setState(keepAbove() ? NET::KeepAbove : NET::States(), NET::KeepAbove);
1889 }
1890
doSetKeepBelow()1891 void X11Client::doSetKeepBelow()
1892 {
1893 info->setState(keepBelow() ? NET::KeepBelow : NET::States(), NET::KeepBelow);
1894 }
1895
doSetSkipTaskbar()1896 void X11Client::doSetSkipTaskbar()
1897 {
1898 info->setState(skipTaskbar() ? NET::SkipTaskbar : NET::States(), NET::SkipTaskbar);
1899 }
1900
doSetSkipPager()1901 void X11Client::doSetSkipPager()
1902 {
1903 info->setState(skipPager() ? NET::SkipPager : NET::States(), NET::SkipPager);
1904 }
1905
doSetSkipSwitcher()1906 void X11Client::doSetSkipSwitcher()
1907 {
1908 info->setState(skipSwitcher() ? NET::SkipSwitcher : NET::States(), NET::SkipSwitcher);
1909 }
1910
doSetDesktop()1911 void X11Client::doSetDesktop()
1912 {
1913 updateVisibility();
1914 }
1915
doSetDemandsAttention()1916 void X11Client::doSetDemandsAttention()
1917 {
1918 info->setState(isDemandingAttention() ? NET::DemandsAttention : NET::States(), NET::DemandsAttention);
1919 }
1920
doSetOnActivities(const QStringList & activityList)1921 void X11Client::doSetOnActivities(const QStringList &activityList)
1922 {
1923 #ifdef KWIN_BUILD_ACTIVITIES
1924 if (activityList.isEmpty()) {
1925 const QByteArray nullUuid = Activities::nullUuid().toUtf8();
1926 m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, nullUuid.length(), nullUuid.constData());
1927 } else {
1928 QByteArray joined = activityList.join(QStringLiteral(",")).toLatin1();
1929 m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, joined.length(), joined.constData());
1930 }
1931 #else
1932 Q_UNUSED(newActivitiesList)
1933 #endif
1934 }
1935
updateActivities(bool includeTransients)1936 void X11Client::updateActivities(bool includeTransients)
1937 {
1938 AbstractClient::updateActivities(includeTransients);
1939 if (!m_activityUpdatesBlocked) {
1940 updateVisibility();
1941 }
1942 }
1943
1944 /**
1945 * Returns the list of activities the client window is on.
1946 * if it's on all activities, the list will be empty.
1947 * Don't use this, use isOnActivity() and friends (from class Toplevel)
1948 */
activities() const1949 QStringList X11Client::activities() const
1950 {
1951 if (sessionActivityOverride) {
1952 return QStringList();
1953 }
1954 return AbstractClient::activities();
1955 }
1956
1957 /**
1958 * Performs the actual focusing of the window using XSetInputFocus and WM_TAKE_FOCUS
1959 */
takeFocus()1960 bool X11Client::takeFocus()
1961 {
1962 if (rules()->checkAcceptFocus(info->input())) {
1963 xcb_void_cookie_t cookie = xcb_set_input_focus_checked(connection(),
1964 XCB_INPUT_FOCUS_POINTER_ROOT,
1965 window(), XCB_TIME_CURRENT_TIME);
1966 ScopedCPointer<xcb_generic_error_t> error(xcb_request_check(connection(), cookie));
1967 if (error) {
1968 qCWarning(KWIN_CORE, "Failed to focus 0x%x (error %d)", window(), error->error_code);
1969 return false;
1970 }
1971 } else {
1972 demandAttention(false); // window cannot take input, at least withdraw urgency
1973 }
1974 if (info->supportsProtocol(NET::TakeFocusProtocol)) {
1975 updateXTime();
1976 sendClientMessage(window(), atoms->wm_protocols, atoms->wm_take_focus);
1977 }
1978 workspace()->setShouldGetFocus(this);
1979
1980 bool breakShowingDesktop = !keepAbove();
1981 if (breakShowingDesktop) {
1982 Q_FOREACH (const X11Client *c, group()->members()) {
1983 if (c->isDesktop()) {
1984 breakShowingDesktop = false;
1985 break;
1986 }
1987 }
1988 }
1989 if (breakShowingDesktop)
1990 workspace()->setShowingDesktop(false);
1991
1992 return true;
1993 }
1994
1995 /**
1996 * Returns whether the window provides context help or not. If it does,
1997 * you should show a help menu item or a help button like '?' and call
1998 * contextHelp() if this is invoked.
1999 *
2000 * \sa contextHelp()
2001 */
providesContextHelp() const2002 bool X11Client::providesContextHelp() const
2003 {
2004 return info->supportsProtocol(NET::ContextHelpProtocol);
2005 }
2006
2007 /**
2008 * Invokes context help on the window. Only works if the window
2009 * actually provides context help.
2010 *
2011 * \sa providesContextHelp()
2012 */
showContextHelp()2013 void X11Client::showContextHelp()
2014 {
2015 if (info->supportsProtocol(NET::ContextHelpProtocol)) {
2016 sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_context_help);
2017 }
2018 }
2019
2020 /**
2021 * Fetches the window's caption (WM_NAME property). It will be
2022 * stored in the client's caption().
2023 */
fetchName()2024 void X11Client::fetchName()
2025 {
2026 setCaption(readName());
2027 }
2028
readNameProperty(xcb_window_t w,xcb_atom_t atom)2029 static inline QString readNameProperty(xcb_window_t w, xcb_atom_t atom)
2030 {
2031 const auto cookie = xcb_icccm_get_text_property_unchecked(connection(), w, atom);
2032 xcb_icccm_get_text_property_reply_t reply;
2033 if (xcb_icccm_get_wm_name_reply(connection(), cookie, &reply, nullptr)) {
2034 QString retVal;
2035 if (reply.encoding == atoms->utf8_string) {
2036 retVal = QString::fromUtf8(QByteArray(reply.name, reply.name_len));
2037 } else if (reply.encoding == XCB_ATOM_STRING) {
2038 retVal = QString::fromLocal8Bit(QByteArray(reply.name, reply.name_len));
2039 }
2040 xcb_icccm_get_text_property_reply_wipe(&reply);
2041 return retVal.simplified();
2042 }
2043 return QString();
2044 }
2045
readName() const2046 QString X11Client::readName() const
2047 {
2048 if (info->name() && info->name()[0] != '\0')
2049 return QString::fromUtf8(info->name()).simplified();
2050 else {
2051 return readNameProperty(window(), XCB_ATOM_WM_NAME);
2052 }
2053 }
2054
2055 // The list is taken from https://www.unicode.org/reports/tr9/ (#154840)
2056 static const QChar LRM(0x200E);
2057
setCaption(const QString & _s,bool force)2058 void X11Client::setCaption(const QString& _s, bool force)
2059 {
2060 QString s(_s);
2061 for (int i = 0; i < s.length(); ) {
2062 if (!s[i].isPrint()) {
2063 if (QChar(s[i]).isHighSurrogate() && i + 1 < s.length() && QChar(s[i + 1]).isLowSurrogate()) {
2064 const uint uc = QChar::surrogateToUcs4(s[i], s[i + 1]);
2065 if (!QChar::isPrint(uc)) {
2066 s.remove(i, 2);
2067 } else {
2068 i += 2;
2069 }
2070 continue;
2071 }
2072 s.remove(i, 1);
2073 continue;
2074 }
2075 ++i;
2076 }
2077 const bool changed = (s != cap_normal);
2078 if (!force && !changed) {
2079 return;
2080 }
2081 cap_normal = s;
2082 if (!force && !changed) {
2083 Q_EMIT captionChanged();
2084 return;
2085 }
2086
2087 bool reset_name = force;
2088 bool was_suffix = (!cap_suffix.isEmpty());
2089 cap_suffix.clear();
2090 QString machine_suffix;
2091 if (!options->condensedTitle()) { // machine doesn't qualify for "clean"
2092 if (clientMachine()->hostName() != ClientMachine::localhost() && !clientMachine()->isLocal())
2093 machine_suffix = QLatin1String(" <@") + QString::fromUtf8(clientMachine()->hostName()) + QLatin1Char('>') + LRM;
2094 }
2095 QString shortcut_suffix = shortcutCaptionSuffix();
2096 cap_suffix = machine_suffix + shortcut_suffix;
2097 if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) {
2098 int i = 2;
2099 do {
2100 cap_suffix = machine_suffix + QLatin1String(" <") + QString::number(i) + QLatin1Char('>') + LRM;
2101 i++;
2102 } while (findClientWithSameCaption());
2103 info->setVisibleName(caption().toUtf8().constData());
2104 reset_name = false;
2105 }
2106 if ((was_suffix && cap_suffix.isEmpty()) || reset_name) {
2107 // If it was new window, it may have old value still set, if the window is reused
2108 info->setVisibleName("");
2109 info->setVisibleIconName("");
2110 } else if (!cap_suffix.isEmpty() && !cap_iconic.isEmpty())
2111 // Keep the same suffix in iconic name if it's set
2112 info->setVisibleIconName(QString(cap_iconic + cap_suffix).toUtf8().constData());
2113
2114 Q_EMIT captionChanged();
2115 }
2116
updateCaption()2117 void X11Client::updateCaption()
2118 {
2119 setCaption(cap_normal, true);
2120 }
2121
fetchIconicName()2122 void X11Client::fetchIconicName()
2123 {
2124 QString s;
2125 if (info->iconName() && info->iconName()[0] != '\0')
2126 s = QString::fromUtf8(info->iconName());
2127 else
2128 s = readNameProperty(window(), XCB_ATOM_WM_ICON_NAME);
2129 if (s != cap_iconic) {
2130 bool was_set = !cap_iconic.isEmpty();
2131 cap_iconic = s;
2132 if (!cap_suffix.isEmpty()) {
2133 if (!cap_iconic.isEmpty()) // Keep the same suffix in iconic name if it's set
2134 info->setVisibleIconName(QString(s + cap_suffix).toUtf8().constData());
2135 else if (was_set)
2136 info->setVisibleIconName("");
2137 }
2138 }
2139 }
2140
setClientShown(bool shown)2141 void X11Client::setClientShown(bool shown)
2142 {
2143 if (isZombie())
2144 return; // Don't change shown status if this client is being deleted
2145 if (shown != hidden)
2146 return; // nothing to change
2147 hidden = !shown;
2148 if (shown) {
2149 map();
2150 takeFocus();
2151 autoRaise();
2152 FocusChain::self()->update(this, FocusChain::MakeFirst);
2153 } else {
2154 unmap();
2155 // Don't move tabs to the end of the list when another tab get's activated
2156 FocusChain::self()->update(this, FocusChain::MakeLast);
2157 addWorkspaceRepaint(visibleGeometry());
2158 }
2159 }
2160
getMotifHints()2161 void X11Client::getMotifHints()
2162 {
2163 const bool wasClosable = isCloseable();
2164 const bool wasNoBorder = m_motif.noBorder();
2165 if (m_managed) // only on property change, initial read is prefetched
2166 m_motif.fetch();
2167 m_motif.read();
2168 if (m_motif.hasDecoration() && m_motif.noBorder() != wasNoBorder) {
2169 // If we just got a hint telling us to hide decorations, we do so.
2170 if (m_motif.noBorder())
2171 noborder = rules()->checkNoBorder(true);
2172 // If the Motif hint is now telling us to show decorations, we only do so if the app didn't
2173 // instruct us to hide decorations in some other way, though.
2174 else if (!app_noborder)
2175 noborder = rules()->checkNoBorder(false);
2176 }
2177
2178 // mminimize; - Ignore, bogus - E.g. shading or sending to another desktop is "minimizing" too
2179 // mmaximize; - Ignore, bogus - Maximizing is basically just resizing
2180 const bool closabilityChanged = wasClosable != isCloseable();
2181 if (isManaged())
2182 updateDecoration(true); // Check if noborder state has changed
2183 if (closabilityChanged) {
2184 Q_EMIT closeableChanged(isCloseable());
2185 }
2186 }
2187
getIcons()2188 void X11Client::getIcons()
2189 {
2190 // First read icons from the window itself
2191 const QString themedIconName = iconFromDesktopFile();
2192 if (!themedIconName.isEmpty()) {
2193 setIcon(QIcon::fromTheme(themedIconName));
2194 return;
2195 }
2196 QIcon icon;
2197 auto readIcon = [this, &icon](int size, bool scale = true) {
2198 const QPixmap pix = KWindowSystem::icon(window(), size, size, scale, KWindowSystem::NETWM | KWindowSystem::WMHints, info);
2199 if (!pix.isNull()) {
2200 icon.addPixmap(pix);
2201 }
2202 };
2203 readIcon(16);
2204 readIcon(32);
2205 readIcon(48, false);
2206 readIcon(64, false);
2207 readIcon(128, false);
2208 if (icon.isNull()) {
2209 // Then try window group
2210 icon = group()->icon();
2211 }
2212 if (icon.isNull() && isTransient()) {
2213 // Then mainclients
2214 auto mainclients = mainClients();
2215 for (auto it = mainclients.constBegin();
2216 it != mainclients.constEnd() && icon.isNull();
2217 ++it) {
2218 if (!(*it)->icon().isNull()) {
2219 icon = (*it)->icon();
2220 break;
2221 }
2222 }
2223 }
2224 if (icon.isNull()) {
2225 // And if nothing else, load icon from classhint or xapp icon
2226 icon.addPixmap(KWindowSystem::icon(window(), 32, 32, true, KWindowSystem::ClassHint | KWindowSystem::XApp, info));
2227 icon.addPixmap(KWindowSystem::icon(window(), 16, 16, true, KWindowSystem::ClassHint | KWindowSystem::XApp, info));
2228 icon.addPixmap(KWindowSystem::icon(window(), 64, 64, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info));
2229 icon.addPixmap(KWindowSystem::icon(window(), 128, 128, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info));
2230 }
2231 setIcon(icon);
2232 }
2233
2234 /**
2235 * Returns \c true if X11Client wants to throttle resizes; otherwise returns \c false.
2236 */
wantsSyncCounter() const2237 bool X11Client::wantsSyncCounter() const
2238 {
2239 return true;
2240 }
2241
getSyncCounter()2242 void X11Client::getSyncCounter()
2243 {
2244 if (!Xcb::Extensions::self()->isSyncAvailable())
2245 return;
2246 if (!wantsSyncCounter())
2247 return;
2248
2249 Xcb::Property syncProp(false, window(), atoms->net_wm_sync_request_counter, XCB_ATOM_CARDINAL, 0, 1);
2250 const xcb_sync_counter_t counter = syncProp.value<xcb_sync_counter_t>(XCB_NONE);
2251 if (counter != XCB_NONE) {
2252 m_syncRequest.counter = counter;
2253 m_syncRequest.value.hi = 0;
2254 m_syncRequest.value.lo = 0;
2255 auto *c = connection();
2256 xcb_sync_set_counter(c, m_syncRequest.counter, m_syncRequest.value);
2257 if (m_syncRequest.alarm == XCB_NONE) {
2258 const uint32_t mask = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE_TYPE | XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_EVENTS;
2259 const uint32_t values[] = {
2260 m_syncRequest.counter,
2261 XCB_SYNC_VALUETYPE_RELATIVE,
2262 XCB_SYNC_TESTTYPE_POSITIVE_TRANSITION,
2263 1
2264 };
2265 m_syncRequest.alarm = xcb_generate_id(c);
2266 auto cookie = xcb_sync_create_alarm_checked(c, m_syncRequest.alarm, mask, values);
2267 ScopedCPointer<xcb_generic_error_t> error(xcb_request_check(c, cookie));
2268 if (!error.isNull()) {
2269 m_syncRequest.alarm = XCB_NONE;
2270 } else {
2271 xcb_sync_change_alarm_value_list_t value;
2272 memset(&value, 0, sizeof(value));
2273 value.value.hi = 0;
2274 value.value.lo = 1;
2275 value.delta.hi = 0;
2276 value.delta.lo = 1;
2277 xcb_sync_change_alarm_aux(c, m_syncRequest.alarm, XCB_SYNC_CA_DELTA | XCB_SYNC_CA_VALUE, &value);
2278 }
2279 }
2280 }
2281 }
2282
2283 /**
2284 * Send the client a _NET_SYNC_REQUEST
2285 */
sendSyncRequest()2286 void X11Client::sendSyncRequest()
2287 {
2288 if (m_syncRequest.counter == XCB_NONE || m_syncRequest.isPending) {
2289 return; // do NOT, NEVER send a sync request when there's one on the stack. the clients will just stop respoding. FOREVER! ...
2290 }
2291
2292 if (!m_syncRequest.failsafeTimeout) {
2293 m_syncRequest.failsafeTimeout = new QTimer(this);
2294 connect(m_syncRequest.failsafeTimeout, &QTimer::timeout, this,
2295 [this]() {
2296 // client does not respond to XSYNC requests in reasonable time, remove support
2297 if (!ready_for_painting) {
2298 // failed on initial pre-show request
2299 setReadyForPainting();
2300 setupWindowManagementInterface();
2301 return;
2302 }
2303 // failed during resize
2304 m_syncRequest.isPending = false;
2305 m_syncRequest.counter = XCB_NONE;
2306 m_syncRequest.alarm = XCB_NONE;
2307 delete m_syncRequest.timeout;
2308 delete m_syncRequest.failsafeTimeout;
2309 m_syncRequest.timeout = nullptr;
2310 m_syncRequest.failsafeTimeout = nullptr;
2311 m_syncRequest.lastTimestamp = XCB_CURRENT_TIME;
2312 }
2313 );
2314 m_syncRequest.failsafeTimeout->setSingleShot(true);
2315 }
2316 // if there's no response within 10 seconds, sth. went wrong and we remove XSYNC support from this client.
2317 // see events.cpp X11Client::syncEvent()
2318 m_syncRequest.failsafeTimeout->start(ready_for_painting ? 10000 : 1000);
2319
2320 // We increment before the notify so that after the notify
2321 // syncCounterSerial will equal the value we are expecting
2322 // in the acknowledgement
2323 const uint32_t oldLo = m_syncRequest.value.lo;
2324 m_syncRequest.value.lo++;
2325 if (oldLo > m_syncRequest.value.lo) {
2326 m_syncRequest.value.hi++;
2327 }
2328 if (m_syncRequest.lastTimestamp >= xTime()) {
2329 updateXTime();
2330 }
2331
2332 // Send the message to client
2333 sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_sync_request,
2334 m_syncRequest.value.lo, m_syncRequest.value.hi);
2335 m_syncRequest.isPending = true;
2336 m_syncRequest.lastTimestamp = xTime();
2337 }
2338
wantsInput() const2339 bool X11Client::wantsInput() const
2340 {
2341 return rules()->checkAcceptFocus(acceptsFocus() || info->supportsProtocol(NET::TakeFocusProtocol));
2342 }
2343
acceptsFocus() const2344 bool X11Client::acceptsFocus() const
2345 {
2346 return info->input();
2347 }
2348
setBlockingCompositing(bool block)2349 void X11Client::setBlockingCompositing(bool block)
2350 {
2351 const bool usedToBlock = blocks_compositing;
2352 blocks_compositing = rules()->checkBlockCompositing(block && options->windowsBlockCompositing());
2353 if (usedToBlock != blocks_compositing) {
2354 Q_EMIT blockingCompositingChanged(blocks_compositing ? this : nullptr);
2355 }
2356 }
2357
updateAllowedActions(bool force)2358 void X11Client::updateAllowedActions(bool force)
2359 {
2360 if (!isManaged() && !force)
2361 return;
2362 NET::Actions old_allowed_actions = NET::Actions(allowed_actions);
2363 allowed_actions = NET::Actions();
2364 if (isMovable())
2365 allowed_actions |= NET::ActionMove;
2366 if (isResizable())
2367 allowed_actions |= NET::ActionResize;
2368 if (isMinimizable())
2369 allowed_actions |= NET::ActionMinimize;
2370 if (isShadeable())
2371 allowed_actions |= NET::ActionShade;
2372 // Sticky state not supported
2373 if (isMaximizable())
2374 allowed_actions |= NET::ActionMax;
2375 if (userCanSetFullScreen())
2376 allowed_actions |= NET::ActionFullScreen;
2377 allowed_actions |= NET::ActionChangeDesktop; // Always (Pagers shouldn't show Docks etc.)
2378 if (isCloseable())
2379 allowed_actions |= NET::ActionClose;
2380 if (old_allowed_actions == allowed_actions)
2381 return;
2382 // TODO: This could be delayed and compressed - It's only for pagers etc. anyway
2383 info->setAllowedActions(allowed_actions);
2384
2385 // ONLY if relevant features have changed (and the window didn't just get/loose moveresize for maximization state changes)
2386 const NET::Actions relevant = ~(NET::ActionMove|NET::ActionResize);
2387 if ((allowed_actions & relevant) != (old_allowed_actions & relevant)) {
2388 if ((allowed_actions & NET::ActionMinimize) != (old_allowed_actions & NET::ActionMinimize)) {
2389 Q_EMIT minimizeableChanged(allowed_actions & NET::ActionMinimize);
2390 }
2391 if ((allowed_actions & NET::ActionShade) != (old_allowed_actions & NET::ActionShade)) {
2392 Q_EMIT shadeableChanged(allowed_actions & NET::ActionShade);
2393 }
2394 if ((allowed_actions & NET::ActionMax) != (old_allowed_actions & NET::ActionMax)) {
2395 Q_EMIT maximizeableChanged(allowed_actions & NET::ActionMax);
2396 }
2397 if ((allowed_actions & NET::ActionClose) != (old_allowed_actions & NET::ActionClose)) {
2398 Q_EMIT closeableChanged(allowed_actions & NET::ActionClose);
2399 }
2400 }
2401 }
2402
fetchActivities() const2403 Xcb::StringProperty X11Client::fetchActivities() const
2404 {
2405 #ifdef KWIN_BUILD_ACTIVITIES
2406 return Xcb::StringProperty(window(), atoms->activities);
2407 #else
2408 return Xcb::StringProperty();
2409 #endif
2410 }
2411
readActivities(Xcb::StringProperty & property)2412 void X11Client::readActivities(Xcb::StringProperty &property)
2413 {
2414 #ifdef KWIN_BUILD_ACTIVITIES
2415 QStringList newActivitiesList;
2416 QString prop = QString::fromUtf8(property);
2417 activitiesDefined = !prop.isEmpty();
2418
2419 if (prop == Activities::nullUuid()) {
2420 //copied from setOnAllActivities to avoid a redundant XChangeProperty.
2421 if (!activityList.isEmpty()) {
2422 activityList.clear();
2423 updateActivities(true);
2424 }
2425 return;
2426 }
2427 if (prop.isEmpty()) {
2428 //note: this makes it *act* like it's on all activities but doesn't set the property to 'ALL'
2429 if (!activityList.isEmpty()) {
2430 activityList.clear();
2431 updateActivities(true);
2432 }
2433 return;
2434 }
2435
2436 newActivitiesList = prop.split(u',');
2437
2438 if (newActivitiesList == activityList)
2439 return; //expected change, it's ok.
2440
2441 //otherwise, somebody else changed it. we need to validate before reacting.
2442 //if the activities are not synced, and there are existing clients with
2443 //activities specified, somebody has restarted kwin. we can not validate
2444 //activities in this case. we need to trust the old values.
2445 if (Activities::self() && Activities::self()->serviceStatus() != KActivities::Consumer::Unknown) {
2446 QStringList allActivities = Activities::self()->all();
2447 if (allActivities.isEmpty()) {
2448 qCDebug(KWIN_CORE) << "no activities!?!?";
2449 //don't touch anything, there's probably something bad going on and we don't wanna make it worse
2450 return;
2451 }
2452
2453
2454 for (int i = 0; i < newActivitiesList.size(); ++i) {
2455 if (! allActivities.contains(newActivitiesList.at(i))) {
2456 qCDebug(KWIN_CORE) << "invalid:" << newActivitiesList.at(i);
2457 newActivitiesList.removeAt(i--);
2458 }
2459 }
2460 }
2461 setOnActivities(newActivitiesList);
2462 #else
2463 Q_UNUSED(property)
2464 #endif
2465 }
2466
checkActivities()2467 void X11Client::checkActivities()
2468 {
2469 #ifdef KWIN_BUILD_ACTIVITIES
2470 Xcb::StringProperty property = fetchActivities();
2471 readActivities(property);
2472 #endif
2473 }
2474
setSessionActivityOverride(bool needed)2475 void X11Client::setSessionActivityOverride(bool needed)
2476 {
2477 sessionActivityOverride = needed;
2478 updateActivities(false);
2479 }
2480
fetchFirstInTabBox() const2481 Xcb::Property X11Client::fetchFirstInTabBox() const
2482 {
2483 return Xcb::Property(false, m_client, atoms->kde_first_in_window_list,
2484 atoms->kde_first_in_window_list, 0, 1);
2485 }
2486
readFirstInTabBox(Xcb::Property & property)2487 void X11Client::readFirstInTabBox(Xcb::Property &property)
2488 {
2489 setFirstInTabBox(property.toBool(32, atoms->kde_first_in_window_list));
2490 }
2491
updateFirstInTabBox()2492 void X11Client::updateFirstInTabBox()
2493 {
2494 // TODO: move into KWindowInfo
2495 Xcb::Property property = fetchFirstInTabBox();
2496 readFirstInTabBox(property);
2497 }
2498
fetchPreferredColorScheme() const2499 Xcb::StringProperty X11Client::fetchPreferredColorScheme() const
2500 {
2501 return Xcb::StringProperty(m_client, atoms->kde_color_sheme);
2502 }
2503
readPreferredColorScheme(Xcb::StringProperty & property) const2504 QString X11Client::readPreferredColorScheme(Xcb::StringProperty &property) const
2505 {
2506 return rules()->checkDecoColor(QString::fromUtf8(property));
2507 }
2508
preferredColorScheme() const2509 QString X11Client::preferredColorScheme() const
2510 {
2511 Xcb::StringProperty property = fetchPreferredColorScheme();
2512 return readPreferredColorScheme(property);
2513 }
2514
isClient() const2515 bool X11Client::isClient() const
2516 {
2517 return true;
2518 }
2519
windowType(bool direct,int supportedTypes) const2520 NET::WindowType X11Client::windowType(bool direct, int supportedTypes) const
2521 {
2522 // TODO: does it make sense to cache the returned window type for SUPPORTED_MANAGED_WINDOW_TYPES_MASK?
2523 if (supportedTypes == 0) {
2524 supportedTypes = SUPPORTED_MANAGED_WINDOW_TYPES_MASK;
2525 }
2526 NET::WindowType wt = info->windowType(NET::WindowTypes(supportedTypes));
2527 if (direct) {
2528 return wt;
2529 }
2530 NET::WindowType wt2 = rules()->checkType(wt);
2531 if (wt != wt2) {
2532 wt = wt2;
2533 info->setWindowType(wt); // force hint change
2534 }
2535 // hacks here
2536 if (wt == NET::Unknown) // this is more or less suggested in NETWM spec
2537 wt = isTransient() ? NET::Dialog : NET::Normal;
2538 return wt;
2539 }
2540
cancelFocusOutTimer()2541 void X11Client::cancelFocusOutTimer()
2542 {
2543 if (m_focusOutTimer) {
2544 m_focusOutTimer->stop();
2545 }
2546 }
2547
frameId() const2548 xcb_window_t X11Client::frameId() const
2549 {
2550 return m_frame;
2551 }
2552
inputGeometry() const2553 QRect X11Client::inputGeometry() const
2554 {
2555 // Notice that the buffer geometry corresponds to the geometry of the frame window.
2556 if (isDecorated()) {
2557 return m_bufferGeometry + decoration()->resizeOnlyBorders();
2558 }
2559 return m_bufferGeometry;
2560 }
2561
framePosToClientPos(const QPoint & point) const2562 QPoint X11Client::framePosToClientPos(const QPoint &point) const
2563 {
2564 int x = point.x();
2565 int y = point.y();
2566
2567 if (isDecorated()) {
2568 x += borderLeft();
2569 y += borderTop();
2570 } else {
2571 x -= m_clientFrameExtents.left();
2572 y -= m_clientFrameExtents.top();
2573 }
2574
2575 return QPoint(x, y);
2576 }
2577
clientPosToFramePos(const QPoint & point) const2578 QPoint X11Client::clientPosToFramePos(const QPoint &point) const
2579 {
2580 int x = point.x();
2581 int y = point.y();
2582
2583 if (isDecorated()) {
2584 x -= borderLeft();
2585 y -= borderTop();
2586 } else {
2587 x += m_clientFrameExtents.left();
2588 y += m_clientFrameExtents.top();
2589 }
2590
2591 return QPoint(x, y);
2592 }
2593
frameSizeToClientSize(const QSize & size) const2594 QSize X11Client::frameSizeToClientSize(const QSize &size) const
2595 {
2596 int width = size.width();
2597 int height = size.height();
2598
2599 if (isDecorated()) {
2600 width -= borderLeft() + borderRight();
2601 height -= borderTop() + borderBottom();
2602 } else {
2603 width += m_clientFrameExtents.left() + m_clientFrameExtents.right();
2604 height += m_clientFrameExtents.top() + m_clientFrameExtents.bottom();
2605 }
2606
2607 return QSize(width, height);
2608 }
2609
clientSizeToFrameSize(const QSize & size) const2610 QSize X11Client::clientSizeToFrameSize(const QSize &size) const
2611 {
2612 int width = size.width();
2613 int height = size.height();
2614
2615 if (isDecorated()) {
2616 width += borderLeft() + borderRight();
2617 height += borderTop() + borderBottom();
2618 } else {
2619 width -= m_clientFrameExtents.left() + m_clientFrameExtents.right();
2620 height -= m_clientFrameExtents.top() + m_clientFrameExtents.bottom();
2621 }
2622
2623 return QSize(width, height);
2624 }
2625
frameRectToBufferRect(const QRect & rect) const2626 QRect X11Client::frameRectToBufferRect(const QRect &rect) const
2627 {
2628 if (isDecorated()) {
2629 return rect;
2630 }
2631 return frameRectToClientRect(rect);
2632 }
2633
inputTransformation() const2634 QMatrix4x4 X11Client::inputTransformation() const
2635 {
2636 QMatrix4x4 matrix;
2637 matrix.translate(-m_bufferGeometry.x(), -m_bufferGeometry.y());
2638 return matrix;
2639 }
2640
fetchShowOnScreenEdge() const2641 Xcb::Property X11Client::fetchShowOnScreenEdge() const
2642 {
2643 return Xcb::Property(false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1);
2644 }
2645
readShowOnScreenEdge(Xcb::Property & property)2646 void X11Client::readShowOnScreenEdge(Xcb::Property &property)
2647 {
2648 //value comes in two parts, edge in the lower byte
2649 //then the type in the upper byte
2650 // 0 = autohide
2651 // 1 = raise in front on activate
2652
2653 const uint32_t value = property.value<uint32_t>(ElectricNone);
2654 ElectricBorder border = ElectricNone;
2655 switch (value & 0xFF) {
2656 case 0:
2657 border = ElectricTop;
2658 break;
2659 case 1:
2660 border = ElectricRight;
2661 break;
2662 case 2:
2663 border = ElectricBottom;
2664 break;
2665 case 3:
2666 border = ElectricLeft;
2667 break;
2668 }
2669 if (border != ElectricNone) {
2670 disconnect(m_edgeRemoveConnection);
2671 disconnect(m_edgeGeometryTrackingConnection);
2672 bool successfullyHidden = false;
2673
2674 if (((value >> 8) & 0xFF) == 1) {
2675 setKeepBelow(true);
2676 successfullyHidden = keepBelow(); //request could have failed due to user kwin rules
2677
2678 m_edgeRemoveConnection = connect(this, &AbstractClient::keepBelowChanged, this, [this](){
2679 if (!keepBelow()) {
2680 ScreenEdges::self()->reserve(this, ElectricNone);
2681 }
2682 });
2683 } else {
2684 hideClient(true);
2685 successfullyHidden = isHiddenInternal();
2686
2687 m_edgeGeometryTrackingConnection = connect(this, &X11Client::frameGeometryChanged, this, [this, border](){
2688 hideClient(true);
2689 ScreenEdges::self()->reserve(this, border);
2690 });
2691 }
2692
2693 if (successfullyHidden) {
2694 ScreenEdges::self()->reserve(this, border);
2695 } else {
2696 ScreenEdges::self()->reserve(this, ElectricNone);
2697 }
2698 } else if (!property.isNull() && property->type != XCB_ATOM_NONE) {
2699 // property value is incorrect, delete the property
2700 // so that the client knows that it is not hidden
2701 xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show);
2702 } else {
2703 // restore
2704 // TODO: add proper unreserve
2705
2706 //this will call showOnScreenEdge to reset the state
2707 disconnect(m_edgeGeometryTrackingConnection);
2708 ScreenEdges::self()->reserve(this, ElectricNone);
2709 }
2710 }
2711
updateShowOnScreenEdge()2712 void X11Client::updateShowOnScreenEdge()
2713 {
2714 Xcb::Property property = fetchShowOnScreenEdge();
2715 readShowOnScreenEdge(property);
2716 }
2717
showOnScreenEdge()2718 void X11Client::showOnScreenEdge()
2719 {
2720 disconnect(m_edgeRemoveConnection);
2721
2722 hideClient(false);
2723 setKeepBelow(false);
2724 xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show);
2725 }
2726
belongsToSameApplication(const AbstractClient * other,SameApplicationChecks checks) const2727 bool X11Client::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const
2728 {
2729 const X11Client *c2 = dynamic_cast<const X11Client *>(other);
2730 if (!c2) {
2731 return false;
2732 }
2733 return X11Client::belongToSameApplication(this, c2, checks);
2734 }
2735
resizeIncrements() const2736 QSize X11Client::resizeIncrements() const
2737 {
2738 return m_geometryHints.resizeIncrements();
2739 }
2740
fetchApplicationMenuServiceName() const2741 Xcb::StringProperty X11Client::fetchApplicationMenuServiceName() const
2742 {
2743 return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_service_name);
2744 }
2745
readApplicationMenuServiceName(Xcb::StringProperty & property)2746 void X11Client::readApplicationMenuServiceName(Xcb::StringProperty &property)
2747 {
2748 updateApplicationMenuServiceName(QString::fromUtf8(property));
2749 }
2750
checkApplicationMenuServiceName()2751 void X11Client::checkApplicationMenuServiceName()
2752 {
2753 Xcb::StringProperty property = fetchApplicationMenuServiceName();
2754 readApplicationMenuServiceName(property);
2755 }
2756
fetchApplicationMenuObjectPath() const2757 Xcb::StringProperty X11Client::fetchApplicationMenuObjectPath() const
2758 {
2759 return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_object_path);
2760 }
2761
readApplicationMenuObjectPath(Xcb::StringProperty & property)2762 void X11Client::readApplicationMenuObjectPath(Xcb::StringProperty &property)
2763 {
2764 updateApplicationMenuObjectPath(QString::fromUtf8(property));
2765 }
2766
checkApplicationMenuObjectPath()2767 void X11Client::checkApplicationMenuObjectPath()
2768 {
2769 Xcb::StringProperty property = fetchApplicationMenuObjectPath();
2770 readApplicationMenuObjectPath(property);
2771 }
2772
handleSync()2773 void X11Client::handleSync()
2774 {
2775 setReadyForPainting();
2776 setupWindowManagementInterface();
2777 m_syncRequest.isPending = false;
2778 if (m_syncRequest.failsafeTimeout) {
2779 m_syncRequest.failsafeTimeout->stop();
2780 }
2781 if (isInteractiveResize()) {
2782 if (m_syncRequest.timeout) {
2783 m_syncRequest.timeout->stop();
2784 }
2785 performInteractiveMoveResize();
2786 updateWindowPixmap();
2787 } else // setReadyForPainting does as well, but there's a small chance for resize syncs after the resize ended
2788 addRepaintFull();
2789 }
2790
belongToSameApplication(const X11Client * c1,const X11Client * c2,SameApplicationChecks checks)2791 bool X11Client::belongToSameApplication(const X11Client *c1, const X11Client *c2, SameApplicationChecks checks)
2792 {
2793 bool same_app = false;
2794
2795 // tests that definitely mean they belong together
2796 if (c1 == c2)
2797 same_app = true;
2798 else if (c1->isTransient() && c2->hasTransient(c1, true))
2799 same_app = true; // c1 has c2 as mainwindow
2800 else if (c2->isTransient() && c1->hasTransient(c2, true))
2801 same_app = true; // c2 has c1 as mainwindow
2802 else if (c1->group() == c2->group())
2803 same_app = true; // same group
2804 else if (c1->wmClientLeader() == c2->wmClientLeader()
2805 && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
2806 && c2->wmClientLeader() != c2->window()) // don't use in this test then
2807 same_app = true; // same client leader
2808
2809 // tests that mean they most probably don't belong together
2810 else if ((c1->pid() != c2->pid() && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses))
2811 || c1->wmClientMachine(false) != c2->wmClientMachine(false))
2812 ; // different processes
2813 else if (c1->wmClientLeader() != c2->wmClientLeader()
2814 && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
2815 && c2->wmClientLeader() != c2->window() // don't use in this test then
2816 && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses))
2817 ; // different client leader
2818 else if (!resourceMatch(c1, c2))
2819 ; // different apps
2820 else if (!sameAppWindowRoleMatch(c1, c2, checks.testFlag(SameApplicationCheck::RelaxedForActive))
2821 && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses))
2822 ; // "different" apps
2823 else if (c1->pid() == 0 || c2->pid() == 0)
2824 ; // old apps that don't have _NET_WM_PID, consider them different
2825 // if they weren't found to match above
2826 else
2827 same_app = true; // looks like it's the same app
2828
2829 return same_app;
2830 }
2831
2832 // Non-transient windows with window role containing '#' are always
2833 // considered belonging to different applications (unless
2834 // the window role is exactly the same). KMainWindow sets
2835 // window role this way by default, and different KMainWindow
2836 // usually "are" different application from user's point of view.
2837 // This help with no-focus-stealing for e.g. konqy reusing.
2838 // On the other hand, if one of the windows is active, they are
2839 // considered belonging to the same application. This is for
2840 // the cases when opening new mainwindow directly from the application,
2841 // e.g. 'Open New Window' in konqy ( active_hack == true ).
sameAppWindowRoleMatch(const X11Client * c1,const X11Client * c2,bool active_hack)2842 bool X11Client::sameAppWindowRoleMatch(const X11Client *c1, const X11Client *c2, bool active_hack)
2843 {
2844 if (c1->isTransient()) {
2845 while (const X11Client *t = dynamic_cast<const X11Client *>(c1->transientFor()))
2846 c1 = t;
2847 if (c1->groupTransient())
2848 return c1->group() == c2->group();
2849 #if 0
2850 // if a group transient is in its own group, it didn't possibly have a group,
2851 // and therefore should be considered belonging to the same app like
2852 // all other windows from the same app
2853 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
2854 #endif
2855 }
2856 if (c2->isTransient()) {
2857 while (const X11Client *t = dynamic_cast<const X11Client *>(c2->transientFor()))
2858 c2 = t;
2859 if (c2->groupTransient())
2860 return c1->group() == c2->group();
2861 #if 0
2862 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
2863 #endif
2864 }
2865 int pos1 = c1->windowRole().indexOf('#');
2866 int pos2 = c2->windowRole().indexOf('#');
2867 if ((pos1 >= 0 && pos2 >= 0)) {
2868 if (!active_hack) // without the active hack for focus stealing prevention,
2869 return c1 == c2; // different mainwindows are always different apps
2870 if (!c1->isActive() && !c2->isActive())
2871 return c1 == c2;
2872 else
2873 return true;
2874 }
2875 return true;
2876 }
2877
2878 /*
2879
2880 Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3
2881
2882 WM_TRANSIENT_FOR is basically means "this is my mainwindow".
2883 For NET::Unknown windows, transient windows are considered to be NET::Dialog
2884 windows, for compatibility with non-NETWM clients. KWin may adjust the value
2885 of this property in some cases (window pointing to itself or creating a loop,
2886 keeping NET::Splash windows above other windows from the same app, etc.).
2887
2888 X11Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after
2889 possibly being adjusted by KWin. X11Client::transient_for points to the Client
2890 this Client is transient for, or is NULL. If X11Client::transient_for_id is
2891 poiting to the root window, the window is considered to be transient
2892 for the whole window group, as suggested in NETWM 7.3.
2893
2894 In the case of group transient window, X11Client::transient_for is NULL,
2895 and X11Client::groupTransient() returns true. Such window is treated as
2896 if it were transient for every window in its window group that has been
2897 mapped _before_ it (or, to be exact, was added to the same group before it).
2898 Otherwise two group transients can create loops, which can lead very very
2899 nasty things (bug #67914 and all its dupes).
2900
2901 X11Client::original_transient_for_id is the value of the property, which
2902 may be different if X11Client::transient_for_id if e.g. forcing NET::Splash
2903 to be kept on top of its window group, or when the mainwindow is not mapped
2904 yet, in which case the window is temporarily made group transient,
2905 and when the mainwindow is mapped, transiency is re-evaluated.
2906
2907 This can get a bit complicated with with e.g. two Konqueror windows created
2908 by the same process. They should ideally appear like two independent applications
2909 to the user. This should be accomplished by all windows in the same process
2910 having the same window group (needs to be changed in Qt at the moment), and
2911 using non-group transients poiting to their relevant mainwindow for toolwindows
2912 etc. KWin should handle both group and non-group transient dialogs well.
2913
2914 In other words:
2915 - non-transient windows : isTransient() == false
2916 - normal transients : transientFor() != NULL
2917 - group transients : groupTransient() == true
2918
2919 - list of mainwindows : mainClients() (call once and loop over the result)
2920 - list of transients : transients()
2921 - every window in the group : group()->members()
2922 */
2923
fetchTransient() const2924 Xcb::TransientFor X11Client::fetchTransient() const
2925 {
2926 return Xcb::TransientFor(window());
2927 }
2928
readTransientProperty(Xcb::TransientFor & transientFor)2929 void X11Client::readTransientProperty(Xcb::TransientFor &transientFor)
2930 {
2931 xcb_window_t new_transient_for_id = XCB_WINDOW_NONE;
2932 if (transientFor.getTransientFor(&new_transient_for_id)) {
2933 m_originalTransientForId = new_transient_for_id;
2934 new_transient_for_id = verifyTransientFor(new_transient_for_id, true);
2935 } else {
2936 m_originalTransientForId = XCB_WINDOW_NONE;
2937 new_transient_for_id = verifyTransientFor(XCB_WINDOW_NONE, false);
2938 }
2939 setTransient(new_transient_for_id);
2940 }
2941
readTransient()2942 void X11Client::readTransient()
2943 {
2944 Xcb::TransientFor transientFor = fetchTransient();
2945 readTransientProperty(transientFor);
2946 }
2947
setTransient(xcb_window_t new_transient_for_id)2948 void X11Client::setTransient(xcb_window_t new_transient_for_id)
2949 {
2950 if (new_transient_for_id != m_transientForId) {
2951 removeFromMainClients();
2952 X11Client *transient_for = nullptr;
2953 m_transientForId = new_transient_for_id;
2954 if (m_transientForId != XCB_WINDOW_NONE && !groupTransient()) {
2955 transient_for = workspace()->findClient(Predicate::WindowMatch, m_transientForId);
2956 Q_ASSERT(transient_for != nullptr); // verifyTransient() had to check this
2957 transient_for->addTransient(this);
2958 } // checkGroup() will check 'check_active_modal'
2959 setTransientFor(transient_for);
2960 checkGroup(nullptr, true); // force, because transiency has changed
2961 updateLayer();
2962 workspace()->resetUpdateToolWindowsTimer();
2963 Q_EMIT transientChanged();
2964 }
2965 }
2966
removeFromMainClients()2967 void X11Client::removeFromMainClients()
2968 {
2969 if (transientFor())
2970 transientFor()->removeTransient(this);
2971 if (groupTransient()) {
2972 for (auto it = group()->members().constBegin();
2973 it != group()->members().constEnd();
2974 ++it)
2975 (*it)->removeTransient(this);
2976 }
2977 }
2978
2979 // *sigh* this transiency handling is madness :(
2980 // This one is called when destroying/releasing a window.
2981 // It makes sure this client is removed from all grouping
2982 // related lists.
cleanGrouping()2983 void X11Client::cleanGrouping()
2984 {
2985 // qDebug() << "CLEANGROUPING:" << this;
2986 // for ( auto it = group()->members().begin();
2987 // it != group()->members().end();
2988 // ++it )
2989 // qDebug() << "CL:" << *it;
2990 // QList<X11Client *> mains;
2991 // mains = mainClients();
2992 // for ( auto it = mains.begin();
2993 // it != mains.end();
2994 // ++it )
2995 // qDebug() << "MN:" << *it;
2996 removeFromMainClients();
2997 // qDebug() << "CLEANGROUPING2:" << this;
2998 // for ( auto it = group()->members().begin();
2999 // it != group()->members().end();
3000 // ++it )
3001 // qDebug() << "CL2:" << *it;
3002 // mains = mainClients();
3003 // for ( auto it = mains.begin();
3004 // it != mains.end();
3005 // ++it )
3006 // qDebug() << "MN2:" << *it;
3007 for (auto it = transients().constBegin();
3008 it != transients().constEnd();
3009 ) {
3010 if ((*it)->transientFor() == this) {
3011 removeTransient(*it);
3012 it = transients().constBegin(); // restart, just in case something more has changed with the list
3013 } else
3014 ++it;
3015 }
3016 // qDebug() << "CLEANGROUPING3:" << this;
3017 // for ( auto it = group()->members().begin();
3018 // it != group()->members().end();
3019 // ++it )
3020 // qDebug() << "CL3:" << *it;
3021 // mains = mainClients();
3022 // for ( auto it = mains.begin();
3023 // it != mains.end();
3024 // ++it )
3025 // qDebug() << "MN3:" << *it;
3026 // HACK
3027 // removeFromMainClients() did remove 'this' from transient
3028 // lists of all group members, but then made windows that
3029 // were transient for 'this' group transient, which again
3030 // added 'this' to those transient lists :(
3031 QList<X11Client *> group_members = group()->members();
3032 group()->removeMember(this);
3033 in_group = nullptr;
3034 for (auto it = group_members.constBegin();
3035 it != group_members.constEnd();
3036 ++it)
3037 (*it)->removeTransient(this);
3038 // qDebug() << "CLEANGROUPING4:" << this;
3039 // for ( auto it = group_members.begin();
3040 // it != group_members.end();
3041 // ++it )
3042 // qDebug() << "CL4:" << *it;
3043 m_transientForId = XCB_WINDOW_NONE;
3044 }
3045
3046 // Make sure that no group transient is considered transient
3047 // for a window that is (directly or indirectly) transient for it
3048 // (including another group transients).
3049 // Non-group transients not causing loops are checked in verifyTransientFor().
checkGroupTransients()3050 void X11Client::checkGroupTransients()
3051 {
3052 for (auto it1 = group()->members().constBegin();
3053 it1 != group()->members().constEnd();
3054 ++it1) {
3055 if (!(*it1)->groupTransient()) // check all group transients in the group
3056 continue; // TODO optimize to check only the changed ones?
3057 for (auto it2 = group()->members().constBegin();
3058 it2 != group()->members().constEnd();
3059 ++it2) { // group transients can be transient only for others in the group,
3060 // so don't make them transient for the ones that are transient for it
3061 if (*it1 == *it2)
3062 continue;
3063 for (AbstractClient* cl = (*it2)->transientFor();
3064 cl != nullptr;
3065 cl = cl->transientFor()) {
3066 if (cl == *it1) {
3067 // don't use removeTransient(), that would modify *it2 too
3068 (*it2)->removeTransientFromList(*it1);
3069 continue;
3070 }
3071 }
3072 // if *it1 and *it2 are both group transients, and are transient for each other,
3073 // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later,
3074 // and should be therefore on top of *it1
3075 // TODO This could possibly be optimized, it also requires hasTransient() to check for loops.
3076 if ((*it2)->groupTransient() && (*it1)->hasTransient(*it2, true) && (*it2)->hasTransient(*it1, true))
3077 (*it2)->removeTransientFromList(*it1);
3078 // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3
3079 // is added, make it transient only for W2, not for W1, because it's already indirectly
3080 // transient for it - the indirect transiency actually shouldn't break anything,
3081 // but it can lead to exponentially expensive operations (#95231)
3082 // TODO this is pretty slow as well
3083 for (auto it3 = group()->members().constBegin();
3084 it3 != group()->members().constEnd();
3085 ++it3) {
3086 if (*it1 == *it2 || *it2 == *it3 || *it1 == *it3)
3087 continue;
3088 if ((*it2)->hasTransient(*it1, false) && (*it3)->hasTransient(*it1, false)) {
3089 if ((*it2)->hasTransient(*it3, true))
3090 (*it2)->removeTransientFromList(*it1);
3091 if ((*it3)->hasTransient(*it2, true))
3092 (*it3)->removeTransientFromList(*it1);
3093 }
3094 }
3095 }
3096 }
3097 }
3098
3099 /**
3100 * Check that the window is not transient for itself, and similar nonsense.
3101 */
verifyTransientFor(xcb_window_t new_transient_for,bool set)3102 xcb_window_t X11Client::verifyTransientFor(xcb_window_t new_transient_for, bool set)
3103 {
3104 xcb_window_t new_property_value = new_transient_for;
3105 // make sure splashscreens are shown above all their app's windows, even though
3106 // they're in Normal layer
3107 if (isSplash() && new_transient_for == XCB_WINDOW_NONE)
3108 new_transient_for = rootWindow();
3109 if (new_transient_for == XCB_WINDOW_NONE) {
3110 if (set) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window
3111 new_property_value = new_transient_for = rootWindow();
3112 else
3113 return XCB_WINDOW_NONE;
3114 }
3115 if (new_transient_for == window()) { // pointing to self
3116 // also fix the property itself
3117 qCWarning(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." ;
3118 new_property_value = new_transient_for = rootWindow();
3119 }
3120 // The transient_for window may be embedded in another application,
3121 // so kwin cannot see it. Try to find the managed client for the
3122 // window and fix the transient_for property if possible.
3123 xcb_window_t before_search = new_transient_for;
3124 while (new_transient_for != XCB_WINDOW_NONE
3125 && new_transient_for != rootWindow()
3126 && !workspace()->findClient(Predicate::WindowMatch, new_transient_for)) {
3127 Xcb::Tree tree(new_transient_for);
3128 if (tree.isNull()) {
3129 break;
3130 }
3131 new_transient_for = tree->parent;
3132 }
3133 if (X11Client *new_transient_for_client = workspace()->findClient(Predicate::WindowMatch, new_transient_for)) {
3134 if (new_transient_for != before_search) {
3135 qCDebug(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window "
3136 << before_search << ", child of " << new_transient_for_client << ", adjusting.";
3137 new_property_value = new_transient_for; // also fix the property
3138 }
3139 } else
3140 new_transient_for = before_search; // nice try
3141 // loop detection
3142 // group transients cannot cause loops, because they're considered transient only for non-transient
3143 // windows in the group
3144 int count = 20;
3145 xcb_window_t loop_pos = new_transient_for;
3146 while (loop_pos != XCB_WINDOW_NONE && loop_pos != rootWindow()) {
3147 X11Client *pos = workspace()->findClient(Predicate::WindowMatch, loop_pos);
3148 if (pos == nullptr)
3149 break;
3150 loop_pos = pos->m_transientForId;
3151 if (--count == 0 || pos == this) {
3152 qCWarning(KWIN_CORE) << "Client " << this << " caused WM_TRANSIENT_FOR loop." ;
3153 new_transient_for = rootWindow();
3154 }
3155 }
3156 if (new_transient_for != rootWindow()
3157 && workspace()->findClient(Predicate::WindowMatch, new_transient_for) == nullptr) {
3158 // it's transient for a specific window, but that window is not mapped
3159 new_transient_for = rootWindow();
3160 }
3161 if (new_property_value != m_originalTransientForId)
3162 Xcb::setTransientFor(window(), new_property_value);
3163 return new_transient_for;
3164 }
3165
addTransient(AbstractClient * cl)3166 void X11Client::addTransient(AbstractClient* cl)
3167 {
3168 AbstractClient::addTransient(cl);
3169 if (workspace()->mostRecentlyActivatedClient() == this && cl->isModal())
3170 check_active_modal = true;
3171 // qDebug() << "ADDTRANS:" << this << ":" << cl;
3172 // qDebug() << kBacktrace();
3173 // for ( auto it = transients_list.begin();
3174 // it != transients_list.end();
3175 // ++it )
3176 // qDebug() << "AT:" << (*it);
3177 }
3178
removeTransient(AbstractClient * cl)3179 void X11Client::removeTransient(AbstractClient* cl)
3180 {
3181 // qDebug() << "REMOVETRANS:" << this << ":" << cl;
3182 // qDebug() << kBacktrace();
3183 // cl is transient for this, but this is going away
3184 // make cl group transient
3185 AbstractClient::removeTransient(cl);
3186 if (cl->transientFor() == this) {
3187 if (X11Client *c = dynamic_cast<X11Client *>(cl)) {
3188 c->m_transientForId = XCB_WINDOW_NONE;
3189 c->setTransientFor(nullptr); // SELI
3190 // SELI cl->setTransient( rootWindow());
3191 c->setTransient(XCB_WINDOW_NONE);
3192 }
3193 }
3194 }
3195
3196 // A new window has been mapped. Check if it's not a mainwindow for this already existing window.
checkTransient(xcb_window_t w)3197 void X11Client::checkTransient(xcb_window_t w)
3198 {
3199 if (m_originalTransientForId != w)
3200 return;
3201 w = verifyTransientFor(w, true);
3202 setTransient(w);
3203 }
3204
3205 // returns true if cl is the transient_for window for this client,
3206 // or recursively the transient_for window
hasTransient(const AbstractClient * cl,bool indirect) const3207 bool X11Client::hasTransient(const AbstractClient* cl, bool indirect) const
3208 {
3209 if (const X11Client *c = dynamic_cast<const X11Client *>(cl)) {
3210 // checkGroupTransients() uses this to break loops, so hasTransient() must detect them
3211 QList<const X11Client *> set;
3212 return hasTransientInternal(c, indirect, set);
3213 }
3214 return false;
3215 }
3216
hasTransientInternal(const X11Client * cl,bool indirect,QList<const X11Client * > & set) const3217 bool X11Client::hasTransientInternal(const X11Client *cl, bool indirect, QList<const X11Client *> &set) const
3218 {
3219 if (const X11Client *t = dynamic_cast<const X11Client *>(cl->transientFor())) {
3220 if (t == this)
3221 return true;
3222 if (!indirect)
3223 return false;
3224 if (set.contains(cl))
3225 return false;
3226 set.append(cl);
3227 return hasTransientInternal(t, indirect, set);
3228 }
3229 if (!cl->isTransient())
3230 return false;
3231 if (group() != cl->group())
3232 return false;
3233 // cl is group transient, search from top
3234 if (transients().contains(const_cast< X11Client *>(cl)))
3235 return true;
3236 if (!indirect)
3237 return false;
3238 if (set.contains(this))
3239 return false;
3240 set.append(this);
3241 for (auto it = transients().constBegin();
3242 it != transients().constEnd();
3243 ++it) {
3244 const X11Client *c = qobject_cast<const X11Client *>(*it);
3245 if (!c) {
3246 continue;
3247 }
3248 if (c->hasTransientInternal(cl, indirect, set))
3249 return true;
3250 }
3251 return false;
3252 }
3253
mainClients() const3254 QList<AbstractClient*> X11Client::mainClients() const
3255 {
3256 if (!isTransient())
3257 return QList<AbstractClient*>();
3258 if (const AbstractClient *t = transientFor())
3259 return QList<AbstractClient*>{const_cast< AbstractClient* >(t)};
3260 QList<AbstractClient*> result;
3261 Q_ASSERT(group());
3262 for (auto it = group()->members().constBegin();
3263 it != group()->members().constEnd();
3264 ++it)
3265 if ((*it)->hasTransient(this, false))
3266 result.append(*it);
3267 return result;
3268 }
3269
findModal(bool allow_itself)3270 AbstractClient* X11Client::findModal(bool allow_itself)
3271 {
3272 for (auto it = transients().constBegin();
3273 it != transients().constEnd();
3274 ++it)
3275 if (AbstractClient* ret = (*it)->findModal(true))
3276 return ret;
3277 if (isModal() && allow_itself)
3278 return this;
3279 return nullptr;
3280 }
3281
3282 // X11Client::window_group only holds the contents of the hint,
3283 // but it should be used only to find the group, not for anything else
3284 // Argument is only when some specific group needs to be set.
checkGroup(Group * set_group,bool force)3285 void X11Client::checkGroup(Group* set_group, bool force)
3286 {
3287 Group* old_group = in_group;
3288 if (old_group != nullptr)
3289 old_group->ref(); // turn off automatic deleting
3290 if (set_group != nullptr) {
3291 if (set_group != in_group) {
3292 if (in_group != nullptr)
3293 in_group->removeMember(this);
3294 in_group = set_group;
3295 in_group->addMember(this);
3296 }
3297 } else if (info->groupLeader() != XCB_WINDOW_NONE) {
3298 Group* new_group = workspace()->findGroup(info->groupLeader());
3299 X11Client *t = qobject_cast<X11Client *>(transientFor());
3300 if (t != nullptr && t->group() != new_group) {
3301 // move the window to the right group (e.g. a dialog provided
3302 // by different app, but transient for this one, so make it part of that group)
3303 new_group = t->group();
3304 }
3305 if (new_group == nullptr) // doesn't exist yet
3306 new_group = new Group(info->groupLeader());
3307 if (new_group != in_group) {
3308 if (in_group != nullptr)
3309 in_group->removeMember(this);
3310 in_group = new_group;
3311 in_group->addMember(this);
3312 }
3313 } else {
3314 if (X11Client *t = qobject_cast<X11Client *>(transientFor())) {
3315 // doesn't have window group set, but is transient for something
3316 // so make it part of that group
3317 Group* new_group = t->group();
3318 if (new_group != in_group) {
3319 if (in_group != nullptr)
3320 in_group->removeMember(this);
3321 in_group = t->group();
3322 in_group->addMember(this);
3323 }
3324 } else if (groupTransient()) {
3325 // group transient which actually doesn't have a group :(
3326 // try creating group with other windows with the same client leader
3327 Group* new_group = workspace()->findClientLeaderGroup(this);
3328 if (new_group == nullptr)
3329 new_group = new Group(XCB_WINDOW_NONE);
3330 if (new_group != in_group) {
3331 if (in_group != nullptr)
3332 in_group->removeMember(this);
3333 in_group = new_group;
3334 in_group->addMember(this);
3335 }
3336 } else { // Not transient without a group, put it in its client leader group.
3337 // This might be stupid if grouping was used for e.g. taskbar grouping
3338 // or minimizing together the whole group, but as long as it is used
3339 // only for dialogs it's better to keep windows from one app in one group.
3340 Group* new_group = workspace()->findClientLeaderGroup(this);
3341 if (in_group != nullptr && in_group != new_group) {
3342 in_group->removeMember(this);
3343 in_group = nullptr;
3344 }
3345 if (new_group == nullptr)
3346 new_group = new Group(XCB_WINDOW_NONE);
3347 if (in_group != new_group) {
3348 in_group = new_group;
3349 in_group->addMember(this);
3350 }
3351 }
3352 }
3353 if (in_group != old_group || force) {
3354 for (auto it = transients().constBegin();
3355 it != transients().constEnd();
3356 ) {
3357 auto *c = *it;
3358 // group transients in the old group are no longer transient for it
3359 if (c->groupTransient() && c->group() != group()) {
3360 removeTransientFromList(c);
3361 it = transients().constBegin(); // restart, just in case something more has changed with the list
3362 } else
3363 ++it;
3364 }
3365 if (groupTransient()) {
3366 // no longer transient for ones in the old group
3367 if (old_group != nullptr) {
3368 for (auto it = old_group->members().constBegin();
3369 it != old_group->members().constEnd();
3370 ++it)
3371 (*it)->removeTransient(this);
3372 }
3373 // and make transient for all in the new group
3374 for (auto it = group()->members().constBegin();
3375 it != group()->members().constEnd();
3376 ++it) {
3377 if (*it == this)
3378 break; // this means the window is only transient for windows mapped before it
3379 (*it)->addTransient(this);
3380 }
3381 }
3382 // group transient splashscreens should be transient even for windows
3383 // in group mapped later
3384 for (auto it = group()->members().constBegin();
3385 it != group()->members().constEnd();
3386 ++it) {
3387 if (!(*it)->isSplash())
3388 continue;
3389 if (!(*it)->groupTransient())
3390 continue;
3391 if (*it == this || hasTransient(*it, true)) // TODO indirect?
3392 continue;
3393 addTransient(*it);
3394 }
3395 }
3396 if (old_group != nullptr)
3397 old_group->deref(); // can be now deleted if empty
3398 checkGroupTransients();
3399 checkActiveModal();
3400 updateLayer();
3401 }
3402
3403 // used by Workspace::findClientLeaderGroup()
changeClientLeaderGroup(Group * gr)3404 void X11Client::changeClientLeaderGroup(Group* gr)
3405 {
3406 // transientFor() != NULL are in the group of their mainwindow, so keep them there
3407 if (transientFor() != nullptr)
3408 return;
3409 // also don't change the group for window which have group set
3410 if (info->groupLeader())
3411 return;
3412 checkGroup(gr); // change group
3413 }
3414
3415 bool X11Client::check_active_modal = false;
3416
checkActiveModal()3417 void X11Client::checkActiveModal()
3418 {
3419 // if the active window got new modal transient, activate it.
3420 // cannot be done in AddTransient(), because there may temporarily
3421 // exist loops, breaking findModal
3422 X11Client *check_modal = dynamic_cast<X11Client *>(workspace()->mostRecentlyActivatedClient());
3423 if (check_modal != nullptr && check_modal->check_active_modal) {
3424 X11Client *new_modal = dynamic_cast<X11Client *>(check_modal->findModal());
3425 if (new_modal != nullptr && new_modal != check_modal) {
3426 if (!new_modal->isManaged())
3427 return; // postpone check until end of manage()
3428 workspace()->activateClient(new_modal);
3429 }
3430 check_modal->check_active_modal = false;
3431 }
3432 }
3433
constrainClientSize(const QSize & size,SizeMode mode) const3434 QSize X11Client::constrainClientSize(const QSize &size, SizeMode mode) const
3435 {
3436 int w = size.width();
3437 int h = size.height();
3438
3439 if (w < 1) w = 1;
3440 if (h < 1) h = 1;
3441
3442 // basesize, minsize, maxsize, paspect and resizeinc have all values defined,
3443 // even if they're not set in flags - see getWmNormalHints()
3444 QSize min_size = minSize();
3445 QSize max_size = maxSize();
3446 if (isDecorated()) {
3447 QSize decominsize(0, 0);
3448 QSize border_size(borderLeft() + borderRight(), borderTop() + borderBottom());
3449 if (border_size.width() > decominsize.width()) // just in case
3450 decominsize.setWidth(border_size.width());
3451 if (border_size.height() > decominsize.height())
3452 decominsize.setHeight(border_size.height());
3453 if (decominsize.width() > min_size.width())
3454 min_size.setWidth(decominsize.width());
3455 if (decominsize.height() > min_size.height())
3456 min_size.setHeight(decominsize.height());
3457 }
3458 w = qMin(max_size.width(), w);
3459 h = qMin(max_size.height(), h);
3460 w = qMax(min_size.width(), w);
3461 h = qMax(min_size.height(), h);
3462
3463 if (!rules()->checkStrictGeometry(!isFullScreen())) {
3464 // Disobey increments and aspect by explicit rule.
3465 return QSize(w, h);
3466 }
3467
3468 int width_inc = m_geometryHints.resizeIncrements().width();
3469 int height_inc = m_geometryHints.resizeIncrements().height();
3470 int basew_inc = m_geometryHints.baseSize().width();
3471 int baseh_inc = m_geometryHints.baseSize().height();
3472 if (!m_geometryHints.hasBaseSize()) {
3473 basew_inc = m_geometryHints.minSize().width();
3474 baseh_inc = m_geometryHints.minSize().height();
3475 }
3476 w = int((w - basew_inc) / width_inc) * width_inc + basew_inc;
3477 h = int((h - baseh_inc) / height_inc) * height_inc + baseh_inc;
3478 // code for aspect ratios based on code from FVWM
3479 /*
3480 * The math looks like this:
3481 *
3482 * minAspectX dwidth maxAspectX
3483 * ---------- <= ------- <= ----------
3484 * minAspectY dheight maxAspectY
3485 *
3486 * If that is multiplied out, then the width and height are
3487 * invalid in the following situations:
3488 *
3489 * minAspectX * dheight > minAspectY * dwidth
3490 * maxAspectX * dheight < maxAspectY * dwidth
3491 *
3492 */
3493 if (m_geometryHints.hasAspect()) {
3494 double min_aspect_w = m_geometryHints.minAspect().width(); // use doubles, because the values can be MAX_INT
3495 double min_aspect_h = m_geometryHints.minAspect().height(); // and multiplying would go wrong otherwise
3496 double max_aspect_w = m_geometryHints.maxAspect().width();
3497 double max_aspect_h = m_geometryHints.maxAspect().height();
3498 // According to ICCCM 4.1.2.3 PMinSize should be a fallback for PBaseSize for size increments,
3499 // but not for aspect ratio. Since this code comes from FVWM, handles both at the same time,
3500 // and I have no idea how it works, let's hope nobody relies on that.
3501 const QSize baseSize = m_geometryHints.baseSize();
3502 w -= baseSize.width();
3503 h -= baseSize.height();
3504 int max_width = max_size.width() - baseSize.width();
3505 int min_width = min_size.width() - baseSize.width();
3506 int max_height = max_size.height() - baseSize.height();
3507 int min_height = min_size.height() - baseSize.height();
3508 #define ASPECT_CHECK_GROW_W \
3509 if ( min_aspect_w * h > min_aspect_h * w ) \
3510 { \
3511 int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \
3512 if ( w + delta <= max_width ) \
3513 w += delta; \
3514 }
3515 #define ASPECT_CHECK_SHRINK_H_GROW_W \
3516 if ( min_aspect_w * h > min_aspect_h * w ) \
3517 { \
3518 int delta = int( h - w * min_aspect_h / min_aspect_w ) / height_inc * height_inc; \
3519 if ( h - delta >= min_height ) \
3520 h -= delta; \
3521 else \
3522 { \
3523 int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \
3524 if ( w + delta <= max_width ) \
3525 w += delta; \
3526 } \
3527 }
3528 #define ASPECT_CHECK_GROW_H \
3529 if ( max_aspect_w * h < max_aspect_h * w ) \
3530 { \
3531 int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \
3532 if ( h + delta <= max_height ) \
3533 h += delta; \
3534 }
3535 #define ASPECT_CHECK_SHRINK_W_GROW_H \
3536 if ( max_aspect_w * h < max_aspect_h * w ) \
3537 { \
3538 int delta = int( w - max_aspect_w * h / max_aspect_h ) / width_inc * width_inc; \
3539 if ( w - delta >= min_width ) \
3540 w -= delta; \
3541 else \
3542 { \
3543 int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \
3544 if ( h + delta <= max_height ) \
3545 h += delta; \
3546 } \
3547 }
3548 switch(mode) {
3549 case SizeModeAny:
3550 #if 0 // make SizeModeAny equal to SizeModeFixedW - prefer keeping fixed width,
3551 // so that changing aspect ratio to a different value and back keeps the same size (#87298)
3552 {
3553 ASPECT_CHECK_SHRINK_H_GROW_W
3554 ASPECT_CHECK_SHRINK_W_GROW_H
3555 ASPECT_CHECK_GROW_H
3556 ASPECT_CHECK_GROW_W
3557 break;
3558 }
3559 #endif
3560 case SizeModeFixedW: {
3561 // the checks are order so that attempts to modify height are first
3562 ASPECT_CHECK_GROW_H
3563 ASPECT_CHECK_SHRINK_H_GROW_W
3564 ASPECT_CHECK_SHRINK_W_GROW_H
3565 ASPECT_CHECK_GROW_W
3566 break;
3567 }
3568 case SizeModeFixedH: {
3569 ASPECT_CHECK_GROW_W
3570 ASPECT_CHECK_SHRINK_W_GROW_H
3571 ASPECT_CHECK_SHRINK_H_GROW_W
3572 ASPECT_CHECK_GROW_H
3573 break;
3574 }
3575 case SizeModeMax: {
3576 // first checks that try to shrink
3577 ASPECT_CHECK_SHRINK_H_GROW_W
3578 ASPECT_CHECK_SHRINK_W_GROW_H
3579 ASPECT_CHECK_GROW_W
3580 ASPECT_CHECK_GROW_H
3581 break;
3582 }
3583 }
3584 #undef ASPECT_CHECK_SHRINK_H_GROW_W
3585 #undef ASPECT_CHECK_SHRINK_W_GROW_H
3586 #undef ASPECT_CHECK_GROW_W
3587 #undef ASPECT_CHECK_GROW_H
3588 w += baseSize.width();
3589 h += baseSize.height();
3590 }
3591
3592 return QSize(w, h);
3593 }
3594
3595 /**
3596 * Gets the client's normal WM hints and reconfigures itself respectively.
3597 */
getWmNormalHints()3598 void X11Client::getWmNormalHints()
3599 {
3600 const bool hadFixedAspect = m_geometryHints.hasAspect();
3601 // roundtrip to X server
3602 m_geometryHints.fetch();
3603 m_geometryHints.read();
3604
3605 if (!hadFixedAspect && m_geometryHints.hasAspect()) {
3606 // align to eventual new constraints
3607 maximize(max_mode);
3608 }
3609 if (isManaged()) {
3610 // update to match restrictions
3611 QSize new_size = adjustedSize();
3612 if (new_size != size() && !isFullScreen()) {
3613 QRect origClientGeometry = m_clientGeometry;
3614 resizeWithChecks(new_size);
3615 if ((!isSpecialWindow() || isToolbar()) && !isFullScreen()) {
3616 // try to keep the window in its xinerama screen if possible,
3617 // if that fails at least keep it visible somewhere
3618 QRect area = workspace()->clientArea(MovementArea, this);
3619 if (area.contains(origClientGeometry))
3620 keepInArea(area);
3621 area = workspace()->clientArea(WorkArea, this);
3622 if (area.contains(origClientGeometry))
3623 keepInArea(area);
3624 }
3625 }
3626 }
3627 updateAllowedActions(); // affects isResizeable()
3628 }
3629
minSize() const3630 QSize X11Client::minSize() const
3631 {
3632 return rules()->checkMinSize(m_geometryHints.minSize());
3633 }
3634
maxSize() const3635 QSize X11Client::maxSize() const
3636 {
3637 return rules()->checkMaxSize(m_geometryHints.maxSize());
3638 }
3639
basicUnit() const3640 QSize X11Client::basicUnit() const
3641 {
3642 return m_geometryHints.resizeIncrements();
3643 }
3644
3645 /**
3646 * Auxiliary function to inform the client about the current window
3647 * configuration.
3648 */
sendSyntheticConfigureNotify()3649 void X11Client::sendSyntheticConfigureNotify()
3650 {
3651 // Every X11 event is 32 bytes (see man xcb_send_event), so XCB will copy
3652 // 32 unconditionally. Use a union to ensure we don't disclose stack memory.
3653 union {
3654 xcb_configure_notify_event_t event;
3655 char buffer[32];
3656 } u;
3657 static_assert(sizeof(u.event) < 32, "wouldn't need the union otherwise");
3658 memset(&u, 0, sizeof(u));
3659 xcb_configure_notify_event_t &c = u.event;
3660 u.event.response_type = XCB_CONFIGURE_NOTIFY;
3661 u.event.event = window();
3662 u.event.window = window();
3663 u.event.x = m_clientGeometry.x();
3664 u.event.y = m_clientGeometry.y();
3665 u.event.width = m_clientGeometry.width();
3666 u.event.height = m_clientGeometry.height();
3667 u.event.border_width = 0;
3668 u.event.above_sibling = XCB_WINDOW_NONE;
3669 u.event.override_redirect = 0;
3670 xcb_send_event(connection(), true, c.event, XCB_EVENT_MASK_STRUCTURE_NOTIFY, reinterpret_cast<const char*>(&u));
3671 xcb_flush(connection());
3672 }
3673
gravityAdjustment(xcb_gravity_t gravity) const3674 QPoint X11Client::gravityAdjustment(xcb_gravity_t gravity) const
3675 {
3676 int dx = 0;
3677 int dy = 0;
3678
3679 // dx, dy specify how the client window moves to make space for the frame.
3680 // In general we have to compute the reference point and from that figure
3681 // out how much we need to shift the client, however given that we ignore
3682 // the border width attribute and the extents of the server-side decoration
3683 // are known in advance, we can simplify the math quite a bit and express
3684 // the required window gravity adjustment in terms of border sizes.
3685 switch(gravity) {
3686 case XCB_GRAVITY_NORTH_WEST: // move down right
3687 default:
3688 dx = borderLeft();
3689 dy = borderTop();
3690 break;
3691 case XCB_GRAVITY_NORTH: // move right
3692 dx = 0;
3693 dy = borderTop();
3694 break;
3695 case XCB_GRAVITY_NORTH_EAST: // move down left
3696 dx = -borderRight();
3697 dy = borderTop();
3698 break;
3699 case XCB_GRAVITY_WEST: // move right
3700 dx = borderLeft();
3701 dy = 0;
3702 break;
3703 case XCB_GRAVITY_CENTER:
3704 dx = (borderLeft() - borderRight()) / 2;
3705 dy = (borderTop() - borderBottom()) / 2;
3706 break;
3707 case XCB_GRAVITY_STATIC: // don't move
3708 dx = 0;
3709 dy = 0;
3710 break;
3711 case XCB_GRAVITY_EAST: // move left
3712 dx = -borderRight();
3713 dy = 0;
3714 break;
3715 case XCB_GRAVITY_SOUTH_WEST: // move up right
3716 dx = borderLeft() ;
3717 dy = -borderBottom();
3718 break;
3719 case XCB_GRAVITY_SOUTH: // move up
3720 dx = 0;
3721 dy = -borderBottom();
3722 break;
3723 case XCB_GRAVITY_SOUTH_EAST: // move up left
3724 dx = -borderRight();
3725 dy = -borderBottom();
3726 break;
3727 }
3728
3729 return QPoint(dx, dy);
3730 }
3731
calculateGravitation(bool invert) const3732 const QPoint X11Client::calculateGravitation(bool invert) const
3733 {
3734 const QPoint adjustment = gravityAdjustment(m_geometryHints.windowGravity());
3735
3736 // translate from client movement to frame movement
3737 const int dx = adjustment.x() - borderLeft();
3738 const int dy = adjustment.y() - borderTop();
3739
3740 if (!invert)
3741 return QPoint(x() + dx, y() + dy);
3742 else
3743 return QPoint(x() - dx, y() - dy);
3744 }
3745
configureRequest(int value_mask,int rx,int ry,int rw,int rh,int gravity,bool from_tool)3746 void X11Client::configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool)
3747 {
3748 const int configurePositionMask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y;
3749 const int configureSizeMask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
3750 const int configureGeometryMask = configurePositionMask | configureSizeMask;
3751
3752 // "maximized" is a user setting -> we do not allow the client to resize itself
3753 // away from this & against the users explicit wish
3754 qCDebug(KWIN_CORE) << this << bool(value_mask & configureGeometryMask) <<
3755 bool(maximizeMode() & MaximizeVertical) <<
3756 bool(maximizeMode() & MaximizeHorizontal);
3757
3758 // we want to (partially) ignore the request when the window is somehow maximized or quicktiled
3759 bool ignore = !app_noborder && (quickTileMode() != QuickTileMode(QuickTileFlag::None) || maximizeMode() != MaximizeRestore);
3760 // however, the user shall be able to force obedience despite and also disobedience in general
3761 ignore = rules()->checkIgnoreGeometry(ignore);
3762 if (!ignore) { // either we're not max'd / q'tiled or the user allowed the client to break that - so break it.
3763 updateQuickTileMode(QuickTileFlag::None);
3764 max_mode = MaximizeRestore;
3765 Q_EMIT quickTileModeChanged();
3766 } else if (!app_noborder && quickTileMode() == QuickTileMode(QuickTileFlag::None) &&
3767 (maximizeMode() == MaximizeVertical || maximizeMode() == MaximizeHorizontal)) {
3768 // ignoring can be, because either we do, or the user does explicitly not want it.
3769 // for partially maximized windows we want to allow configures in the other dimension.
3770 // so we've to ask the user again - to know whether we just ignored for the partial maximization.
3771 // the problem here is, that the user can explicitly permit configure requests - even for maximized windows!
3772 // we cannot distinguish that from passing "false" for partially maximized windows.
3773 ignore = rules()->checkIgnoreGeometry(false);
3774 if (!ignore) { // the user is not interested, so we fix up dimensions
3775 if (maximizeMode() == MaximizeVertical)
3776 value_mask &= ~(XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT);
3777 if (maximizeMode() == MaximizeHorizontal)
3778 value_mask &= ~(XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_WIDTH);
3779 if (!(value_mask & configureGeometryMask)) {
3780 ignore = true; // the modification turned the request void
3781 }
3782 }
3783 }
3784
3785 if (ignore) {
3786 qCDebug(KWIN_CORE) << "DENIED";
3787 return; // nothing to (left) to do for use - bugs #158974, #252314, #321491
3788 }
3789
3790 qCDebug(KWIN_CORE) << "PERMITTED" << this << bool(value_mask & configureGeometryMask);
3791
3792 if (gravity == 0) // default (nonsense) value for the argument
3793 gravity = m_geometryHints.windowGravity();
3794 if (value_mask & configurePositionMask) {
3795 QPoint new_pos = framePosToClientPos(pos());
3796 new_pos -= gravityAdjustment(xcb_gravity_t(gravity));
3797 if (value_mask & XCB_CONFIG_WINDOW_X) {
3798 new_pos.setX(rx);
3799 }
3800 if (value_mask & XCB_CONFIG_WINDOW_Y) {
3801 new_pos.setY(ry);
3802 }
3803 // clever(?) workaround for applications like xv that want to set
3804 // the location to the current location but miscalculate the
3805 // frame size due to kwin being a double-reparenting window
3806 // manager
3807 if (new_pos.x() == m_clientGeometry.x() && new_pos.y() == m_clientGeometry.y()
3808 && gravity == XCB_GRAVITY_NORTH_WEST && !from_tool) {
3809 new_pos.setX(x());
3810 new_pos.setY(y());
3811 }
3812 new_pos += gravityAdjustment(xcb_gravity_t(gravity));
3813 new_pos = clientPosToFramePos(new_pos);
3814
3815 int nw = clientSize().width();
3816 int nh = clientSize().height();
3817 if (value_mask & XCB_CONFIG_WINDOW_WIDTH) {
3818 nw = rw;
3819 }
3820 if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
3821 nh = rh;
3822 }
3823 const QSize requestedClientSize = constrainClientSize(QSize(nw, nh));
3824 QSize requestedFrameSize = clientSizeToFrameSize(requestedClientSize);
3825 requestedFrameSize = rules()->checkSize(requestedFrameSize);
3826 new_pos = rules()->checkPosition(new_pos);
3827
3828 AbstractOutput *newOutput = kwinApp()->platform()->outputAt(QRect(new_pos, requestedFrameSize).center());
3829 if (newOutput != rules()->checkOutput(newOutput)) {
3830 return; // not allowed by rule
3831 }
3832
3833 QRect origClientGeometry = m_clientGeometry;
3834 GeometryUpdatesBlocker blocker(this);
3835 move(new_pos);
3836 resize(requestedFrameSize);
3837 QRect area = workspace()->clientArea(WorkArea, this);
3838 if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()
3839 && area.contains(origClientGeometry))
3840 keepInArea(area);
3841
3842 // this is part of the kicker-xinerama-hack... it should be
3843 // safe to remove when kicker gets proper ExtendedStrut support;
3844 // see Workspace::updateClientArea() and
3845 // X11Client::adjustedClientArea()
3846 if (hasStrut())
3847 workspace() -> updateClientArea();
3848 }
3849
3850 if (value_mask & configureSizeMask && !(value_mask & configurePositionMask)) { // pure resize
3851 int nw = clientSize().width();
3852 int nh = clientSize().height();
3853 if (value_mask & XCB_CONFIG_WINDOW_WIDTH) {
3854 nw = rw;
3855 }
3856 if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
3857 nh = rh;
3858 }
3859
3860 const QSize requestedClientSize = constrainClientSize(QSize(nw, nh));
3861 QSize requestedFrameSize = clientSizeToFrameSize(requestedClientSize);
3862 requestedFrameSize = rules()->checkSize(requestedFrameSize);
3863
3864 if (requestedFrameSize != size()) { // don't restore if some app sets its own size again
3865 QRect origClientGeometry = m_clientGeometry;
3866 GeometryUpdatesBlocker blocker(this);
3867 resizeWithChecks(requestedFrameSize, xcb_gravity_t(gravity));
3868 if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()) {
3869 // try to keep the window in its xinerama screen if possible,
3870 // if that fails at least keep it visible somewhere
3871 QRect area = workspace()->clientArea(MovementArea, this);
3872 if (area.contains(origClientGeometry))
3873 keepInArea(area);
3874 area = workspace()->clientArea(WorkArea, this);
3875 if (area.contains(origClientGeometry))
3876 keepInArea(area);
3877 }
3878 }
3879 }
3880 setGeometryRestore(frameGeometry());
3881 // No need to send synthetic configure notify event here, either it's sent together
3882 // with geometry change, or there's no need to send it.
3883 // Handling of the real ConfigureRequest event forces sending it, as there it's necessary.
3884 }
3885
resizeWithChecks(int w,int h,xcb_gravity_t gravity)3886 void X11Client::resizeWithChecks(int w, int h, xcb_gravity_t gravity)
3887 {
3888 Q_ASSERT(!shade_geometry_change);
3889 if (isShade()) {
3890 if (h == borderTop() + borderBottom()) {
3891 qCWarning(KWIN_CORE) << "Shaded geometry passed for size:" ;
3892 }
3893 }
3894 int newx = x();
3895 int newy = y();
3896 QRect area = workspace()->clientArea(WorkArea, this);
3897 // don't allow growing larger than workarea
3898 if (w > area.width())
3899 w = area.width();
3900 if (h > area.height())
3901 h = area.height();
3902 QSize tmp = constrainFrameSize(QSize(w, h)); // checks size constraints, including min/max size
3903 w = tmp.width();
3904 h = tmp.height();
3905 if (gravity == 0) {
3906 gravity = m_geometryHints.windowGravity();
3907 }
3908 switch(gravity) {
3909 case XCB_GRAVITY_NORTH_WEST: // top left corner doesn't move
3910 default:
3911 break;
3912 case XCB_GRAVITY_NORTH: // middle of top border doesn't move
3913 newx = (newx + width() / 2) - (w / 2);
3914 break;
3915 case XCB_GRAVITY_NORTH_EAST: // top right corner doesn't move
3916 newx = newx + width() - w;
3917 break;
3918 case XCB_GRAVITY_WEST: // middle of left border doesn't move
3919 newy = (newy + height() / 2) - (h / 2);
3920 break;
3921 case XCB_GRAVITY_CENTER: // middle point doesn't move
3922 newx = (newx + width() / 2) - (w / 2);
3923 newy = (newy + height() / 2) - (h / 2);
3924 break;
3925 case XCB_GRAVITY_STATIC: // top left corner of _client_ window doesn't move
3926 // since decoration doesn't change, equal to NorthWestGravity
3927 break;
3928 case XCB_GRAVITY_EAST: // // middle of right border doesn't move
3929 newx = newx + width() - w;
3930 newy = (newy + height() / 2) - (h / 2);
3931 break;
3932 case XCB_GRAVITY_SOUTH_WEST: // bottom left corner doesn't move
3933 newy = newy + height() - h;
3934 break;
3935 case XCB_GRAVITY_SOUTH: // middle of bottom border doesn't move
3936 newx = (newx + width() / 2) - (w / 2);
3937 newy = newy + height() - h;
3938 break;
3939 case XCB_GRAVITY_SOUTH_EAST: // bottom right corner doesn't move
3940 newx = newx + width() - w;
3941 newy = newy + height() - h;
3942 break;
3943 }
3944 moveResize(QRect{newx, newy, w, h});
3945 }
3946
3947 // _NET_MOVERESIZE_WINDOW
NETMoveResizeWindow(int flags,int x,int y,int width,int height)3948 void X11Client::NETMoveResizeWindow(int flags, int x, int y, int width, int height)
3949 {
3950 int gravity = flags & 0xff;
3951 int value_mask = 0;
3952 if (flags & (1 << 8)) {
3953 value_mask |= XCB_CONFIG_WINDOW_X;
3954 }
3955 if (flags & (1 << 9)) {
3956 value_mask |= XCB_CONFIG_WINDOW_Y;
3957 }
3958 if (flags & (1 << 10)) {
3959 value_mask |= XCB_CONFIG_WINDOW_WIDTH;
3960 }
3961 if (flags & (1 << 11)) {
3962 value_mask |= XCB_CONFIG_WINDOW_HEIGHT;
3963 }
3964 configureRequest(value_mask, x, y, width, height, gravity, true);
3965 }
3966
isMovable() const3967 bool X11Client::isMovable() const
3968 {
3969 if (!hasNETSupport() && !m_motif.move()) {
3970 return false;
3971 }
3972 if (isFullScreen())
3973 return false;
3974 if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :)
3975 return false;
3976 if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position
3977 return false;
3978 return true;
3979 }
3980
isMovableAcrossScreens() const3981 bool X11Client::isMovableAcrossScreens() const
3982 {
3983 if (!hasNETSupport() && !m_motif.move()) {
3984 return false;
3985 }
3986 if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :)
3987 return false;
3988 if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position
3989 return false;
3990 return true;
3991 }
3992
isResizable() const3993 bool X11Client::isResizable() const
3994 {
3995 if (!hasNETSupport() && !m_motif.resize()) {
3996 return false;
3997 }
3998 if (isFullScreen())
3999 return false;
4000 if (isSpecialWindow() || isSplash() || isToolbar())
4001 return false;
4002 if (rules()->checkSize(QSize()).isValid()) // forced size
4003 return false;
4004 const Position mode = interactiveMoveResizePointerMode();
4005 if ((mode == PositionTop || mode == PositionTopLeft || mode == PositionTopRight ||
4006 mode == PositionLeft || mode == PositionBottomLeft) && rules()->checkPosition(invalidPoint) != invalidPoint)
4007 return false;
4008
4009 QSize min = minSize();
4010 QSize max = maxSize();
4011 return min.width() < max.width() || min.height() < max.height();
4012 }
4013
isMaximizable() const4014 bool X11Client::isMaximizable() const
4015 {
4016 if (!isResizable() || isToolbar()) // SELI isToolbar() ?
4017 return false;
4018 if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore && rules()->checkMaximize(MaximizeFull) != MaximizeRestore)
4019 return true;
4020 return false;
4021 }
4022
4023
4024 /**
4025 * Reimplemented to inform the client about the new window position.
4026 */
moveResizeInternal(const QRect & rect,MoveResizeMode mode)4027 void X11Client::moveResizeInternal(const QRect &rect, MoveResizeMode mode)
4028 {
4029 // Ok, the shading geometry stuff. Generally, code doesn't care about shaded geometry,
4030 // simply because there are too many places dealing with geometry. Those places
4031 // ignore shaded state and use normal geometry, which they usually should get
4032 // from adjustedSize(). Such geometry comes here, and if the window is shaded,
4033 // the geometry is used only for client_size, since that one is not used when
4034 // shading. Then the frame geometry is adjusted for the shaded geometry.
4035 // This gets more complicated in the case the code does only something like
4036 // setGeometry( geometry()) - geometry() will return the shaded frame geometry.
4037 // Such code is wrong and should be changed to handle the case when the window is shaded,
4038 // for example using X11Client::clientSize()
4039
4040 QRect frameGeometry = rect;
4041
4042 if (shade_geometry_change)
4043 ; // nothing
4044 else if (isShade()) {
4045 if (frameGeometry.height() == borderTop() + borderBottom()) {
4046 qCDebug(KWIN_CORE) << "Shaded geometry passed for size:";
4047 } else {
4048 m_clientGeometry = frameRectToClientRect(frameGeometry);
4049 frameGeometry.setHeight(borderTop() + borderBottom());
4050 }
4051 } else {
4052 m_clientGeometry = frameRectToClientRect(frameGeometry);
4053 }
4054 m_frameGeometry = frameGeometry;
4055 m_bufferGeometry = frameRectToBufferRect(frameGeometry);
4056
4057 if (pendingMoveResizeMode() == MoveResizeMode::None &&
4058 m_lastBufferGeometry == m_bufferGeometry &&
4059 m_lastFrameGeometry == m_frameGeometry &&
4060 m_lastClientGeometry == m_clientGeometry) {
4061 return;
4062 }
4063 if (areGeometryUpdatesBlocked()) {
4064 setPendingMoveResizeMode(mode);
4065 return;
4066 }
4067
4068 const QRect oldBufferGeometry = m_lastBufferGeometry;
4069 const QRect oldFrameGeometry = m_lastFrameGeometry;
4070 const QRect oldClientGeometry = m_lastClientGeometry;
4071
4072 updateServerGeometry();
4073 updateWindowRules(Rules::Position|Rules::Size);
4074
4075 m_lastBufferGeometry = m_bufferGeometry;
4076 m_lastFrameGeometry = m_frameGeometry;
4077 m_lastClientGeometry = m_clientGeometry;
4078
4079 if (isActive()) {
4080 workspace()->setActiveOutput(output());
4081 }
4082 workspace()->updateStackingOrder();
4083
4084 if (oldBufferGeometry != m_bufferGeometry) {
4085 Q_EMIT bufferGeometryChanged(this, oldBufferGeometry);
4086 }
4087 if (oldClientGeometry != m_clientGeometry) {
4088 Q_EMIT clientGeometryChanged(this, oldClientGeometry);
4089 }
4090 if (oldFrameGeometry != m_frameGeometry) {
4091 Q_EMIT frameGeometryChanged(this, oldFrameGeometry);
4092 }
4093 Q_EMIT geometryShapeChanged(this, oldFrameGeometry);
4094 }
4095
updateServerGeometry()4096 void X11Client::updateServerGeometry()
4097 {
4098 const QRect oldBufferGeometry = m_lastBufferGeometry;
4099
4100 // Compute the old client rect, the client geometry is always inside the buffer geometry.
4101 QRect oldClientRect = m_lastClientGeometry;
4102 oldClientRect.translate(-m_lastBufferGeometry.topLeft());
4103
4104 if (oldBufferGeometry.size() != m_bufferGeometry.size() ||
4105 oldClientRect != QRect(clientPos(), clientSize())) {
4106 resizeDecoration();
4107 // If the client is being interactively resized, then the frame window, the wrapper window,
4108 // and the client window have correct geometry at this point, so we don't have to configure
4109 // them again.
4110 if (m_frame.geometry() != m_bufferGeometry) {
4111 m_frame.setGeometry(m_bufferGeometry);
4112 }
4113 if (!isShade()) {
4114 const QRect requestedWrapperGeometry(clientPos(), clientSize());
4115 if (m_wrapper.geometry() != requestedWrapperGeometry) {
4116 m_wrapper.setGeometry(requestedWrapperGeometry);
4117 }
4118 const QRect requestedClientGeometry(QPoint(0, 0), clientSize());
4119 if (m_client.geometry() != requestedClientGeometry) {
4120 m_client.setGeometry(requestedClientGeometry);
4121 }
4122 // SELI - won't this be too expensive?
4123 // THOMAS - yes, but gtk+ clients will not resize without ...
4124 sendSyntheticConfigureNotify();
4125 }
4126 updateShape();
4127 } else {
4128 if (isInteractiveMoveResize()) {
4129 if (Compositor::compositing()) { // Defer the X update until we leave this mode
4130 needsXWindowMove = true;
4131 } else {
4132 m_frame.move(m_bufferGeometry.topLeft()); // sendSyntheticConfigureNotify() on finish shall be sufficient
4133 }
4134 } else {
4135 m_frame.move(m_bufferGeometry.topLeft());
4136 sendSyntheticConfigureNotify();
4137 }
4138 // Unconditionally move the input window: it won't affect rendering
4139 m_decoInputExtent.move(pos() + inputPos());
4140 }
4141 }
4142
4143 static bool changeMaximizeRecursion = false;
changeMaximize(bool horizontal,bool vertical,bool adjust)4144 void X11Client::changeMaximize(bool horizontal, bool vertical, bool adjust)
4145 {
4146 if (changeMaximizeRecursion)
4147 return;
4148
4149 if (!isResizable() || isToolbar()) // SELI isToolbar() ?
4150 return;
4151
4152 QRect clientArea;
4153 if (isElectricBorderMaximizing())
4154 clientArea = workspace()->clientArea(MaximizeArea, this, Cursors::self()->mouse()->pos());
4155 else
4156 clientArea = workspace()->clientArea(MaximizeArea, this, moveResizeGeometry().center());
4157
4158 MaximizeMode old_mode = max_mode;
4159 // 'adjust == true' means to update the size only, e.g. after changing workspace size
4160 if (!adjust) {
4161 if (vertical)
4162 max_mode = MaximizeMode(max_mode ^ MaximizeVertical);
4163 if (horizontal)
4164 max_mode = MaximizeMode(max_mode ^ MaximizeHorizontal);
4165 }
4166
4167 // if the client insist on a fix aspect ratio, we check whether the maximizing will get us
4168 // out of screen bounds and take that as a "full maximization with aspect check" then
4169 if (m_geometryHints.hasAspect() && // fixed aspect
4170 (max_mode == MaximizeVertical || max_mode == MaximizeHorizontal) && // ondimensional maximization
4171 rules()->checkStrictGeometry(true)) { // obey aspect
4172 const QSize minAspect = m_geometryHints.minAspect();
4173 const QSize maxAspect = m_geometryHints.maxAspect();
4174 if (max_mode == MaximizeVertical || (old_mode & MaximizeVertical)) {
4175 const double fx = minAspect.width(); // use doubles, because the values can be MAX_INT
4176 const double fy = maxAspect.height(); // use doubles, because the values can be MAX_INT
4177 if (fx*clientArea.height()/fy > clientArea.width()) // too big
4178 max_mode = old_mode & MaximizeHorizontal ? MaximizeRestore : MaximizeFull;
4179 } else { // max_mode == MaximizeHorizontal
4180 const double fx = maxAspect.width();
4181 const double fy = minAspect.height();
4182 if (fy*clientArea.width()/fx > clientArea.height()) // too big
4183 max_mode = old_mode & MaximizeVertical ? MaximizeRestore : MaximizeFull;
4184 }
4185 }
4186
4187 max_mode = rules()->checkMaximize(max_mode);
4188 if (!adjust && max_mode == old_mode)
4189 return;
4190
4191 GeometryUpdatesBlocker blocker(this);
4192
4193 // maximing one way and unmaximizing the other way shouldn't happen,
4194 // so restore first and then maximize the other way
4195 if ((old_mode == MaximizeVertical && max_mode == MaximizeHorizontal)
4196 || (old_mode == MaximizeHorizontal && max_mode == MaximizeVertical)) {
4197 changeMaximize(false, false, false); // restore
4198 }
4199
4200 // save sizes for restoring, if maximalizing
4201 QSize sz;
4202 if (isShade())
4203 sz = adjustedSize();
4204 else
4205 sz = size();
4206
4207 if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) {
4208 QRect savedGeometry = geometryRestore();
4209 if (!adjust && !(old_mode & MaximizeVertical)) {
4210 savedGeometry.setTop(y());
4211 savedGeometry.setHeight(sz.height());
4212 }
4213 if (!adjust && !(old_mode & MaximizeHorizontal)) {
4214 savedGeometry.setLeft(x());
4215 savedGeometry.setWidth(sz.width());
4216 }
4217 setGeometryRestore(savedGeometry);
4218 }
4219
4220 // call into decoration update borders
4221 if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && max_mode == KWin::MaximizeFull)) {
4222 changeMaximizeRecursion = true;
4223 const auto c = decoration()->client().toStrongRef();
4224 if ((max_mode & MaximizeVertical) != (old_mode & MaximizeVertical)) {
4225 Q_EMIT c->maximizedVerticallyChanged(max_mode & MaximizeVertical);
4226 }
4227 if ((max_mode & MaximizeHorizontal) != (old_mode & MaximizeHorizontal)) {
4228 Q_EMIT c->maximizedHorizontallyChanged(max_mode & MaximizeHorizontal);
4229 }
4230 if ((max_mode == MaximizeFull) != (old_mode == MaximizeFull)) {
4231 Q_EMIT c->maximizedChanged(max_mode == MaximizeFull);
4232 }
4233 changeMaximizeRecursion = false;
4234 }
4235
4236 if (options->borderlessMaximizedWindows()) {
4237 // triggers a maximize change.
4238 // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry
4239 changeMaximizeRecursion = true;
4240 setNoBorder(rules()->checkNoBorder(app_noborder || (m_motif.hasDecoration() && m_motif.noBorder()) || max_mode == MaximizeFull));
4241 changeMaximizeRecursion = false;
4242 }
4243
4244 // Conditional quick tiling exit points
4245 if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) {
4246 if (old_mode == MaximizeFull &&
4247 !clientArea.contains(geometryRestore().center())) {
4248 // Not restoring on the same screen
4249 // TODO: The following doesn't work for some reason
4250 //quick_tile_mode = QuickTileFlag::None; // And exit quick tile mode manually
4251 } else if ((old_mode == MaximizeVertical && max_mode == MaximizeRestore) ||
4252 (old_mode == MaximizeFull && max_mode == MaximizeHorizontal)) {
4253 // Modifying geometry of a tiled window
4254 updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry
4255 }
4256 }
4257
4258 switch(max_mode) {
4259
4260 case MaximizeVertical: {
4261 if (old_mode & MaximizeHorizontal) { // actually restoring from MaximizeFull
4262 if (geometryRestore().width() == 0 || !clientArea.contains(geometryRestore().center())) {
4263 // needs placement
4264 resize(constrainFrameSize(QSize(width() * 2 / 3, clientArea.height()), SizeModeFixedH));
4265 Placement::self()->placeSmart(this, clientArea);
4266 } else {
4267 moveResize(QRect(QPoint(geometryRestore().x(), clientArea.top()),
4268 constrainFrameSize(QSize(geometryRestore().width(), clientArea.height()), SizeModeFixedH)));
4269 }
4270 } else {
4271 QRect r(x(), clientArea.top(), width(), clientArea.height());
4272 r.setTopLeft(rules()->checkPosition(r.topLeft()));
4273 r.setSize(constrainFrameSize(r.size(), SizeModeFixedH));
4274 moveResize(r);
4275 }
4276 info->setState(NET::MaxVert, NET::Max);
4277 break;
4278 }
4279
4280 case MaximizeHorizontal: {
4281 if (old_mode & MaximizeVertical) { // actually restoring from MaximizeFull
4282 if (geometryRestore().height() == 0 || !clientArea.contains(geometryRestore().center())) {
4283 // needs placement
4284 resize(constrainFrameSize(QSize(clientArea.width(), height() * 2 / 3), SizeModeFixedW));
4285 Placement::self()->placeSmart(this, clientArea);
4286 } else {
4287 moveResize(QRect(QPoint(clientArea.left(), geometryRestore().y()),
4288 constrainFrameSize(QSize(clientArea.width(), geometryRestore().height()), SizeModeFixedW)));
4289 }
4290 } else {
4291 QRect r(clientArea.left(), y(), clientArea.width(), height());
4292 r.setTopLeft(rules()->checkPosition(r.topLeft()));
4293 r.setSize(constrainFrameSize(r.size(), SizeModeFixedW));
4294 moveResize(r);
4295 }
4296 info->setState(NET::MaxHoriz, NET::Max);
4297 break;
4298 }
4299
4300 case MaximizeRestore: {
4301 QRect restore = frameGeometry();
4302 // when only partially maximized, geom_restore may not have the other dimension remembered
4303 if (old_mode & MaximizeVertical) {
4304 restore.setTop(geometryRestore().top());
4305 restore.setBottom(geometryRestore().bottom());
4306 }
4307 if (old_mode & MaximizeHorizontal) {
4308 restore.setLeft(geometryRestore().left());
4309 restore.setRight(geometryRestore().right());
4310 }
4311 if (!restore.isValid()) {
4312 QSize s = QSize(clientArea.width() * 2 / 3, clientArea.height() * 2 / 3);
4313 if (geometryRestore().width() > 0) {
4314 s.setWidth(geometryRestore().width());
4315 }
4316 if (geometryRestore().height() > 0) {
4317 s.setHeight(geometryRestore().height());
4318 }
4319 resize(constrainFrameSize(s));
4320 Placement::self()->placeSmart(this, clientArea);
4321 restore = frameGeometry();
4322 if (geometryRestore().width() > 0) {
4323 restore.moveLeft(geometryRestore().x());
4324 }
4325 if (geometryRestore().height() > 0) {
4326 restore.moveTop(geometryRestore().y());
4327 }
4328 setGeometryRestore(restore); // relevant for mouse pos calculation, bug #298646
4329 }
4330 if (m_geometryHints.hasAspect()) {
4331 restore.setSize(constrainFrameSize(restore.size(), SizeModeAny));
4332 }
4333 moveResize(restore);
4334 if (!clientArea.contains(geometryRestore().center())) { // Not restoring to the same screen
4335 Placement::self()->place(this, clientArea);
4336 }
4337 info->setState(NET::States(), NET::Max);
4338 updateQuickTileMode(QuickTileFlag::None);
4339 break;
4340 }
4341
4342 case MaximizeFull: {
4343 QRect r(clientArea);
4344 r.setTopLeft(rules()->checkPosition(r.topLeft()));
4345 r.setSize(constrainFrameSize(r.size(), SizeModeMax));
4346 if (r.size() != clientArea.size()) { // to avoid off-by-one errors...
4347 if (isElectricBorderMaximizing() && r.width() < clientArea.width()) {
4348 r.moveLeft(qMax(clientArea.left(), Cursors::self()->mouse()->pos().x() - r.width()/2));
4349 r.moveRight(qMin(clientArea.right(), r.right()));
4350 } else {
4351 r.moveCenter(clientArea.center());
4352 const bool closeHeight = r.height() > 97*clientArea.height()/100;
4353 const bool closeWidth = r.width() > 97*clientArea.width() /100;
4354 const bool overHeight = r.height() > clientArea.height();
4355 const bool overWidth = r.width() > clientArea.width();
4356 if (closeWidth || closeHeight) {
4357 Position titlePos = titlebarPosition();
4358 const QRect screenArea = workspace()->clientArea(ScreenArea, this, clientArea.center());
4359 if (closeHeight) {
4360 bool tryBottom = titlePos == PositionBottom;
4361 if ((overHeight && titlePos == PositionTop) ||
4362 screenArea.top() == clientArea.top())
4363 r.setTop(clientArea.top());
4364 else
4365 tryBottom = true;
4366 if (tryBottom &&
4367 (overHeight || screenArea.bottom() == clientArea.bottom()))
4368 r.setBottom(clientArea.bottom());
4369 }
4370 if (closeWidth) {
4371 bool tryLeft = titlePos == PositionLeft;
4372 if ((overWidth && titlePos == PositionRight) ||
4373 screenArea.right() == clientArea.right())
4374 r.setRight(clientArea.right());
4375 else
4376 tryLeft = true;
4377 if (tryLeft && (overWidth || screenArea.left() == clientArea.left()))
4378 r.setLeft(clientArea.left());
4379 }
4380 }
4381 }
4382 r.moveTopLeft(rules()->checkPosition(r.topLeft()));
4383 }
4384 moveResize(r);
4385 if (options->electricBorderMaximize() && r.top() == clientArea.top())
4386 updateQuickTileMode(QuickTileFlag::Maximize);
4387 else
4388 updateQuickTileMode(QuickTileFlag::None);
4389 info->setState(NET::Max, NET::Max);
4390 break;
4391 }
4392 default:
4393 break;
4394 }
4395
4396 updateAllowedActions();
4397 updateWindowRules(Rules::MaximizeVert|Rules::MaximizeHoriz|Rules::Position|Rules::Size);
4398 Q_EMIT quickTileModeChanged();
4399 }
4400
userCanSetFullScreen() const4401 bool X11Client::userCanSetFullScreen() const
4402 {
4403 if (!isFullScreenable()) {
4404 return false;
4405 }
4406 return isNormalWindow() || isDialog();
4407 }
4408
setFullScreen(bool set,bool user)4409 void X11Client::setFullScreen(bool set, bool user)
4410 {
4411 set = rules()->checkFullScreen(set);
4412
4413 const bool wasFullscreen = isFullScreen();
4414 if (wasFullscreen == set) {
4415 return;
4416 }
4417 if (user && !userCanSetFullScreen()) {
4418 return;
4419 }
4420
4421 setShade(ShadeNone);
4422
4423 if (wasFullscreen) {
4424 workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event
4425 } else {
4426 setFullscreenGeometryRestore(frameGeometry());
4427 }
4428
4429 if (set) {
4430 m_fullscreenMode = FullScreenNormal;
4431 workspace()->raiseClient(this);
4432 } else {
4433 m_fullscreenMode = FullScreenNone;
4434 }
4435
4436 StackingUpdatesBlocker blocker1(workspace());
4437 GeometryUpdatesBlocker blocker2(this);
4438
4439 // active fullscreens get different layer
4440 updateLayer();
4441
4442 info->setState(isFullScreen() ? NET::FullScreen : NET::States(), NET::FullScreen);
4443 updateDecoration(false, false);
4444
4445 if (set) {
4446 if (info->fullscreenMonitors().isSet()) {
4447 moveResize(fullscreenMonitorsArea(info->fullscreenMonitors()));
4448 } else {
4449 moveResize(workspace()->clientArea(FullScreenArea, this));
4450 }
4451 } else {
4452 Q_ASSERT(!fullscreenGeometryRestore().isNull());
4453 AbstractOutput *currentOutput = output();
4454 moveResize(QRect(fullscreenGeometryRestore().topLeft(), constrainFrameSize(fullscreenGeometryRestore().size())));
4455 if (currentOutput != output()) {
4456 workspace()->sendClientToOutput(this, currentOutput);
4457 }
4458 }
4459
4460 updateWindowRules(Rules::Fullscreen | Rules::Position | Rules::Size);
4461 Q_EMIT clientFullScreenSet(this, set, user);
4462 Q_EMIT fullScreenChanged();
4463 }
4464
4465
updateFullscreenMonitors(NETFullscreenMonitors topology)4466 void X11Client::updateFullscreenMonitors(NETFullscreenMonitors topology)
4467 {
4468 const int outputCount = kwinApp()->platform()->enabledOutputs().count();
4469
4470 // qDebug() << "incoming request with top: " << topology.top << " bottom: " << topology.bottom
4471 // << " left: " << topology.left << " right: " << topology.right
4472 // << ", we have: " << nscreens << " screens.";
4473
4474 if (topology.top >= outputCount ||
4475 topology.bottom >= outputCount ||
4476 topology.left >= outputCount ||
4477 topology.right >= outputCount) {
4478 qCWarning(KWIN_CORE) << "fullscreenMonitors update failed. request higher than number of screens.";
4479 return;
4480 }
4481
4482 info->setFullscreenMonitors(topology);
4483 if (isFullScreen())
4484 moveResize(fullscreenMonitorsArea(topology));
4485 }
4486
4487 /**
4488 * Calculates the bounding rectangle defined by the 4 monitor indices indicating the
4489 * top, bottom, left, and right edges of the window when the fullscreen state is enabled.
4490 */
fullscreenMonitorsArea(NETFullscreenMonitors requestedTopology) const4491 QRect X11Client::fullscreenMonitorsArea(NETFullscreenMonitors requestedTopology) const
4492 {
4493 QRect total;
4494
4495 if (auto output = kwinApp()->platform()->findOutput(requestedTopology.top)) {
4496 total = total.united(output->geometry());
4497 }
4498 if (auto output = kwinApp()->platform()->findOutput(requestedTopology.bottom)) {
4499 total = total.united(output->geometry());
4500 }
4501 if (auto output = kwinApp()->platform()->findOutput(requestedTopology.left)) {
4502 total = total.united(output->geometry());
4503 }
4504 if (auto output = kwinApp()->platform()->findOutput(requestedTopology.right)) {
4505 total = total.united(output->geometry());
4506 }
4507
4508 return total;
4509 }
4510
4511 static GeometryTip* geometryTip = nullptr;
4512
positionGeometryTip()4513 void X11Client::positionGeometryTip()
4514 {
4515 Q_ASSERT(isInteractiveMove() || isInteractiveResize());
4516 // Position and Size display
4517 if (effects && static_cast<EffectsHandlerImpl*>(effects)->provides(Effect::GeometryTip))
4518 return; // some effect paints this for us
4519 if (options->showGeometryTip()) {
4520 if (!geometryTip) {
4521 geometryTip = new GeometryTip(&m_geometryHints);
4522 }
4523 QRect wgeom(moveResizeGeometry()); // position of the frame, size of the window itself
4524 wgeom.setWidth(wgeom.width() - (width() - clientSize().width()));
4525 wgeom.setHeight(wgeom.height() - (height() - clientSize().height()));
4526 if (isShade())
4527 wgeom.setHeight(0);
4528 geometryTip->setGeometry(wgeom);
4529 if (!geometryTip->isVisible())
4530 geometryTip->show();
4531 geometryTip->raise();
4532 }
4533 }
4534
doStartInteractiveMoveResize()4535 bool X11Client::doStartInteractiveMoveResize()
4536 {
4537 bool has_grab = false;
4538 // This reportedly improves smoothness of the moveresize operation,
4539 // something with Enter/LeaveNotify events, looks like XFree performance problem or something *shrug*
4540 // (https://lists.kde.org/?t=107302193400001&r=1&w=2)
4541 QRect r = workspace()->clientArea(FullArea, this);
4542 m_moveResizeGrabWindow.create(r, XCB_WINDOW_CLASS_INPUT_ONLY, 0, nullptr, rootWindow());
4543 m_moveResizeGrabWindow.map();
4544 m_moveResizeGrabWindow.raise();
4545 updateXTime();
4546 const xcb_grab_pointer_cookie_t cookie = xcb_grab_pointer_unchecked(connection(), false, m_moveResizeGrabWindow,
4547 XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION |
4548 XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW,
4549 XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, m_moveResizeGrabWindow, Cursors::self()->mouse()->x11Cursor(cursor()), xTime());
4550 ScopedCPointer<xcb_grab_pointer_reply_t> pointerGrab(xcb_grab_pointer_reply(connection(), cookie, nullptr));
4551 if (!pointerGrab.isNull() && pointerGrab->status == XCB_GRAB_STATUS_SUCCESS) {
4552 has_grab = true;
4553 }
4554 if (!has_grab && grabXKeyboard(frameId()))
4555 has_grab = move_resize_has_keyboard_grab = true;
4556 if (!has_grab) { // at least one grab is necessary in order to be able to finish move/resize
4557 m_moveResizeGrabWindow.reset();
4558 return false;
4559 }
4560 return true;
4561 }
4562
leaveInteractiveMoveResize()4563 void X11Client::leaveInteractiveMoveResize()
4564 {
4565 if (needsXWindowMove) {
4566 // Do the deferred move
4567 m_frame.move(m_bufferGeometry.topLeft());
4568 needsXWindowMove = false;
4569 }
4570 if (!isInteractiveResize())
4571 sendSyntheticConfigureNotify(); // tell the client about it's new final position
4572 if (geometryTip) {
4573 geometryTip->hide();
4574 delete geometryTip;
4575 geometryTip = nullptr;
4576 }
4577 if (move_resize_has_keyboard_grab)
4578 ungrabXKeyboard();
4579 move_resize_has_keyboard_grab = false;
4580 xcb_ungrab_pointer(connection(), xTime());
4581 m_moveResizeGrabWindow.reset();
4582 if (m_syncRequest.counter == XCB_NONE) { // don't forget to sanitize since the timeout will no more fire
4583 m_syncRequest.isPending = false;
4584 }
4585 delete m_syncRequest.timeout;
4586 m_syncRequest.timeout = nullptr;
4587 AbstractClient::leaveInteractiveMoveResize();
4588 }
4589
isWaitingForInteractiveMoveResizeSync() const4590 bool X11Client::isWaitingForInteractiveMoveResizeSync() const
4591 {
4592 return m_syncRequest.isPending && isInteractiveResize();
4593 }
4594
doInteractiveResizeSync()4595 void X11Client::doInteractiveResizeSync()
4596 {
4597 if (!m_syncRequest.timeout) {
4598 m_syncRequest.timeout = new QTimer(this);
4599 connect(m_syncRequest.timeout, &QTimer::timeout, this, &X11Client::handleSyncTimeout);
4600 m_syncRequest.timeout->setSingleShot(true);
4601 }
4602 if (m_syncRequest.counter != XCB_NONE) {
4603 m_syncRequest.timeout->start(250);
4604 sendSyncRequest();
4605 } else { // for clients not supporting the XSYNC protocol, we
4606 m_syncRequest.isPending = true; // limit the resizes to 30Hz to take pointless load from X11
4607 m_syncRequest.timeout->start(33); // and the client, the mouse is still moved at full speed
4608 } // and no human can control faster resizes anyway
4609 const QRect moveResizeClientGeometry = frameRectToClientRect(moveResizeGeometry());
4610 const QRect moveResizeBufferGeometry = frameRectToBufferRect(moveResizeGeometry());
4611
4612 // According to the Composite extension spec, a window will get a new pixmap allocated each time
4613 // it is mapped or resized. Given that we redirect frame windows and not client windows, we have
4614 // to resize the frame window in order to forcefully reallocate offscreen storage. If we don't do
4615 // this, then we might render partially updated client window. I know, it sucks.
4616 m_frame.setGeometry(moveResizeBufferGeometry);
4617 m_wrapper.setGeometry(QRect(clientPos(), moveResizeClientGeometry.size()));
4618 m_client.setGeometry(QRect(QPoint(0, 0), moveResizeClientGeometry.size()));
4619 }
4620
handleSyncTimeout()4621 void X11Client::handleSyncTimeout()
4622 {
4623 if (m_syncRequest.counter == XCB_NONE) { // client w/o XSYNC support. allow the next resize event
4624 m_syncRequest.isPending = false; // NEVER do this for clients with a valid counter
4625 } // (leads to sync request races in some clients)
4626 performInteractiveMoveResize();
4627 }
4628
strut() const4629 NETExtendedStrut X11Client::strut() const
4630 {
4631 NETExtendedStrut ext = info->extendedStrut();
4632 NETStrut str = info->strut();
4633 const QSize displaySize = workspace()->geometry().size();
4634 if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0
4635 && (str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0)) {
4636 // build extended from simple
4637 if (str.left != 0) {
4638 ext.left_width = str.left;
4639 ext.left_start = 0;
4640 ext.left_end = displaySize.height();
4641 }
4642 if (str.right != 0) {
4643 ext.right_width = str.right;
4644 ext.right_start = 0;
4645 ext.right_end = displaySize.height();
4646 }
4647 if (str.top != 0) {
4648 ext.top_width = str.top;
4649 ext.top_start = 0;
4650 ext.top_end = displaySize.width();
4651 }
4652 if (str.bottom != 0) {
4653 ext.bottom_width = str.bottom;
4654 ext.bottom_start = 0;
4655 ext.bottom_end = displaySize.width();
4656 }
4657 }
4658 return ext;
4659 }
4660
strutRect(StrutArea area) const4661 StrutRect X11Client::strutRect(StrutArea area) const
4662 {
4663 Q_ASSERT(area != StrutAreaAll); // Not valid
4664 const QSize displaySize = workspace()->geometry().size();
4665 NETExtendedStrut strutArea = strut();
4666 switch(area) {
4667 case StrutAreaTop:
4668 if (strutArea.top_width != 0)
4669 return StrutRect(QRect(
4670 strutArea.top_start, 0,
4671 strutArea.top_end - strutArea.top_start, strutArea.top_width
4672 ), StrutAreaTop);
4673 break;
4674 case StrutAreaRight:
4675 if (strutArea.right_width != 0)
4676 return StrutRect(QRect(
4677 displaySize.width() - strutArea.right_width, strutArea.right_start,
4678 strutArea.right_width, strutArea.right_end - strutArea.right_start
4679 ), StrutAreaRight);
4680 break;
4681 case StrutAreaBottom:
4682 if (strutArea.bottom_width != 0)
4683 return StrutRect(QRect(
4684 strutArea.bottom_start, displaySize.height() - strutArea.bottom_width,
4685 strutArea.bottom_end - strutArea.bottom_start, strutArea.bottom_width
4686 ), StrutAreaBottom);
4687 break;
4688 case StrutAreaLeft:
4689 if (strutArea.left_width != 0)
4690 return StrutRect(QRect(
4691 0, strutArea.left_start,
4692 strutArea.left_width, strutArea.left_end - strutArea.left_start
4693 ), StrutAreaLeft);
4694 break;
4695 default:
4696 abort(); // Not valid
4697 }
4698 return StrutRect(); // Null rect
4699 }
4700
hasStrut() const4701 bool X11Client::hasStrut() const
4702 {
4703 NETExtendedStrut ext = strut();
4704 if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0)
4705 return false;
4706 return true;
4707 }
4708
applyWindowRules()4709 void X11Client::applyWindowRules()
4710 {
4711 AbstractClient::applyWindowRules();
4712 updateAllowedActions();
4713 setBlockingCompositing(info->isBlockingCompositing());
4714 }
4715
supportsWindowRules() const4716 bool X11Client::supportsWindowRules() const
4717 {
4718 return true;
4719 }
4720
damageNotifyEvent()4721 void X11Client::damageNotifyEvent()
4722 {
4723 Q_ASSERT(kwinApp()->operationMode() == Application::OperationModeX11);
4724
4725 if (!readyForPainting()) { // avoid "setReadyForPainting()" function calling overhead
4726 if (m_syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now
4727 setReadyForPainting();
4728 setupWindowManagementInterface();
4729 }
4730 }
4731
4732 SurfaceItemX11 *item = static_cast<SurfaceItemX11 *>(surfaceItem());
4733 if (item) {
4734 item->processDamage();
4735 }
4736 }
4737
discardWindowPixmap()4738 void X11Client::discardWindowPixmap()
4739 {
4740 if (auto item = surfaceItem()) {
4741 item->discardPixmap();
4742 }
4743 }
4744
updateWindowPixmap()4745 void X11Client::updateWindowPixmap()
4746 {
4747 if (auto item = surfaceItem()) {
4748 item->updatePixmap();
4749 }
4750 }
4751
4752 } // namespace
4753