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