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     SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
8 
9     SPDX-License-Identifier: GPL-2.0-or-later
10 */
11 // own
12 #include "workspace.h"
13 // kwin libs
14 #include <kwinglplatform.h>
15 // kwin
16 #include "abstract_wayland_output.h"
17 #ifdef KWIN_BUILD_ACTIVITIES
18 #include "activities.h"
19 #endif
20 #include "appmenu.h"
21 #include "atoms.h"
22 #include "x11client.h"
23 #include "composite.h"
24 #include "cursor.h"
25 #include "dbusinterface.h"
26 #include "deleted.h"
27 #include "effects.h"
28 #include "focuschain.h"
29 #include "group.h"
30 #include "input.h"
31 #include "internal_client.h"
32 #include "moving_client_x11_filter.h"
33 #include "killwindow.h"
34 #include "netinfo.h"
35 #include "outline.h"
36 #include "placement.h"
37 #include "pluginmanager.h"
38 #include "rules.h"
39 #include "screenedge.h"
40 #include "screens.h"
41 #include "platform.h"
42 #include "scripting/scripting.h"
43 #include "syncalarmx11filter.h"
44 #ifdef KWIN_BUILD_TABBOX
45 #include "tabbox.h"
46 #endif
47 #include "unmanaged.h"
48 #include "useractions.h"
49 #include "virtualdesktops.h"
50 #include "was_user_interaction_x11_filter.h"
51 #include "wayland_server.h"
52 #include "xcbutils.h"
53 #include "main.h"
54 #include "decorations/decorationbridge.h"
55 #include "xwaylandclient.h"
56 // KDE
57 #include <KConfig>
58 #include <KConfigGroup>
59 #include <KLocalizedString>
60 #include <KStartupInfo>
61 // Qt
62 #include <QtConcurrentRun>
63 
64 namespace KWin
65 {
66 
67 extern int screen_number;
68 extern bool is_multihead;
69 
X11EventFilterContainer(X11EventFilter * filter)70 X11EventFilterContainer::X11EventFilterContainer(X11EventFilter *filter)
71     : m_filter(filter)
72 {
73 }
74 
filter() const75 X11EventFilter *X11EventFilterContainer::filter() const
76 {
77     return m_filter;
78 }
79 
ColorMapper(QObject * parent)80 ColorMapper::ColorMapper(QObject *parent)
81     : QObject(parent)
82     , m_default(kwinApp()->x11DefaultScreen()->default_colormap)
83     , m_installed(kwinApp()->x11DefaultScreen()->default_colormap)
84 {
85 }
86 
~ColorMapper()87 ColorMapper::~ColorMapper()
88 {
89 }
90 
update()91 void ColorMapper::update()
92 {
93     xcb_colormap_t cmap = m_default;
94     if (X11Client *c = dynamic_cast<X11Client *>(Workspace::self()->activeClient())) {
95         if (c->colormap() != XCB_COLORMAP_NONE) {
96             cmap = c->colormap();
97         }
98     }
99     if (cmap != m_installed) {
100         xcb_install_colormap(connection(), cmap);
101         m_installed = cmap;
102     }
103 }
104 
105 Workspace* Workspace::_self = nullptr;
106 
Workspace()107 Workspace::Workspace()
108     : QObject(nullptr)
109     , m_compositor(nullptr)
110     // Unsorted
111     , m_quickTileCombineTimer(nullptr)
112     , active_popup(nullptr)
113     , active_popup_client(nullptr)
114     , m_initialDesktop(1)
115     , active_client(nullptr)
116     , last_active_client(nullptr)
117     , movingClient(nullptr)
118     , delayfocus_client(nullptr)
119     , force_restacking(false)
120     , showing_desktop(false)
121     , was_user_interaction(false)
122     , block_focus(0)
123     , m_userActionsMenu(new UserActionsMenu(this))
124     , client_keys_dialog(nullptr)
125     , client_keys_client(nullptr)
126     , global_shortcuts_disabled_for_client(false)
127     , workspaceInit(true)
128     , set_active_client_recursion(0)
129     , block_stacking_updates(0)
130     , m_sessionManager(new SessionManager(this))
131 {
132     // If KWin was already running it saved its configuration after loosing the selection -> Reread
133     QFuture<void> reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration);
134 
135     ApplicationMenu::create(this);
136 
137     _self = this;
138 
139 #ifdef KWIN_BUILD_ACTIVITIES
140     Activities *activities = nullptr;
141     if (kwinApp()->usesKActivities()) {
142         activities = Activities::create(this);
143     }
144     if (activities) {
145         connect(activities, &Activities::currentChanged, this, &Workspace::updateCurrentActivity);
146     }
147 #endif
148 
149     // PluginMgr needs access to the config file, so we need to wait for it for finishing
150     reparseConfigFuture.waitForFinished();
151 
152     options->loadConfig();
153     options->loadCompositingConfig(false);
154 
155     delayFocusTimer = nullptr;
156 
157     m_quickTileCombineTimer = new QTimer(this);
158     m_quickTileCombineTimer->setSingleShot(true);
159 
160     RuleBook::create(this)->load();
161 
162     kwinApp()->createScreens();
163     ScreenEdges::create(this);
164 
165     // VirtualDesktopManager needs to be created prior to init shortcuts
166     // and prior to TabBox, due to TabBox connecting to signals
167     // actual initialization happens in init()
168     VirtualDesktopManager::create(this);
169     //dbus interface
170     new VirtualDesktopManagerDBusInterface(VirtualDesktopManager::self());
171 
172 #ifdef KWIN_BUILD_TABBOX
173     // need to create the tabbox before compositing scene is setup
174     TabBox::TabBox::create(this);
175 #endif
176 
177     if (Compositor::self()) {
178         m_compositor = Compositor::self();
179     } else {
180         Q_ASSERT(kwinApp()->operationMode() == Application::OperationMode::OperationModeX11);
181         m_compositor = X11Compositor::create(this);
182     }
183     connect(this, &Workspace::currentDesktopChanged, m_compositor, &Compositor::addRepaintFull);
184     connect(m_compositor, &QObject::destroyed, this, [this] { m_compositor = nullptr; });
185 
186     auto decorationBridge = Decoration::DecorationBridge::create(this);
187     decorationBridge->init();
188     connect(this, &Workspace::configChanged, decorationBridge, &Decoration::DecorationBridge::reconfigure);
189 
190     connect(m_sessionManager, &SessionManager::loadSessionRequested, this, &Workspace::loadSessionInfo);
191 
192     connect(m_sessionManager, &SessionManager::prepareSessionSaveRequested, this, [this](const QString &name) {
193         storeSession(name, SMSavePhase0);
194     });
195     connect(m_sessionManager, &SessionManager::finishSessionSaveRequested, this, [this](const QString &name) {
196         storeSession(name, SMSavePhase2);
197     });
198 
199     new DBusInterface(this);
200     Outline::create(this);
201 
202     initShortcuts();
203 
204     init();
205 }
206 
init()207 void Workspace::init()
208 {
209     KSharedConfigPtr config = kwinApp()->config();
210     ScreenEdges *screenEdges = ScreenEdges::self();
211     screenEdges->setConfig(config);
212     screenEdges->init();
213     connect(options, &Options::configChanged, screenEdges, &ScreenEdges::reconfigure);
214     connect(VirtualDesktopManager::self(), &VirtualDesktopManager::layoutChanged, screenEdges, &ScreenEdges::updateLayout);
215     connect(this, &Workspace::clientActivated, screenEdges, &ScreenEdges::checkBlocking);
216 
217     FocusChain *focusChain = FocusChain::create(this);
218     connect(this, &Workspace::clientRemoved, focusChain, &FocusChain::remove);
219     connect(this, &Workspace::clientActivated, focusChain, &FocusChain::setActiveClient);
220     connect(VirtualDesktopManager::self(), &VirtualDesktopManager::currentChanged, focusChain, [focusChain]() {
221         focusChain->setCurrentDesktop(VirtualDesktopManager::self()->currentDesktop());
222     });
223     connect(options, &Options::separateScreenFocusChanged, focusChain, &FocusChain::setSeparateScreenFocus);
224     focusChain->setSeparateScreenFocus(options->isSeparateScreenFocus());
225 
226     Platform *platform = kwinApp()->platform();
227     connect(platform, &Platform::outputEnabled, this, &Workspace::slotOutputEnabled);
228     connect(platform, &Platform::outputDisabled, this, &Workspace::slotOutputDisabled);
229 
230     const QVector<AbstractOutput *> outputs = platform->enabledOutputs();
231     for (AbstractOutput *output : outputs) {
232         slotOutputEnabled(output);
233     }
234 
235     // create VirtualDesktopManager and perform dependency injection
236     VirtualDesktopManager *vds = VirtualDesktopManager::self();
237     connect(vds, &VirtualDesktopManager::desktopCreated, this, &Workspace::slotDesktopAdded);
238     connect(vds, &VirtualDesktopManager::desktopRemoved, this, &Workspace::slotDesktopRemoved);
239     connect(vds, &VirtualDesktopManager::currentChanged, this, &Workspace::slotCurrentDesktopChanged);
240     vds->setNavigationWrappingAround(options->isRollOverDesktops());
241     connect(options, &Options::rollOverDesktopsChanged, vds, &VirtualDesktopManager::setNavigationWrappingAround);
242     vds->setConfig(config);
243 
244     // Now we know how many desktops we'll have, thus we initialize the positioning object
245     Placement::create(this);
246 
247     // positioning object needs to be created before the virtual desktops are loaded.
248     vds->load();
249     vds->updateLayout();
250     //makes sure any autogenerated id is saved, necessary as in case of xwayland, load will be called 2 times
251     // load is needed to be called again when starting xwayalnd to sync to RootInfo, see BUG 385260
252     vds->save();
253 
254     if (!VirtualDesktopManager::self()->setCurrent(m_initialDesktop))
255         VirtualDesktopManager::self()->setCurrent(1);
256 
257     reconfigureTimer.setSingleShot(true);
258     updateToolWindowsTimer.setSingleShot(true);
259 
260     connect(&reconfigureTimer, &QTimer::timeout, this, &Workspace::slotReconfigure);
261     connect(&updateToolWindowsTimer, &QTimer::timeout, this, &Workspace::slotUpdateToolWindows);
262 
263     // TODO: do we really need to reconfigure everything when fonts change?
264     // maybe just reconfigure the decorations? Move this into libkdecoration?
265     QDBusConnection::sessionBus().connect(QString(),
266                                           QStringLiteral("/KDEPlatformTheme"),
267                                           QStringLiteral("org.kde.KDEPlatformTheme"),
268                                           QStringLiteral("refreshFonts"),
269                                           this, SLOT(reconfigure()));
270 
271     active_client = nullptr;
272 
273     // We want to have some xcb connection while tearing down X11 components. We don't really
274     // care if the xcb connection is broken or has an error.
275     connect(kwinApp(), &Application::x11ConnectionChanged, this, &Workspace::initializeX11);
276     connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, &Workspace::cleanupX11);
277     initializeX11();
278 
279     Scripting::create(this);
280 
281     if (auto server = waylandServer()) {
282         connect(server, &WaylandServer::shellClientAdded, this, &Workspace::addShellClient);
283         connect(server, &WaylandServer::shellClientRemoved, this, &Workspace::removeShellClient);
284     }
285 
286     // SELI TODO: This won't work with unreasonable focus policies,
287     // and maybe in rare cases also if the selected client doesn't
288     // want focus
289     workspaceInit = false;
290 
291     // broadcast that Workspace is ready, but first process all events.
292     QMetaObject::invokeMethod(this, "workspaceInitialized", Qt::QueuedConnection);
293 
294     // TODO: ungrabXServer()
295 }
296 
initializeX11()297 void Workspace::initializeX11()
298 {
299     if (!kwinApp()->x11Connection()) {
300         return;
301     }
302 
303     atoms->retrieveHelpers();
304 
305     // first initialize the extensions
306     Xcb::Extensions::self();
307     m_colorMapper.reset(new ColorMapper(this));
308     connect(this, &Workspace::clientActivated, m_colorMapper.data(), &ColorMapper::update);
309 
310     // Call this before XSelectInput() on the root window
311     m_startup.reset(new KStartupInfo(
312         KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this));
313 
314     // Select windowmanager privileges
315     selectWmInputEventMask();
316 
317     // Compatibility
318     int32_t data = 1;
319 
320     xcb_change_property(connection(), XCB_PROP_MODE_APPEND, rootWindow(), atoms->kwin_running,
321                         atoms->kwin_running, 32, 1, &data);
322 
323     if (kwinApp()->operationMode() == Application::OperationModeX11) {
324         m_wasUserInteractionFilter.reset(new WasUserInteractionX11Filter);
325         m_movingClientFilter.reset(new MovingClientX11Filter);
326     }
327     if (Xcb::Extensions::self()->isSyncAvailable()) {
328         m_syncAlarmFilter.reset(new SyncAlarmX11Filter);
329     }
330     updateXTime(); // Needed for proper initialization of user_time in Client ctor
331 
332     const uint32_t nullFocusValues[] = {true};
333     m_nullFocus.reset(new Xcb::Window(QRect(-1, -1, 1, 1), XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, nullFocusValues));
334     m_nullFocus->map();
335 
336     RootInfo *rootInfo = RootInfo::create();
337     const auto vds = VirtualDesktopManager::self();
338     vds->setRootInfo(rootInfo);
339     rootInfo->activate();
340 
341     // TODO: only in X11 mode
342     // Extra NETRootInfo instance in Client mode is needed to get the values of the properties
343     NETRootInfo client_info(connection(), NET::ActiveWindow | NET::CurrentDesktop);
344     if (!qApp->isSessionRestored()) {
345         m_initialDesktop = client_info.currentDesktop();
346         vds->setCurrent(m_initialDesktop);
347     }
348 
349     // TODO: better value
350     rootInfo->setActiveWindow(XCB_WINDOW_NONE);
351     focusToNull();
352 
353     if (!qApp->isSessionRestored())
354         ++block_focus; // Because it will be set below
355 
356     {
357         // Begin updates blocker block
358         StackingUpdatesBlocker blocker(this);
359 
360         Xcb::Tree tree(rootWindow());
361         xcb_window_t *wins = xcb_query_tree_children(tree.data());
362 
363         QVector<Xcb::WindowAttributes> windowAttributes(tree->children_len);
364         QVector<Xcb::WindowGeometry> windowGeometries(tree->children_len);
365 
366         // Request the attributes and geometries of all toplevel windows
367         for (int i = 0; i < tree->children_len; i++) {
368             windowAttributes[i] = Xcb::WindowAttributes(wins[i]);
369             windowGeometries[i] = Xcb::WindowGeometry(wins[i]);
370         }
371 
372         // Get the replies
373         for (int i = 0; i < tree->children_len; i++) {
374             Xcb::WindowAttributes attr(windowAttributes.at(i));
375 
376             if (attr.isNull()) {
377                 continue;
378             }
379 
380             if (attr->override_redirect) {
381                 if (attr->map_state == XCB_MAP_STATE_VIEWABLE &&
382                     attr->_class != XCB_WINDOW_CLASS_INPUT_ONLY)
383                     // ### This will request the attributes again
384                     createUnmanaged(wins[i]);
385             } else if (attr->map_state != XCB_MAP_STATE_UNMAPPED) {
386                 if (Application::wasCrash()) {
387                     fixPositionAfterCrash(wins[i], windowGeometries.at(i).data());
388                 }
389 
390                 // ### This will request the attributes again
391                 createClient(wins[i], true);
392             }
393         }
394 
395         // Propagate clients, will really happen at the end of the updates blocker block
396         updateStackingOrder(true);
397 
398         saveOldScreenSizes();
399         updateClientArea();
400 
401         // NETWM spec says we have to set it to (0,0) if we don't support it
402         NETPoint* viewports = new NETPoint[VirtualDesktopManager::self()->count()];
403         rootInfo->setDesktopViewport(VirtualDesktopManager::self()->count(), *viewports);
404         delete[] viewports;
405 
406         NETSize desktop_geometry;
407         desktop_geometry.width = m_geometry.width();
408         desktop_geometry.height = m_geometry.height();
409         rootInfo->setDesktopGeometry(desktop_geometry);
410         setShowingDesktop(false);
411 
412     } // End updates blocker block
413 
414     // TODO: only on X11?
415     AbstractClient* new_active_client = nullptr;
416     if (!qApp->isSessionRestored()) {
417         --block_focus;
418         new_active_client = findClient(Predicate::WindowMatch, client_info.activeWindow());
419     }
420     if (new_active_client == nullptr
421             && activeClient() == nullptr && should_get_focus.count() == 0) {
422         // No client activated in manage()
423         if (new_active_client == nullptr)
424             new_active_client = topClientOnDesktop(VirtualDesktopManager::self()->currentDesktop());
425         if (new_active_client == nullptr)
426             new_active_client = findDesktop(true, VirtualDesktopManager::self()->currentDesktop());
427     }
428     if (new_active_client != nullptr)
429         activateClient(new_active_client);
430 }
431 
cleanupX11()432 void Workspace::cleanupX11()
433 {
434     // We expect that other components will unregister their X11 event filters after the
435     // connection to the X server has been lost.
436 
437     StackingUpdatesBlocker blocker(this);
438 
439     // Use stacking_order, so that kwin --replace keeps stacking order.
440     const QList<X11Client *> orderedClients = ensureStackingOrder(m_x11Clients);
441     for (X11Client *client : orderedClients) {
442         client->releaseWindow(true);
443         removeFromStack(client);
444     }
445 
446     // We need a shadow copy because windows get removed as we go through them.
447     const QList<Unmanaged *> unmanaged = m_unmanaged;
448     for (Unmanaged *overrideRedirect : unmanaged) {
449         overrideRedirect->release(ReleaseReason::KWinShutsDown);
450         removeFromStack(overrideRedirect);
451     }
452 
453     manual_overlays.clear();
454 
455     VirtualDesktopManager *desktopManager = VirtualDesktopManager::self();
456     desktopManager->setRootInfo(nullptr);
457 
458     X11Client::cleanupX11();
459     RootInfo::destroy();
460     Xcb::Extensions::destroy();
461 
462     if (xcb_connection_t *connection = kwinApp()->x11Connection()) {
463         xcb_delete_property(connection, kwinApp()->x11RootWindow(), atoms->kwin_running);
464     }
465 
466     m_colorMapper.reset();
467     m_movingClientFilter.reset();
468     m_startup.reset();
469     m_nullFocus.reset();
470     m_syncAlarmFilter.reset();
471     m_wasUserInteractionFilter.reset();
472     m_xStackingQueryTree.reset();
473 }
474 
~Workspace()475 Workspace::~Workspace()
476 {
477     blockStackingUpdates(true);
478 
479     cleanupX11();
480 
481     if (waylandServer()) {
482         const QList<AbstractClient *> shellClients = waylandServer()->clients();
483         for (AbstractClient *client : shellClients) {
484             client->destroyClient();
485         }
486     }
487 
488     // We need a shadow copy because clients get removed as we go through them.
489     const QList<InternalClient *> internalClients = m_internalClients;
490     for (InternalClient *client : internalClients) {
491         client->destroyClient();
492     }
493 
494     for (auto it = deleted.begin(); it != deleted.end();) {
495         Q_EMIT deletedRemoved(*it);
496         (*it)->finishCompositing();
497         it = deleted.erase(it);
498     }
499 
500     delete RuleBook::self();
501     kwinApp()->config()->sync();
502 
503     delete Placement::self();
504     delete client_keys_dialog;
505     qDeleteAll(session);
506 
507     _self = nullptr;
508 }
509 
setupClientConnections(AbstractClient * c)510 void Workspace::setupClientConnections(AbstractClient *c)
511 {
512     connect(c, &AbstractClient::desktopPresenceChanged, this, &Workspace::desktopPresenceChanged);
513     connect(c, &AbstractClient::minimizedChanged, this, std::bind(&Workspace::clientMinimizedChanged, this, c));
514 }
515 
constrain(AbstractClient * below,AbstractClient * above)516 void Workspace::constrain(AbstractClient *below, AbstractClient *above)
517 {
518     if (below == above) {
519         return;
520     }
521 
522     QList<Constraint *> parents;
523     QList<Constraint *> children;
524     for (Constraint *constraint : qAsConst(m_constraints)) {
525         if (constraint->below == below && constraint->above == above) {
526             return;
527         }
528         if (constraint->below == above) {
529             children << constraint;
530         } else if (constraint->above == below) {
531             parents << constraint;
532         }
533     }
534 
535     Constraint *constraint = new Constraint();
536     constraint->parents = parents;
537     constraint->below = below;
538     constraint->above = above;
539     constraint->children = children;
540     m_constraints << constraint;
541 
542     for (Constraint *parent : qAsConst(parents)) {
543         parent->children << constraint;
544     }
545 
546     for (Constraint *child : qAsConst(children)) {
547         child->parents << constraint;
548     }
549 
550     updateStackingOrder();
551 }
552 
unconstrain(AbstractClient * below,AbstractClient * above)553 void Workspace::unconstrain(AbstractClient *below, AbstractClient *above)
554 {
555     Constraint *constraint = nullptr;
556     for (int i = 0; i < m_constraints.count(); ++i) {
557         if (m_constraints[i]->below == below && m_constraints[i]->above == above) {
558             constraint = m_constraints.takeAt(i);
559             break;
560         }
561     }
562 
563     if (!constraint) {
564         return;
565     }
566 
567     const QList<Constraint *> parents = constraint->parents;
568     for (Constraint *parent : parents) {
569         parent->children.removeOne(constraint);
570     }
571 
572     const QList<Constraint *> children = constraint->children;
573     for (Constraint *child : children) {
574         child->parents.removeOne(constraint);
575     }
576 
577     delete constraint;
578     updateStackingOrder();
579 }
580 
addToStack(Toplevel * toplevel)581 void Workspace::addToStack(Toplevel *toplevel)
582 {
583     // If the stacking order of a window has been restored from the session, that
584     // toplevel will already be in the stack when Workspace::addClient() is called.
585     if (!unconstrained_stacking_order.contains(toplevel)) {
586         unconstrained_stacking_order.append(toplevel);
587     }
588     if (!stacking_order.contains(toplevel)) {
589         stacking_order.append(toplevel);
590     }
591 }
592 
replaceInStack(Toplevel * original,Deleted * deleted)593 void Workspace::replaceInStack(Toplevel *original, Deleted *deleted)
594 {
595     const int unconstraintedIndex = unconstrained_stacking_order.indexOf(original);
596     if (unconstraintedIndex != -1) {
597         unconstrained_stacking_order.replace(unconstraintedIndex, deleted);
598     } else {
599         // This can be the case only if an override-redirect window is unmapped.
600         unconstrained_stacking_order.append(deleted);
601     }
602 
603     const int index = stacking_order.indexOf(original);
604     if (index != -1) {
605         stacking_order.replace(index, deleted);
606     } else {
607         // This can be the case only if an override-redirect window is unmapped.
608         stacking_order.append(deleted);
609     }
610 
611     for (Constraint *constraint : qAsConst(m_constraints)) {
612         if (constraint->below == original) {
613             constraint->below = deleted;
614         } else if (constraint->above == original) {
615             constraint->above = deleted;
616         }
617     }
618 }
619 
removeFromStack(Toplevel * toplevel)620 void Workspace::removeFromStack(Toplevel *toplevel)
621 {
622     unconstrained_stacking_order.removeAll(toplevel);
623     stacking_order.removeAll(toplevel);
624 
625     for (int i = m_constraints.count() - 1; i >= 0; --i) {
626         Constraint *constraint = m_constraints[i];
627         const bool isBelow = (constraint->below == toplevel);
628         const bool isAbove = (constraint->above == toplevel);
629         if (!isBelow && !isAbove) {
630             continue;
631         }
632         if (isBelow) {
633             for (Constraint *child : qAsConst(constraint->children)) {
634                 child->parents.removeOne(constraint);
635             }
636         } else {
637             for (Constraint *parent : qAsConst(constraint->parents)) {
638                 parent->children.removeOne(constraint);
639             }
640         }
641         delete m_constraints.takeAt(i);
642     }
643 }
644 
createClient(xcb_window_t w,bool is_mapped)645 X11Client *Workspace::createClient(xcb_window_t w, bool is_mapped)
646 {
647     StackingUpdatesBlocker blocker(this);
648     X11Client *c = nullptr;
649     if (kwinApp()->operationMode() == Application::OperationModeX11) {
650         c = new X11Client();
651     } else {
652         c = new XwaylandClient();
653     }
654     setupClientConnections(c);
655     if (X11Compositor *compositor = X11Compositor::self()) {
656         connect(c, &X11Client::blockingCompositingChanged, compositor, &X11Compositor::updateClientCompositeBlocking);
657     }
658     connect(c, &X11Client::clientFullScreenSet, ScreenEdges::self(), &ScreenEdges::checkBlocking);
659     if (!c->manage(w, is_mapped)) {
660         X11Client::deleteClient(c);
661         return nullptr;
662     }
663     addClient(c);
664     return c;
665 }
666 
createUnmanaged(xcb_window_t w)667 Unmanaged* Workspace::createUnmanaged(xcb_window_t w)
668 {
669     if (X11Compositor *compositor = X11Compositor::self()) {
670         if (compositor->checkForOverlayWindow(w)) {
671             return nullptr;
672         }
673     }
674     Unmanaged* c = new Unmanaged();
675     if (!c->track(w)) {
676         Unmanaged::deleteUnmanaged(c);
677         return nullptr;
678     }
679     addUnmanaged(c);
680     Q_EMIT unmanagedAdded(c);
681     return c;
682 }
683 
addClient(X11Client * c)684 void Workspace::addClient(X11Client *c)
685 {
686     Group* grp = findGroup(c->window());
687 
688     Q_EMIT clientAdded(c);
689 
690     if (grp != nullptr)
691         grp->gotLeader(c);
692 
693     if (c->isDesktop()) {
694         if (active_client == nullptr && should_get_focus.isEmpty() && c->isOnCurrentDesktop())
695             requestFocus(c);   // TODO: Make sure desktop is active after startup if there's no other window active
696     } else {
697         FocusChain::self()->update(c, FocusChain::Update);
698     }
699     m_x11Clients.append(c);
700     m_allClients.append(c);
701     addToStack(c);
702     markXStackingOrderAsDirty();
703     updateClientArea(); // This cannot be in manage(), because the client got added only now
704     c->updateLayer();
705     if (c->isDesktop()) {
706         raiseClient(c);
707         // If there's no active client, make this desktop the active one
708         if (activeClient() == nullptr && should_get_focus.count() == 0)
709             activateClient(findDesktop(true, VirtualDesktopManager::self()->currentDesktop()));
710     }
711     c->checkActiveModal();
712     checkTransients(c->window());   // SELI TODO: Does this really belong here?
713     updateStackingOrder(true);   // Propagate new client
714     if (c->isUtility() || c->isMenu() || c->isToolbar())
715         updateToolWindows(true);
716     updateTabbox();
717 }
718 
addUnmanaged(Unmanaged * c)719 void Workspace::addUnmanaged(Unmanaged* c)
720 {
721     m_unmanaged.append(c);
722     markXStackingOrderAsDirty();
723 }
724 
725 /**
726  * Destroys the client \a c
727  */
removeX11Client(X11Client * c)728 void Workspace::removeX11Client(X11Client *c)
729 {
730     if (c == active_popup_client)
731         closeActivePopup();
732     if (m_userActionsMenu->isMenuClient(c)) {
733         m_userActionsMenu->close();
734     }
735 
736     Q_ASSERT(m_x11Clients.contains(c));
737     // TODO: if marked client is removed, notify the marked list
738     m_x11Clients.removeAll(c);
739     Group* group = findGroup(c->window());
740     if (group != nullptr)
741         group->lostLeader();
742     removeAbstractClient(c);
743 }
744 
removeUnmanaged(Unmanaged * c)745 void Workspace::removeUnmanaged(Unmanaged* c)
746 {
747     Q_ASSERT(m_unmanaged.contains(c));
748     m_unmanaged.removeAll(c);
749     Q_EMIT unmanagedRemoved(c);
750     markXStackingOrderAsDirty();
751 }
752 
addDeleted(Deleted * c,Toplevel * orig)753 void Workspace::addDeleted(Deleted* c, Toplevel *orig)
754 {
755     Q_ASSERT(!deleted.contains(c));
756     deleted.append(c);
757     replaceInStack(orig, c);
758     markXStackingOrderAsDirty();
759 }
760 
removeDeleted(Deleted * c)761 void Workspace::removeDeleted(Deleted* c)
762 {
763     Q_ASSERT(deleted.contains(c));
764     Q_EMIT deletedRemoved(c);
765     deleted.removeAll(c);
766     removeFromStack(c);
767     markXStackingOrderAsDirty();
768     if (!c->wasClient()) {
769         return;
770     }
771     if (X11Compositor *compositor = X11Compositor::self()) {
772         compositor->updateClientCompositeBlocking();
773     }
774 }
775 
addShellClient(AbstractClient * client)776 void Workspace::addShellClient(AbstractClient *client)
777 {
778     setupClientConnections(client);
779     client->updateLayer();
780 
781     if (client->isPlaceable()) {
782         const QRect area = clientArea(PlacementArea, client, activeOutput());
783         bool placementDone = false;
784         if (client->isRequestedFullScreen()) {
785             placementDone = true;
786         }
787         if (client->requestedMaximizeMode() == MaximizeMode::MaximizeFull) {
788             placementDone = true;
789         }
790         if (client->rules()->checkPosition(invalidPoint, true) != invalidPoint) {
791             placementDone = true;
792         }
793         if (!placementDone) {
794             client->placeIn(area);
795         }
796     }
797     m_allClients.append(client);
798     addToStack(client);
799 
800     markXStackingOrderAsDirty();
801     updateStackingOrder(true);
802     updateClientArea();
803     if (client->wantsInput() && !client->isMinimized()) {
804         activateClient(client);
805     }
806     updateTabbox();
807     connect(client, &AbstractClient::windowShown, this, [this, client] {
808         client->updateLayer();
809         markXStackingOrderAsDirty();
810         updateStackingOrder(true);
811         updateClientArea();
812         if (client->wantsInput()) {
813             activateClient(client);
814         }
815     });
816     connect(client, &AbstractClient::windowHidden, this, [this] {
817         // TODO: update tabbox if it's displayed
818         markXStackingOrderAsDirty();
819         updateStackingOrder(true);
820         updateClientArea();
821     });
822     Q_EMIT clientAdded(client);
823 }
824 
removeShellClient(AbstractClient * client)825 void Workspace::removeShellClient(AbstractClient *client)
826 {
827     clientHidden(client);
828     removeAbstractClient(client);
829 }
830 
removeAbstractClient(AbstractClient * client)831 void Workspace::removeAbstractClient(AbstractClient *client)
832 {
833     m_allClients.removeAll(client);
834     if (client == delayfocus_client) {
835         cancelDelayFocus();
836     }
837     attention_chain.removeAll(client);
838     should_get_focus.removeAll(client);
839     if (client == active_client) {
840         active_client = nullptr;
841     }
842     if (client == last_active_client) {
843         last_active_client = nullptr;
844     }
845     if (client_keys_client == client) {
846         setupWindowShortcutDone(false);
847     }
848     if (!client->shortcut().isEmpty()) {
849         client->setShortcut(QString());   // Remove from client_keys
850         clientShortcutUpdated(client);    // Needed, since this is otherwise delayed by setShortcut() and wouldn't run
851     }
852 
853     Q_EMIT clientRemoved(client);
854     markXStackingOrderAsDirty();
855 
856     updateStackingOrder(true);
857     updateClientArea();
858     updateTabbox();
859 }
860 
updateToolWindows(bool also_hide)861 void Workspace::updateToolWindows(bool also_hide)
862 {
863     // TODO: What if Client's transiency/group changes? should this be called too? (I'm paranoid, am I not?)
864     if (!options->isHideUtilityWindowsForInactive()) {
865         for (auto it = m_x11Clients.constBegin(); it != m_x11Clients.constEnd(); ++it)
866             (*it)->hideClient(false);
867         return;
868     }
869     const Group* group = nullptr;
870     auto client = active_client;
871     // Go up in transiency hiearchy, if the top is found, only tool transients for the top mainwindow
872     // will be shown; if a group transient is group, all tools in the group will be shown
873     while (client != nullptr) {
874         if (!client->isTransient())
875             break;
876         if (client->groupTransient()) {
877             group = client->group();
878             break;
879         }
880         client = client->transientFor();
881     }
882     // Use stacking order only to reduce flicker, it doesn't matter if block_stacking_updates == 0,
883     // I.e. if it's not up to date
884 
885     // SELI TODO: But maybe it should - what if a new client has been added that's not in stacking order yet?
886     QVector<AbstractClient*> to_show, to_hide;
887     for (auto it = stacking_order.constBegin();
888             it != stacking_order.constEnd();
889             ++it) {
890         auto c = qobject_cast<AbstractClient*>(*it);
891         if (!c) {
892             continue;
893         }
894         if (c->isUtility() || c->isMenu() || c->isToolbar()) {
895             bool show = true;
896             if (!c->isTransient()) {
897                 if (!c->group() || c->group()->members().count() == 1)   // Has its own group, keep always visible
898                     show = true;
899                 else if (client != nullptr && c->group() == client->group())
900                     show = true;
901                 else
902                     show = false;
903             } else {
904                 if (group != nullptr && c->group() == group)
905                     show = true;
906                 else if (client != nullptr && client->hasTransient(c, true))
907                     show = true;
908                 else
909                     show = false;
910             }
911             if (!show && also_hide) {
912                 const auto mainclients = c->mainClients();
913                 // Don't hide utility windows which are standalone(?) or
914                 // have e.g. kicker as mainwindow
915                 if (mainclients.isEmpty())
916                     show = true;
917                 for (auto it2 = mainclients.constBegin();
918                         it2 != mainclients.constEnd();
919                         ++it2) {
920                     if ((*it2)->isSpecialWindow())
921                         show = true;
922                 }
923                 if (!show)
924                     to_hide.append(c);
925             }
926             if (show)
927                 to_show.append(c);
928         }
929     } // First show new ones, then hide
930     for (int i = to_show.size() - 1;
931             i >= 0;
932             --i)  // From topmost
933         // TODO: Since this is in stacking order, the order of taskbar entries changes :(
934         to_show.at(i)->hideClient(false);
935     if (also_hide) {
936         for (auto it = to_hide.constBegin();
937                 it != to_hide.constEnd();
938                 ++it)  // From bottommost
939             (*it)->hideClient(true);
940         updateToolWindowsTimer.stop();
941     } else // setActiveClient() is after called with NULL client, quickly followed
942         // by setting a new client, which would result in flickering
943         resetUpdateToolWindowsTimer();
944 }
945 
946 
resetUpdateToolWindowsTimer()947 void Workspace::resetUpdateToolWindowsTimer()
948 {
949     updateToolWindowsTimer.start(200);
950 }
951 
slotUpdateToolWindows()952 void Workspace::slotUpdateToolWindows()
953 {
954     updateToolWindows(true);
955 }
956 
slotReloadConfig()957 void Workspace::slotReloadConfig()
958 {
959     reconfigure();
960 }
961 
reconfigure()962 void Workspace::reconfigure()
963 {
964     reconfigureTimer.start(200);
965 }
966 
967 /**
968  * Reread settings
969  */
970 
slotReconfigure()971 void Workspace::slotReconfigure()
972 {
973     qCDebug(KWIN_CORE) << "Workspace::slotReconfigure()";
974     reconfigureTimer.stop();
975 
976     bool borderlessMaximizedWindows = options->borderlessMaximizedWindows();
977 
978     kwinApp()->config()->reparseConfiguration();
979     options->updateSettings();
980 
981     Q_EMIT configChanged();
982     m_userActionsMenu->discard();
983     updateToolWindows(true);
984 
985     RuleBook::self()->load();
986     for (AbstractClient *client : qAsConst(m_allClients)) {
987         if (client->supportsWindowRules()) {
988             client->evaluateWindowRules();
989             RuleBook::self()->discardUsed(client, false);
990         }
991     }
992 
993     if (borderlessMaximizedWindows != options->borderlessMaximizedWindows() &&
994             !options->borderlessMaximizedWindows()) {
995         // in case borderless maximized windows option changed and new option
996         // is to have borders, we need to unset the borders for all maximized windows
997         for (auto it = m_allClients.cbegin();
998                 it != m_allClients.cend();
999                 ++it) {
1000             if ((*it)->maximizeMode() == MaximizeFull)
1001                 (*it)->checkNoBorder();
1002         }
1003     }
1004 }
1005 
slotCurrentDesktopChanged(uint oldDesktop,uint newDesktop)1006 void Workspace::slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop)
1007 {
1008     closeActivePopup();
1009     ++block_focus;
1010     StackingUpdatesBlocker blocker(this);
1011     updateClientVisibilityOnDesktopChange(VirtualDesktopManager::self()->desktopForX11Id(newDesktop));
1012     // Restore the focus on this desktop
1013     --block_focus;
1014 
1015     activateClientOnNewDesktop(VirtualDesktopManager::self()->desktopForX11Id(newDesktop));
1016     Q_EMIT currentDesktopChanged(oldDesktop, movingClient);
1017 }
1018 
updateClientVisibilityOnDesktopChange(VirtualDesktop * newDesktop)1019 void Workspace::updateClientVisibilityOnDesktopChange(VirtualDesktop *newDesktop)
1020 {
1021     for (auto it = stacking_order.constBegin();
1022             it != stacking_order.constEnd();
1023             ++it) {
1024         X11Client *c = qobject_cast<X11Client *>(*it);
1025         if (!c) {
1026             continue;
1027         }
1028         if (!c->isOnDesktop(newDesktop) && c != movingClient && c->isOnCurrentActivity()) {
1029             (c)->updateVisibility();
1030         }
1031     }
1032     // Now propagate the change, after hiding, before showing
1033     if (rootInfo()) {
1034         rootInfo()->setCurrentDesktop(VirtualDesktopManager::self()->current());
1035     }
1036 
1037     if (movingClient && !movingClient->isOnDesktop(newDesktop)) {
1038         movingClient->setDesktops({newDesktop});
1039     }
1040 
1041     for (int i = stacking_order.size() - 1; i >= 0 ; --i) {
1042         X11Client *c = qobject_cast<X11Client *>(stacking_order.at(i));
1043         if (!c) {
1044             continue;
1045         }
1046         if (c->isOnDesktop(newDesktop) && c->isOnCurrentActivity())
1047             c->updateVisibility();
1048     }
1049     if (showingDesktop())   // Do this only after desktop change to avoid flicker
1050         setShowingDesktop(false);
1051 }
1052 
activateClientOnNewDesktop(VirtualDesktop * desktop)1053 void Workspace::activateClientOnNewDesktop(VirtualDesktop *desktop)
1054 {
1055     AbstractClient* c = nullptr;
1056     if (options->focusPolicyIsReasonable()) {
1057         c = findClientToActivateOnDesktop(desktop);
1058     }
1059     // If "unreasonable focus policy" and active_client is on_all_desktops and
1060     // under mouse (Hence == old_active_client), conserve focus.
1061     // (Thanks to Volker Schatz <V.Schatz at thphys.uni-heidelberg.de>)
1062     else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop())
1063         c = active_client;
1064 
1065     if (!c)
1066         c = findDesktop(true, desktop);
1067 
1068     if (c != active_client)
1069         setActiveClient(nullptr);
1070 
1071     if (c)
1072         requestFocus(c);
1073     else
1074         focusToNull();
1075 }
1076 
findClientToActivateOnDesktop(VirtualDesktop * desktop)1077 AbstractClient *Workspace::findClientToActivateOnDesktop(VirtualDesktop *desktop)
1078 {
1079     if (movingClient != nullptr && active_client == movingClient &&
1080         FocusChain::self()->contains(active_client, desktop) &&
1081         active_client->isShown(true) && active_client->isOnCurrentDesktop()) {
1082         // A requestFocus call will fail, as the client is already active
1083         return active_client;
1084     }
1085     // from actiavtion.cpp
1086     if (options->isNextFocusPrefersMouse()) {
1087         auto it = stackingOrder().constEnd();
1088         while (it != stackingOrder().constBegin()) {
1089             AbstractClient *client = qobject_cast<AbstractClient *>(*(--it));
1090             if (!client) {
1091                 continue;
1092             }
1093 
1094             if (!(client->isShown(false) && client->isOnDesktop(desktop) &&
1095                 client->isOnCurrentActivity() && client->isOnActiveOutput()))
1096                 continue;
1097 
1098             if (client->frameGeometry().contains(Cursors::self()->mouse()->pos())) {
1099                 if (!client->isDesktop())
1100                     return client;
1101             break; // unconditional break  - we do not pass the focus to some client below an unusable one
1102             }
1103         }
1104     }
1105     return FocusChain::self()->getForActivation(desktop);
1106 }
1107 
1108 /**
1109  * Updates the current activity when it changes
1110  * do *not* call this directly; it does not set the activity.
1111  *
1112  * Shows/Hides windows according to the stacking order
1113  */
1114 
updateCurrentActivity(const QString & new_activity)1115 void Workspace::updateCurrentActivity(const QString &new_activity)
1116 {
1117 #ifdef KWIN_BUILD_ACTIVITIES
1118     if (!Activities::self()) {
1119         return;
1120     }
1121     //closeActivePopup();
1122     ++block_focus;
1123     // TODO: Q_ASSERT( block_stacking_updates == 0 ); // Make sure stacking_order is up to date
1124     StackingUpdatesBlocker blocker(this);
1125 
1126     // Optimized Desktop switching: unmapping done from back to front
1127     // mapping done from front to back => less exposure events
1128     //Notify::raise((Notify::Event) (Notify::DesktopChange+new_desktop));
1129 
1130     for (auto it = stacking_order.constBegin();
1131             it != stacking_order.constEnd();
1132             ++it) {
1133         X11Client *c = qobject_cast<X11Client *>(*it);
1134         if (!c) {
1135             continue;
1136         }
1137         if (!c->isOnActivity(new_activity) && c != movingClient && c->isOnCurrentDesktop()) {
1138             c->updateVisibility();
1139         }
1140     }
1141 
1142     // Now propagate the change, after hiding, before showing
1143     //rootInfo->setCurrentDesktop( currentDesktop() );
1144 
1145     /* TODO someday enable dragging windows to other activities
1146     if ( movingClient && !movingClient->isOnDesktop( new_desktop ))
1147         {
1148         movingClient->setDesktop( new_desktop );
1149         */
1150 
1151     for (int i = stacking_order.size() - 1; i >= 0 ; --i) {
1152         X11Client *c = qobject_cast<X11Client *>(stacking_order.at(i));
1153         if (!c) {
1154             continue;
1155         }
1156         if (c->isOnActivity(new_activity))
1157             c->updateVisibility();
1158     }
1159 
1160     //FIXME not sure if I should do this either
1161     if (showingDesktop())   // Do this only after desktop change to avoid flicker
1162         setShowingDesktop(false);
1163 
1164     // Restore the focus on this desktop
1165     --block_focus;
1166     AbstractClient* c = nullptr;
1167 
1168     //FIXME below here is a lot of focuschain stuff, probably all wrong now
1169     if (options->focusPolicyIsReasonable()) {
1170         // Search in focus chain
1171         c = FocusChain::self()->getForActivation(VirtualDesktopManager::self()->currentDesktop());
1172     }
1173     // If "unreasonable focus policy" and active_client is on_all_desktops and
1174     // under mouse (Hence == old_active_client), conserve focus.
1175     // (Thanks to Volker Schatz <V.Schatz at thphys.uni-heidelberg.de>)
1176     else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop() && active_client->isOnCurrentActivity())
1177         c = active_client;
1178 
1179     if (!c)
1180         c = findDesktop(true, VirtualDesktopManager::self()->currentDesktop());
1181 
1182     if (c != active_client)
1183         setActiveClient(nullptr);
1184 
1185     if (c)
1186         requestFocus(c);
1187     else
1188         focusToNull();
1189 
1190     // Not for the very first time, only if something changed and there are more than 1 desktops
1191 
1192     //if ( effects != NULL && old_desktop != 0 && old_desktop != new_desktop )
1193     //    static_cast<EffectsHandlerImpl*>( effects )->desktopChanged( old_desktop );
1194     if (Compositor::compositing() && m_compositor)
1195         m_compositor->addRepaintFull();
1196 #else
1197     Q_UNUSED(new_activity)
1198 #endif
1199 }
1200 
slotOutputEnabled(AbstractOutput * output)1201 void Workspace::slotOutputEnabled(AbstractOutput *output)
1202 {
1203     if (!m_activeOutput) {
1204         m_activeOutput = output;
1205     }
1206 
1207     connect(output, &AbstractOutput::geometryChanged, this, &Workspace::desktopResized);
1208     desktopResized();
1209 }
1210 
slotOutputDisabled(AbstractOutput * output)1211 void Workspace::slotOutputDisabled(AbstractOutput *output)
1212 {
1213     if (m_activeOutput == output) {
1214         m_activeOutput = kwinApp()->platform()->outputAt(output->geometry().center());
1215     }
1216 
1217     const auto stack = xStackingOrder();
1218     for (Toplevel *toplevel : stack) {
1219         if (toplevel->output() == output) {
1220             toplevel->setOutput(kwinApp()->platform()->outputAt(toplevel->frameGeometry().center()));
1221         }
1222     }
1223 
1224     disconnect(output, &AbstractOutput::geometryChanged, this, &Workspace::desktopResized);
1225     desktopResized();
1226 }
1227 
slotDesktopAdded(VirtualDesktop * desktop)1228 void Workspace::slotDesktopAdded(VirtualDesktop *desktop)
1229 {
1230     FocusChain::self()->addDesktop(desktop);
1231     Placement::self()->reinitCascading(0);
1232     updateClientArea();
1233 }
1234 
slotDesktopRemoved(VirtualDesktop * desktop)1235 void Workspace::slotDesktopRemoved(VirtualDesktop *desktop)
1236 {
1237     for (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) {
1238         if (!(*it)->desktops().contains(desktop)) {
1239             continue;
1240         }
1241         if ((*it)->desktops().count() > 1) {
1242             (*it)->leaveDesktop(desktop);
1243         } else {
1244             sendClientToDesktop(*it, qMin(desktop->x11DesktopNumber(), VirtualDesktopManager::self()->count()), true);
1245         }
1246     }
1247 
1248     updateClientArea();
1249     Placement::self()->reinitCascading(0);
1250     FocusChain::self()->removeDesktop(desktop);
1251 }
1252 
selectWmInputEventMask()1253 void Workspace::selectWmInputEventMask()
1254 {
1255     uint32_t presentMask = 0;
1256     Xcb::WindowAttributes attr(rootWindow());
1257     if (!attr.isNull()) {
1258         presentMask = attr->your_event_mask;
1259     }
1260 
1261     Xcb::selectInput(rootWindow(),
1262                      presentMask |
1263                      XCB_EVENT_MASK_KEY_PRESS |
1264                      XCB_EVENT_MASK_PROPERTY_CHANGE |
1265                      XCB_EVENT_MASK_COLOR_MAP_CHANGE |
1266                      XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
1267                      XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
1268                      XCB_EVENT_MASK_FOCUS_CHANGE | // For NotifyDetailNone
1269                      XCB_EVENT_MASK_EXPOSURE
1270     );
1271 }
1272 
1273 /**
1274  * Sends client \a c to desktop \a desk.
1275  *
1276  * Takes care of transients as well.
1277  */
sendClientToDesktop(AbstractClient * c,int desk,bool dont_activate)1278 void Workspace::sendClientToDesktop(AbstractClient* c, int desk, bool dont_activate)
1279 {
1280     if ((desk < 1 && desk != NET::OnAllDesktops) || desk > static_cast<int>(VirtualDesktopManager::self()->count()))
1281         return;
1282     int old_desktop = c->desktop();
1283     const bool wasOnCurrent = c->isOnCurrentDesktop();
1284     c->setDesktop(desk);
1285     if (c->desktop() != desk)   // No change or desktop forced
1286         return;
1287     desk = c->desktop(); // Client did range checking
1288 
1289     if (c->isOnCurrentDesktop()) {
1290         if (c->wantsTabFocus() && options->focusPolicyIsReasonable() &&
1291                 !wasOnCurrent && // for stickyness changes
1292                 !dont_activate)
1293             requestFocus(c);
1294         else
1295             restackClientUnderActive(c);
1296     } else
1297         raiseClient(c);
1298 
1299     c->checkWorkspacePosition( QRect(), QRect(), VirtualDesktopManager::self()->desktopForX11Id(old_desktop) );
1300 
1301     auto transients_stacking_order = ensureStackingOrder(c->transients());
1302     for (auto it = transients_stacking_order.constBegin();
1303             it != transients_stacking_order.constEnd();
1304             ++it)
1305         sendClientToDesktop(*it, desk, dont_activate);
1306     updateClientArea();
1307 }
1308 
1309 /**
1310  * checks whether the X Window with the input focus is on our X11 screen
1311  * if the window cannot be determined or inspected, resturn depends on whether there's actually
1312  * more than one screen
1313  *
1314  * this is NOT in any way related to XRandR multiscreen
1315  *
1316  */
1317 extern bool is_multihead; // main.cpp
isOnCurrentHead()1318 bool Workspace::isOnCurrentHead()
1319 {
1320     if (!is_multihead) {
1321         return true;
1322     }
1323 
1324     Xcb::CurrentInput currentInput;
1325     if (currentInput.window() == XCB_WINDOW_NONE) {
1326         return !is_multihead;
1327     }
1328 
1329     Xcb::WindowGeometry geometry(currentInput.window());
1330     if (geometry.isNull()) { // should not happen
1331         return !is_multihead;
1332     }
1333 
1334     return rootWindow() == geometry->root;
1335 }
1336 
sendClientToOutput(AbstractClient * client,AbstractOutput * output)1337 void Workspace::sendClientToOutput(AbstractClient *client, AbstractOutput *output)
1338 {
1339     client->sendToOutput(output);
1340 }
1341 
1342 /**
1343  * Delayed focus functions
1344  */
delayFocus()1345 void Workspace::delayFocus()
1346 {
1347     requestFocus(delayfocus_client);
1348     cancelDelayFocus();
1349 }
1350 
requestDelayFocus(AbstractClient * c)1351 void Workspace::requestDelayFocus(AbstractClient* c)
1352 {
1353     delayfocus_client = c;
1354     delete delayFocusTimer;
1355     delayFocusTimer = new QTimer(this);
1356     connect(delayFocusTimer, &QTimer::timeout, this, &Workspace::delayFocus);
1357     delayFocusTimer->setSingleShot(true);
1358     delayFocusTimer->start(options->delayFocusInterval());
1359 }
1360 
cancelDelayFocus()1361 void Workspace::cancelDelayFocus()
1362 {
1363     delete delayFocusTimer;
1364     delayFocusTimer = nullptr;
1365 }
1366 
checkStartupNotification(xcb_window_t w,KStartupInfoId & id,KStartupInfoData & data)1367 bool Workspace::checkStartupNotification(xcb_window_t w, KStartupInfoId &id, KStartupInfoData &data)
1368 {
1369     return m_startup->checkStartup(w, id, data) == KStartupInfo::Match;
1370 }
1371 
1372 /**
1373  * Puts the focus on a dummy window
1374  * Just using XSetInputFocus() with None would block keyboard input
1375  */
focusToNull()1376 void Workspace::focusToNull()
1377 {
1378     if (m_nullFocus) {
1379         should_get_focus.clear();
1380         m_nullFocus->focus();
1381     }
1382 }
1383 
setShowingDesktop(bool showing)1384 void Workspace::setShowingDesktop(bool showing)
1385 {
1386     const bool changed = showing != showing_desktop;
1387     if (rootInfo() && changed) {
1388         rootInfo()->setShowingDesktop(showing);
1389     }
1390     showing_desktop = showing;
1391 
1392     AbstractClient *topDesk = nullptr;
1393 
1394     { // for the blocker RAII
1395     StackingUpdatesBlocker blocker(this); // updateLayer & lowerClient would invalidate stacking_order
1396     for (int i = stacking_order.count() - 1; i > -1; --i) {
1397         AbstractClient *c = qobject_cast<AbstractClient*>(stacking_order.at(i));
1398         if (c && c->isOnCurrentDesktop()) {
1399             if (c->isDock()) {
1400                 c->updateLayer();
1401             } else if (c->isDesktop() && c->isShown(true)) {
1402                 c->updateLayer();
1403                 lowerClient(c);
1404                 if (!topDesk)
1405                     topDesk = c;
1406                 if (auto group = c->group()) {
1407                     Q_FOREACH (X11Client *cm, group->members()) {
1408                         cm->updateLayer();
1409                     }
1410                 }
1411             }
1412         }
1413     }
1414     } // ~StackingUpdatesBlocker
1415 
1416     if (showing_desktop && topDesk) {
1417         requestFocus(topDesk);
1418     } else if (!showing_desktop && changed) {
1419         const auto client = FocusChain::self()->getForActivation(VirtualDesktopManager::self()->currentDesktop());
1420         if (client) {
1421             activateClient(client);
1422         }
1423     }
1424     if (changed)
1425         Q_EMIT showingDesktopChanged(showing);
1426 }
1427 
disableGlobalShortcutsForClient(bool disable)1428 void Workspace::disableGlobalShortcutsForClient(bool disable)
1429 {
1430     if (global_shortcuts_disabled_for_client == disable)
1431         return;
1432     QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kglobalaccel"),
1433                                                           QStringLiteral("/kglobalaccel"),
1434                                                           QStringLiteral("org.kde.KGlobalAccel"),
1435                                                           QStringLiteral("blockGlobalShortcuts"));
1436     message.setArguments(QList<QVariant>() << disable);
1437     QDBusConnection::sessionBus().asyncCall(message);
1438 
1439     global_shortcuts_disabled_for_client = disable;
1440     // Update also Meta+LMB actions etc.
1441     for (auto it = m_x11Clients.constBegin();
1442             it != m_x11Clients.constEnd();
1443             ++it)
1444         (*it)->updateMouseGrab();
1445 }
1446 
supportInformation() const1447 QString Workspace::supportInformation() const
1448 {
1449     QString support;
1450     const QString yes = QStringLiteral("yes\n");
1451     const QString no = QStringLiteral("no\n");
1452 
1453     support.append(ki18nc("Introductory text shown in the support information.",
1454         "KWin Support Information:\n"
1455         "The following information should be used when requesting support on e.g. https://forum.kde.org.\n"
1456         "It provides information about the currently running instance, which options are used,\n"
1457         "what OpenGL driver and which effects are running.\n"
1458         "Please post the information provided underneath this introductory text to a paste bin service\n"
1459         "like https://paste.kde.org instead of pasting into support threads.\n").toString());
1460     support.append(QStringLiteral("\n==========================\n\n"));
1461     // all following strings are intended for support. They need to be pasted to e.g forums.kde.org
1462     // it is expected that the support will happen in English language or that the people providing
1463     // help understand English. Because of that all texts are not translated
1464     support.append(QStringLiteral("Version\n"));
1465     support.append(QStringLiteral("=======\n"));
1466     support.append(QStringLiteral("KWin version: "));
1467     support.append(QStringLiteral(KWIN_VERSION_STRING));
1468     support.append(QStringLiteral("\n"));
1469     support.append(QStringLiteral("Qt Version: "));
1470     support.append(QString::fromUtf8(qVersion()));
1471     support.append(QStringLiteral("\n"));
1472     support.append(QStringLiteral("Qt compile version: %1\n").arg(QStringLiteral(QT_VERSION_STR)));
1473     support.append(QStringLiteral("XCB compile version: %1\n\n").arg(QStringLiteral(XCB_VERSION_STRING)));
1474     support.append(QStringLiteral("Operation Mode: "));
1475     switch (kwinApp()->operationMode()) {
1476     case Application::OperationModeX11:
1477         support.append(QStringLiteral("X11 only"));
1478         break;
1479     case Application::OperationModeWaylandOnly:
1480         support.append(QStringLiteral("Wayland Only"));
1481         break;
1482     case Application::OperationModeXwayland:
1483         support.append(QStringLiteral("Xwayland"));
1484         break;
1485     }
1486     support.append(QStringLiteral("\n\n"));
1487 
1488     support.append(QStringLiteral("Build Options\n"));
1489     support.append(QStringLiteral("=============\n"));
1490 
1491     support.append(QStringLiteral("KWIN_BUILD_DECORATIONS: "));
1492 #ifdef KWIN_BUILD_DECORATIONS
1493     support.append(yes);
1494 #else
1495     support.append(no);
1496 #endif
1497     support.append(QStringLiteral("KWIN_BUILD_TABBOX: "));
1498 #ifdef KWIN_BUILD_TABBOX
1499     support.append(yes);
1500 #else
1501     support.append(no);
1502 #endif
1503     support.append(QStringLiteral("KWIN_BUILD_ACTIVITIES: "));
1504 #ifdef KWIN_BUILD_ACTIVITIES
1505     support.append(yes);
1506 #else
1507     support.append(no);
1508 #endif
1509     support.append(QStringLiteral("HAVE_GBM: "));
1510 #if HAVE_GBM
1511     support.append(yes);
1512 #else
1513     support.append(no);
1514 #endif
1515     support.append(QStringLiteral("HAVE_EGL_STREAMS: "));
1516 #if HAVE_EGL_STREAMS
1517     support.append(yes);
1518 #else
1519     support.append(no);
1520 #endif
1521     support.append(QStringLiteral("HAVE_X11_XCB: "));
1522 #if HAVE_X11_XCB
1523     support.append(yes);
1524 #else
1525     support.append(no);
1526 #endif
1527     support.append(QStringLiteral("HAVE_EPOXY_GLX: "));
1528 #if HAVE_EPOXY_GLX
1529     support.append(yes);
1530 #else
1531     support.append(no);
1532 #endif
1533     support.append(QStringLiteral("HAVE_WAYLAND_EGL: "));
1534 #if HAVE_WAYLAND_EGL
1535     support.append(yes);
1536 #else
1537     support.append(no);
1538 #endif
1539     support.append(QStringLiteral("\n"));
1540 
1541     if (auto c = kwinApp()->x11Connection()) {
1542         support.append(QStringLiteral("X11\n"));
1543         support.append(QStringLiteral("===\n"));
1544         auto x11setup = xcb_get_setup(c);
1545         support.append(QStringLiteral("Vendor: %1\n").arg(QString::fromUtf8(QByteArray::fromRawData(xcb_setup_vendor(x11setup), xcb_setup_vendor_length(x11setup)))));
1546         support.append(QStringLiteral("Vendor Release: %1\n").arg(x11setup->release_number));
1547         support.append(QStringLiteral("Protocol Version/Revision: %1/%2\n").arg(x11setup->protocol_major_version).arg(x11setup->protocol_minor_version));
1548         const auto extensions = Xcb::Extensions::self()->extensions();
1549         for (const auto &e : extensions) {
1550             support.append(QStringLiteral("%1: %2; Version: 0x%3\n")
1551                                .arg(QString::fromUtf8(e.name), e.present ? yes.trimmed() : no.trimmed(), QString::number(e.version, 16)));
1552         }
1553         support.append(QStringLiteral("\n"));
1554     }
1555 
1556     if (auto bridge = Decoration::DecorationBridge::self()) {
1557         support.append(QStringLiteral("Decoration\n"));
1558         support.append(QStringLiteral("==========\n"));
1559         support.append(bridge->supportInformation());
1560         support.append(QStringLiteral("\n"));
1561     }
1562     support.append(QStringLiteral("Platform\n"));
1563     support.append(QStringLiteral("==========\n"));
1564     support.append(kwinApp()->platform()->supportInformation());
1565     support.append(QStringLiteral("\n"));
1566 
1567     const Cursor *cursor = Cursors::self()->mouse();
1568     support.append(QLatin1String("Cursor\n"));
1569     support.append(QLatin1String("======\n"));
1570     support.append(QLatin1String("themeName: ") + cursor->themeName() + QLatin1Char('\n'));
1571     support.append(QLatin1String("themeSize: ") + QString::number(cursor->themeSize()) + QLatin1Char('\n'));
1572     support.append(QLatin1Char('\n'));
1573 
1574     support.append(QStringLiteral("Options\n"));
1575     support.append(QStringLiteral("=======\n"));
1576     const QMetaObject *metaOptions = options->metaObject();
1577     auto printProperty = [] (const QVariant &variant) {
1578         if (variant.type() == QVariant::Size) {
1579             const QSize &s = variant.toSize();
1580             return QStringLiteral("%1x%2").arg(s.width()).arg(s.height());
1581         }
1582         if (QLatin1String(variant.typeName()) == QLatin1String("KWin::OpenGLPlatformInterface") ||
1583                 QLatin1String(variant.typeName()) == QLatin1String("KWin::Options::WindowOperation")) {
1584             return QString::number(variant.toInt());
1585         }
1586         return variant.toString();
1587     };
1588     for (int i=0; i<metaOptions->propertyCount(); ++i) {
1589         const QMetaProperty property = metaOptions->property(i);
1590         if (QLatin1String(property.name()) == QLatin1String("objectName")) {
1591             continue;
1592         }
1593         support.append(QStringLiteral("%1: %2\n").arg(property.name(), printProperty(options->property(property.name()))));
1594     }
1595     support.append(QStringLiteral("\nScreen Edges\n"));
1596     support.append(QStringLiteral(  "============\n"));
1597     const QMetaObject *metaScreenEdges = ScreenEdges::self()->metaObject();
1598     for (int i=0; i<metaScreenEdges->propertyCount(); ++i) {
1599         const QMetaProperty property = metaScreenEdges->property(i);
1600         if (QLatin1String(property.name()) == QLatin1String("objectName")) {
1601             continue;
1602         }
1603         support.append(QStringLiteral("%1: %2\n").arg(property.name(), printProperty(ScreenEdges::self()->property(property.name()))));
1604     }
1605     support.append(QStringLiteral("\nScreens\n"));
1606     support.append(QStringLiteral(  "=======\n"));
1607     support.append(QStringLiteral("Multi-Head: "));
1608     if (is_multihead) {
1609         support.append(QStringLiteral("yes\n"));
1610         support.append(QStringLiteral("Head: %1\n").arg(screen_number));
1611     } else {
1612         support.append(QStringLiteral("no\n"));
1613     }
1614     support.append(QStringLiteral("Active screen follows mouse: "));
1615     if (options->activeMouseScreen())
1616         support.append(QStringLiteral(" yes\n"));
1617     else
1618         support.append(QStringLiteral(" no\n"));
1619     const QVector<AbstractOutput *> outputs = kwinApp()->platform()->enabledOutputs();
1620     support.append(QStringLiteral("Number of Screens: %1\n\n").arg(outputs.count()));
1621     for (int i = 0; i < outputs.count(); ++i) {
1622         const auto output = outputs[i];
1623         const QRect geo = outputs[i]->geometry();
1624         support.append(QStringLiteral("Screen %1:\n").arg(i));
1625         support.append(QStringLiteral("---------\n"));
1626         support.append(QStringLiteral("Name: %1\n").arg(output->name()));
1627         support.append(QStringLiteral("Geometry: %1,%2,%3x%4\n")
1628                               .arg(geo.x())
1629                               .arg(geo.y())
1630                               .arg(geo.width())
1631                               .arg(geo.height()));
1632         support.append(QStringLiteral("Scale: %1\n").arg(output->scale()));
1633         support.append(QStringLiteral("Refresh Rate: %1\n").arg(output->refreshRate()));
1634         const auto waylandOutput = qobject_cast<AbstractWaylandOutput *>(output);
1635         if (waylandOutput) {
1636             QString vrr = QStringLiteral("incapable");
1637             if (waylandOutput->capabilities() & AbstractWaylandOutput::Capability::Vrr) {
1638                 switch (waylandOutput->vrrPolicy()) {
1639                 case RenderLoop::VrrPolicy::Never:
1640                     vrr = QStringLiteral("never");
1641                     break;
1642                 case RenderLoop::VrrPolicy::Always:
1643                     vrr = QStringLiteral("always");
1644                     break;
1645                 case RenderLoop::VrrPolicy::Automatic:
1646                     vrr = QStringLiteral("automatic");
1647                     break;
1648                 }
1649             }
1650             support.append(QStringLiteral("Adaptive Sync: %1\n").arg(vrr));
1651         }
1652     }
1653     support.append(QStringLiteral("\nCompositing\n"));
1654     support.append(QStringLiteral(  "===========\n"));
1655     if (effects) {
1656         support.append(QStringLiteral("Compositing is active\n"));
1657         switch (effects->compositingType()) {
1658         case OpenGLCompositing: {
1659             GLPlatform *platform = GLPlatform::instance();
1660             if (platform->isGLES()) {
1661                 support.append(QStringLiteral("Compositing Type: OpenGL ES 2.0\n"));
1662             } else {
1663                 support.append(QStringLiteral("Compositing Type: OpenGL\n"));
1664             }
1665             support.append(QStringLiteral("OpenGL vendor string: ") +   QString::fromUtf8(platform->glVendorString()) + QStringLiteral("\n"));
1666             support.append(QStringLiteral("OpenGL renderer string: ") + QString::fromUtf8(platform->glRendererString()) + QStringLiteral("\n"));
1667             support.append(QStringLiteral("OpenGL version string: ") +  QString::fromUtf8(platform->glVersionString()) + QStringLiteral("\n"));
1668             support.append(QStringLiteral("OpenGL platform interface: "));
1669             switch (platform->platformInterface()) {
1670             case GlxPlatformInterface:
1671                 support.append(QStringLiteral("GLX"));
1672                 break;
1673             case EglPlatformInterface:
1674                 support.append(QStringLiteral("EGL"));
1675                 break;
1676             default:
1677                 support.append(QStringLiteral("UNKNOWN"));
1678             }
1679             support.append(QStringLiteral("\n"));
1680 
1681             if (platform->supports(LimitedGLSL) || platform->supports(GLSL))
1682                 support.append(QStringLiteral("OpenGL shading language version string: ") + QString::fromUtf8(platform->glShadingLanguageVersionString()) + QStringLiteral("\n"));
1683 
1684             support.append(QStringLiteral("Driver: ") + GLPlatform::driverToString(platform->driver()) + QStringLiteral("\n"));
1685             if (!platform->isMesaDriver())
1686                 support.append(QStringLiteral("Driver version: ") + GLPlatform::versionToString(platform->driverVersion()) + QStringLiteral("\n"));
1687 
1688             support.append(QStringLiteral("GPU class: ") + GLPlatform::chipClassToString(platform->chipClass()) + QStringLiteral("\n"));
1689 
1690             support.append(QStringLiteral("OpenGL version: ") + GLPlatform::versionToString(platform->glVersion()) + QStringLiteral("\n"));
1691 
1692             if (platform->supports(LimitedGLSL) || platform->supports(GLSL))
1693                 support.append(QStringLiteral("GLSL version: ") + GLPlatform::versionToString(platform->glslVersion()) + QStringLiteral("\n"));
1694 
1695             if (platform->isMesaDriver())
1696                 support.append(QStringLiteral("Mesa version: ") + GLPlatform::versionToString(platform->mesaVersion()) + QStringLiteral("\n"));
1697             if (platform->serverVersion() > 0)
1698                 support.append(QStringLiteral("X server version: ") + GLPlatform::versionToString(platform->serverVersion()) + QStringLiteral("\n"));
1699             if (platform->kernelVersion() > 0)
1700                 support.append(QStringLiteral("Linux kernel version: ") + GLPlatform::versionToString(platform->kernelVersion()) + QStringLiteral("\n"));
1701 
1702             support.append(QStringLiteral("Direct rendering: "));
1703             support.append(QStringLiteral("Requires strict binding: "));
1704             if (!platform->isLooseBinding()) {
1705                 support.append(QStringLiteral("yes\n"));
1706             } else {
1707                 support.append(QStringLiteral("no\n"));
1708             }
1709             support.append(QStringLiteral("GLSL shaders: "));
1710             if (platform->supports(GLSL)) {
1711                 if (platform->supports(LimitedGLSL)) {
1712                     support.append(QStringLiteral(" limited\n"));
1713                 } else {
1714                     support.append(QStringLiteral(" yes\n"));
1715                 }
1716             } else {
1717                 support.append(QStringLiteral(" no\n"));
1718             }
1719             support.append(QStringLiteral("Texture NPOT support: "));
1720             if (platform->supports(TextureNPOT)) {
1721                 if (platform->supports(LimitedNPOT)) {
1722                     support.append(QStringLiteral(" limited\n"));
1723                 } else {
1724                     support.append(QStringLiteral(" yes\n"));
1725                 }
1726             } else {
1727                 support.append(QStringLiteral(" no\n"));
1728             }
1729             support.append(QStringLiteral("Virtual Machine: "));
1730             if (platform->isVirtualMachine()) {
1731                 support.append(QStringLiteral(" yes\n"));
1732             } else {
1733                 support.append(QStringLiteral(" no\n"));
1734             }
1735 
1736             support.append(QStringLiteral("OpenGL 2 Shaders are used\n"));
1737             break;
1738         }
1739         case QPainterCompositing:
1740             support.append("Compositing Type: QPainter\n");
1741             break;
1742         case NoCompositing:
1743         default:
1744             support.append(QStringLiteral("Something is really broken, neither OpenGL nor QPainter is used"));
1745         }
1746         support.append(QStringLiteral("\nLoaded Effects:\n"));
1747         support.append(QStringLiteral(  "---------------\n"));
1748         Q_FOREACH (const QString &effect, static_cast<EffectsHandlerImpl*>(effects)->loadedEffects()) {
1749             support.append(effect + QStringLiteral("\n"));
1750         }
1751         support.append(QStringLiteral("\nCurrently Active Effects:\n"));
1752         support.append(QStringLiteral(  "-------------------------\n"));
1753         Q_FOREACH (const QString &effect, static_cast<EffectsHandlerImpl*>(effects)->activeEffects()) {
1754             support.append(effect + QStringLiteral("\n"));
1755         }
1756         support.append(QStringLiteral("\nEffect Settings:\n"));
1757         support.append(QStringLiteral(  "----------------\n"));
1758         Q_FOREACH (const QString &effect, static_cast<EffectsHandlerImpl*>(effects)->loadedEffects()) {
1759             support.append(static_cast<EffectsHandlerImpl*>(effects)->supportInformation(effect));
1760             support.append(QStringLiteral("\n"));
1761         }
1762         support.append(QLatin1String("\nLoaded Plugins:\n"));
1763         support.append(QLatin1String("---------------\n"));
1764         QStringList loadedPlugins = PluginManager::self()->loadedPlugins();
1765         loadedPlugins.sort();
1766         for (const QString &plugin : qAsConst(loadedPlugins)) {
1767             support.append(plugin + QLatin1Char('\n'));
1768         }
1769         support.append(QLatin1String("\nAvailable Plugins:\n"));
1770         support.append(QLatin1String("------------------\n"));
1771         QStringList availablePlugins = PluginManager::self()->availablePlugins();
1772         availablePlugins.sort();
1773         for (const QString &plugin : qAsConst(availablePlugins)) {
1774             support.append(plugin + QLatin1Char('\n'));
1775         }
1776     } else {
1777         support.append(QStringLiteral("Compositing is not active\n"));
1778     }
1779     return support;
1780 }
1781 
findClient(std::function<bool (const X11Client *)> func) const1782 X11Client *Workspace::findClient(std::function<bool (const X11Client *)> func) const
1783 {
1784     if (X11Client *ret = Toplevel::findInList(m_x11Clients, func)) {
1785         return ret;
1786     }
1787     return nullptr;
1788 }
1789 
findAbstractClient(std::function<bool (const AbstractClient *)> func) const1790 AbstractClient *Workspace::findAbstractClient(std::function<bool (const AbstractClient*)> func) const
1791 {
1792     if (AbstractClient *ret = Toplevel::findInList(m_allClients, func)) {
1793         return ret;
1794     }
1795     if (InternalClient *ret = Toplevel::findInList(m_internalClients, func)) {
1796         return ret;
1797     }
1798     return nullptr;
1799 }
1800 
findAbstractClient(const QUuid & internalId) const1801 AbstractClient *Workspace::findAbstractClient(const QUuid &internalId) const
1802 {
1803     return qobject_cast<AbstractClient *>(findToplevel(internalId));
1804 }
1805 
findUnmanaged(std::function<bool (const Unmanaged *)> func) const1806 Unmanaged *Workspace::findUnmanaged(std::function<bool (const Unmanaged*)> func) const
1807 {
1808     return Toplevel::findInList(m_unmanaged, func);
1809 }
1810 
findUnmanaged(xcb_window_t w) const1811 Unmanaged *Workspace::findUnmanaged(xcb_window_t w) const
1812 {
1813     return findUnmanaged([w](const Unmanaged *u) {
1814         return u->window() == w;
1815     });
1816 }
1817 
findClient(Predicate predicate,xcb_window_t w) const1818 X11Client *Workspace::findClient(Predicate predicate, xcb_window_t w) const
1819 {
1820     switch (predicate) {
1821     case Predicate::WindowMatch:
1822         return findClient([w](const X11Client *c) {
1823             return c->window() == w;
1824         });
1825     case Predicate::WrapperIdMatch:
1826         return findClient([w](const X11Client *c) {
1827             return c->wrapperId() == w;
1828         });
1829     case Predicate::FrameIdMatch:
1830         return findClient([w](const X11Client *c) {
1831             return c->frameId() == w;
1832         });
1833     case Predicate::InputIdMatch:
1834         return findClient([w](const X11Client *c) {
1835             return c->inputId() == w;
1836         });
1837     }
1838     return nullptr;
1839 }
1840 
findToplevel(std::function<bool (const Toplevel *)> func) const1841 Toplevel *Workspace::findToplevel(std::function<bool (const Toplevel*)> func) const
1842 {
1843     if (auto *ret = Toplevel::findInList(m_allClients, func)) {
1844         return ret;
1845     }
1846     if (Unmanaged *ret = Toplevel::findInList(m_unmanaged, func)) {
1847         return ret;
1848     }
1849     if (InternalClient *ret = Toplevel::findInList(m_internalClients, func)) {
1850         return ret;
1851     }
1852     return nullptr;
1853 }
1854 
findToplevel(const QUuid & internalId) const1855 Toplevel *Workspace::findToplevel(const QUuid &internalId) const
1856 {
1857     return findToplevel([internalId] (const KWin::Toplevel* l) -> bool {
1858         return internalId == l->internalId();
1859     });
1860 }
1861 
forEachToplevel(std::function<void (Toplevel *)> func)1862 void Workspace::forEachToplevel(std::function<void (Toplevel *)> func)
1863 {
1864     std::for_each(m_allClients.constBegin(), m_allClients.constEnd(), func);
1865     std::for_each(deleted.constBegin(), deleted.constEnd(), func);
1866     std::for_each(m_unmanaged.constBegin(), m_unmanaged.constEnd(), func);
1867     std::for_each(m_internalClients.constBegin(), m_internalClients.constEnd(), func);
1868 }
1869 
hasClient(const AbstractClient * c)1870 bool Workspace::hasClient(const AbstractClient *c)
1871 {
1872     return findAbstractClient([&c](const AbstractClient *test) {
1873         return test == c;
1874     }) != nullptr;
1875 }
1876 
forEachAbstractClient(std::function<void (AbstractClient *)> func)1877 void Workspace::forEachAbstractClient(std::function< void (AbstractClient*) > func)
1878 {
1879     std::for_each(m_allClients.constBegin(), m_allClients.constEnd(), func);
1880     std::for_each(m_internalClients.constBegin(), m_internalClients.constEnd(), func);
1881 }
1882 
findInternal(QWindow * w) const1883 Toplevel *Workspace::findInternal(QWindow *w) const
1884 {
1885     if (!w) {
1886         return nullptr;
1887     }
1888     if (kwinApp()->operationMode() == Application::OperationModeX11) {
1889         return findUnmanaged(w->winId());
1890     }
1891     for (InternalClient *client : m_internalClients) {
1892         if (client->internalWindow() == w) {
1893             return client;
1894         }
1895     }
1896     return nullptr;
1897 }
1898 
markXStackingOrderAsDirty()1899 void Workspace::markXStackingOrderAsDirty()
1900 {
1901     m_xStackingDirty = true;
1902     if (kwinApp()->x11Connection() && !kwinApp()->isClosingX11Connection()) {
1903         m_xStackingQueryTree.reset(new Xcb::Tree(kwinApp()->x11RootWindow()));
1904     }
1905 }
1906 
setWasUserInteraction()1907 void Workspace::setWasUserInteraction()
1908 {
1909     if (was_user_interaction) {
1910         return;
1911     }
1912     was_user_interaction = true;
1913     // might be called from within the filter, so delay till we now the filter returned
1914     QTimer::singleShot(0, this,
1915         [this] {
1916             m_wasUserInteractionFilter.reset();
1917         }
1918     );
1919 }
1920 
updateTabbox()1921 void Workspace::updateTabbox()
1922 {
1923 #ifdef KWIN_BUILD_TABBOX
1924     TabBox::TabBox *tabBox = TabBox::TabBox::self();
1925     if (tabBox->isDisplayed()) {
1926         tabBox->reset(true);
1927     }
1928 #endif
1929 }
1930 
addInternalClient(InternalClient * client)1931 void Workspace::addInternalClient(InternalClient *client)
1932 {
1933     m_internalClients.append(client);
1934     addToStack(client);
1935 
1936     setupClientConnections(client);
1937     client->updateLayer();
1938 
1939     if (client->isPlaceable()) {
1940         const QRect area = clientArea(PlacementArea, client, workspace()->activeOutput());
1941         client->placeIn(area);
1942     }
1943 
1944     markXStackingOrderAsDirty();
1945     updateStackingOrder(true);
1946     updateClientArea();
1947 
1948     Q_EMIT internalClientAdded(client);
1949 }
1950 
removeInternalClient(InternalClient * client)1951 void Workspace::removeInternalClient(InternalClient *client)
1952 {
1953     m_internalClients.removeOne(client);
1954 
1955     markXStackingOrderAsDirty();
1956     updateStackingOrder(true);
1957     updateClientArea();
1958 
1959     Q_EMIT internalClientRemoved(client);
1960 }
1961 
findGroup(xcb_window_t leader) const1962 Group* Workspace::findGroup(xcb_window_t leader) const
1963 {
1964     Q_ASSERT(leader != XCB_WINDOW_NONE);
1965     for (auto it = groups.constBegin();
1966             it != groups.constEnd();
1967             ++it)
1968         if ((*it)->leader() == leader)
1969             return *it;
1970     return nullptr;
1971 }
1972 
1973 // Client is group transient, but has no group set. Try to find
1974 // group with windows with the same client leader.
findClientLeaderGroup(const X11Client * c) const1975 Group* Workspace::findClientLeaderGroup(const X11Client *c) const
1976 {
1977     Group* ret = nullptr;
1978     for (auto it = m_x11Clients.constBegin();
1979             it != m_x11Clients.constEnd();
1980             ++it) {
1981         if (*it == c)
1982             continue;
1983         if ((*it)->wmClientLeader() == c->wmClientLeader()) {
1984             if (ret == nullptr || ret == (*it)->group())
1985                 ret = (*it)->group();
1986             else {
1987                 // There are already two groups with the same client leader.
1988                 // This most probably means the app uses group transients without
1989                 // setting group for its windows. Merging the two groups is a bad
1990                 // hack, but there's no really good solution for this case.
1991                 QList<X11Client *> old_group = (*it)->group()->members();
1992                 // old_group autodeletes when being empty
1993                 for (int pos = 0;
1994                         pos < old_group.count();
1995                         ++pos) {
1996                     X11Client *tmp = old_group[ pos ];
1997                     if (tmp != c)
1998                         tmp->changeClientLeaderGroup(ret);
1999                 }
2000             }
2001         }
2002     }
2003     return ret;
2004 }
2005 
updateMinimizedOfTransients(AbstractClient * c)2006 void Workspace::updateMinimizedOfTransients(AbstractClient* c)
2007 {
2008     // if mainwindow is minimized or shaded, minimize transients too
2009     if (c->isMinimized()) {
2010         for (auto it = c->transients().constBegin();
2011                 it != c->transients().constEnd();
2012                 ++it) {
2013             if ((*it)->isModal())
2014                 continue; // there's no reason to hide modal dialogs with the main client
2015             // but to keep them to eg. watch progress or whatever
2016             if (!(*it)->isMinimized()) {
2017                 (*it)->minimize();
2018                 updateMinimizedOfTransients((*it));
2019             }
2020         }
2021         if (c->isModal()) { // if a modal dialog is minimized, minimize its mainwindow too
2022             Q_FOREACH (AbstractClient * c2, c->mainClients())
2023             c2->minimize();
2024         }
2025     } else {
2026         // else unmiminize the transients
2027         for (auto it = c->transients().constBegin();
2028                 it != c->transients().constEnd();
2029                 ++it) {
2030             if ((*it)->isMinimized()) {
2031                 (*it)->unminimize();
2032                 updateMinimizedOfTransients((*it));
2033             }
2034         }
2035         if (c->isModal()) {
2036             Q_FOREACH (AbstractClient * c2, c->mainClients())
2037             c2->unminimize();
2038         }
2039     }
2040 }
2041 
2042 
2043 /**
2044  * Sets the client \a c's transient windows' on_all_desktops property to \a on_all_desktops.
2045  */
updateOnAllDesktopsOfTransients(AbstractClient * c)2046 void Workspace::updateOnAllDesktopsOfTransients(AbstractClient* c)
2047 {
2048     for (auto it = c->transients().constBegin();
2049             it != c->transients().constEnd();
2050             ++it) {
2051         if ((*it)->isOnAllDesktops() != c->isOnAllDesktops())
2052             (*it)->setOnAllDesktops(c->isOnAllDesktops());
2053     }
2054 }
2055 
2056 // A new window has been mapped. Check if it's not a mainwindow for some already existing transient window.
checkTransients(xcb_window_t w)2057 void Workspace::checkTransients(xcb_window_t w)
2058 {
2059     for (auto it = m_x11Clients.constBegin();
2060             it != m_x11Clients.constEnd();
2061             ++it)
2062         (*it)->checkTransient(w);
2063 }
2064 
2065 /**
2066  * Resizes the workspace after an XRANDR screen size change
2067  */
desktopResized()2068 void Workspace::desktopResized()
2069 {
2070     const auto outputs = kwinApp()->platform()->enabledOutputs();
2071 
2072     m_geometry = QRect();
2073     for (const AbstractOutput *output : outputs) {
2074         m_geometry = m_geometry.united(output->geometry());
2075     }
2076 
2077     if (rootInfo()) {
2078         NETSize desktop_geometry;
2079         desktop_geometry.width = m_geometry.width();
2080         desktop_geometry.height = m_geometry.height();
2081         rootInfo()->setDesktopGeometry(desktop_geometry);
2082     }
2083 
2084     updateClientArea();
2085     saveOldScreenSizes(); // after updateClientArea(), so that one still uses the previous one
2086 
2087     // TODO: emit a signal instead and remove the deep function calls into edges and effects
2088     ScreenEdges::self()->recreateEdges();
2089 }
2090 
saveOldScreenSizes()2091 void Workspace::saveOldScreenSizes()
2092 {
2093     olddisplaysize = m_geometry.size();
2094     oldscreensizes.clear();
2095 
2096     const auto outputs = kwinApp()->platform()->enabledOutputs();
2097     for (const AbstractOutput *output : outputs) {
2098         oldscreensizes.append(output->geometry());
2099     }
2100 }
2101 
2102 /**
2103  * Whether or not the window has a strut that expands through the invisible area of
2104  * an xinerama setup where the monitors are not the same resolution.
2105  */
hasOffscreenXineramaStrut(AbstractClient * client)2106 static bool hasOffscreenXineramaStrut(AbstractClient *client)
2107 {
2108     // Get strut as a QRegion
2109     QRegion region;
2110     region += client->strutRect(StrutAreaTop);
2111     region += client->strutRect(StrutAreaRight);
2112     region += client->strutRect(StrutAreaBottom);
2113     region += client->strutRect(StrutAreaLeft);
2114 
2115     // Remove all visible areas so that only the invisible remain
2116     const auto outputs = kwinApp()->platform()->enabledOutputs();
2117     for (const AbstractOutput *output : outputs) {
2118         region -= output->geometry();
2119     }
2120 
2121     // If there's anything left then we have an offscreen strut
2122     return !region.isEmpty();
2123 }
2124 
adjustClientArea(AbstractClient * client,const QRect & area) const2125 QRect Workspace::adjustClientArea(AbstractClient *client, const QRect &area) const
2126 {
2127     QRect adjustedArea = area;
2128 
2129     QRect strutLeft = client->strutRect(StrutAreaLeft);
2130     QRect strutRight = client->strutRect(StrutAreaRight);
2131     QRect strutTop = client->strutRect(StrutAreaTop);
2132     QRect strutBottom = client->strutRect(StrutAreaBottom);
2133 
2134     QRect screenArea = clientArea(ScreenArea, client);
2135     // HACK: workarea handling is not xinerama aware, so if this strut
2136     // reserves place at a xinerama edge that's inside the virtual screen,
2137     // ignore the strut for workspace setting.
2138     if (area == QRect(QPoint(0, 0), m_geometry.size())) {
2139         if (strutLeft.left() < screenArea.left()) {
2140             strutLeft = QRect();
2141         }
2142         if (strutRight.right() > screenArea.right()) {
2143             strutRight = QRect();
2144         }
2145         if (strutTop.top() < screenArea.top()) {
2146             strutTop = QRect();
2147         }
2148         if (strutBottom.bottom() < screenArea.bottom()) {
2149             strutBottom = QRect();
2150         }
2151     }
2152 
2153     // Handle struts at xinerama edges that are inside the virtual screen.
2154     // They're given in virtual screen coordinates, make them affect only
2155     // their xinerama screen.
2156     strutLeft.setLeft(qMax(strutLeft.left(), screenArea.left()));
2157     strutRight.setRight(qMin(strutRight.right(), screenArea.right()));
2158     strutTop.setTop(qMax(strutTop.top(), screenArea.top()));
2159     strutBottom.setBottom(qMin(strutBottom.bottom(), screenArea.bottom()));
2160 
2161     if (strutLeft.intersects(area)) {
2162         adjustedArea.setLeft(strutLeft.right() + 1);
2163     }
2164     if (strutRight.intersects(area)) {
2165         adjustedArea.setRight(strutRight.left() - 1);
2166     }
2167     if (strutTop.intersects(area)) {
2168         adjustedArea.setTop(strutTop.bottom() + 1);
2169     }
2170     if (strutBottom.intersects(area)) {
2171         adjustedArea.setBottom(strutBottom.top() - 1);
2172     }
2173 
2174     return adjustedArea;
2175 }
2176 
2177 /**
2178  * Updates the current client areas according to the current clients.
2179  *
2180  * The client area is the area that is available for clients (that
2181  * which is not taken by windows like panels, the top-of-screen menu
2182  * etc).
2183  *
2184  * @see clientArea()
2185  */
updateClientArea()2186 void Workspace::updateClientArea()
2187 {
2188     const QVector<AbstractOutput *> outputs = kwinApp()->platform()->enabledOutputs();
2189     const QVector<VirtualDesktop *> desktops = VirtualDesktopManager::self()->desktops();
2190 
2191     QHash<const VirtualDesktop *, QRect> workAreas;
2192     QHash<const VirtualDesktop *, StrutRects> restrictedAreas;
2193     QHash<const VirtualDesktop *, QHash<const AbstractOutput *, QRect>> screenAreas;
2194 
2195     for (const VirtualDesktop *desktop : desktops) {
2196         workAreas[desktop] = m_geometry;
2197 
2198         for (const AbstractOutput *output : outputs) {
2199             screenAreas[desktop][output] = output->geometry();
2200         }
2201     }
2202 
2203     for (AbstractClient *client : qAsConst(m_allClients)) {
2204         if (!client->hasStrut()) {
2205             continue;
2206         }
2207         QRect r = adjustClientArea(client, m_geometry);
2208 
2209         // This happens sometimes when the workspace size changes and the
2210         // struted clients haven't repositioned yet
2211         if (!r.isValid()) {
2212             continue;
2213         }
2214         // sanity check that a strut doesn't exclude a complete screen geometry
2215         // this is a violation to EWMH, as KWin just ignores the strut
2216         for (const AbstractOutput *output : outputs) {
2217             if (!r.intersects(output->geometry())) {
2218                 qCDebug(KWIN_CORE) << "Adjusted client area would exclude a complete screen, ignore";
2219                 r = m_geometry;
2220                 break;
2221             }
2222         }
2223         StrutRects strutRegion = client->strutRects();
2224         const QRect clientsScreenRect = client->output()->geometry();
2225         for (auto strut = strutRegion.begin(); strut != strutRegion.end(); strut++) {
2226             *strut = StrutRect((*strut).intersected(clientsScreenRect), (*strut).area());
2227         }
2228 
2229         // Ignore offscreen xinerama struts. These interfere with the larger monitors on the setup
2230         // and should be ignored so that applications that use the work area to work out where
2231         // windows can go can use the entire visible area of the larger monitors.
2232         // This goes against the EWMH description of the work area but it is a toss up between
2233         // having unusable sections of the screen (Which can be quite large with newer monitors)
2234         // or having some content appear offscreen (Relatively rare compared to other).
2235         bool hasOffscreenStrut = hasOffscreenXineramaStrut(client);
2236 
2237         const auto vds = client->isOnAllDesktops() ? desktops : client->desktops();
2238         for (VirtualDesktop *vd : vds) {
2239             if (!hasOffscreenStrut) {
2240                 workAreas[vd] &= r;
2241             }
2242             restrictedAreas[vd] += strutRegion;
2243             for (AbstractOutput *output : outputs) {
2244                 const auto geo = screenAreas[vd][output].intersected(adjustClientArea(client, output->geometry()));
2245                 // ignore the geometry if it results in the screen getting removed completely
2246                 if (!geo.isEmpty()) {
2247                     screenAreas[vd][output] = geo;
2248                 }
2249             }
2250         }
2251     }
2252 
2253     if (m_workAreas != workAreas || m_restrictedAreas != restrictedAreas || m_screenAreas != screenAreas) {
2254         m_workAreas = workAreas;
2255         m_screenAreas = screenAreas;
2256 
2257         m_oldRestrictedAreas = m_restrictedAreas;
2258         m_restrictedAreas = restrictedAreas;
2259 
2260         if (rootInfo()) {
2261             NETRect r;
2262             for (VirtualDesktop *desktop : desktops) {
2263                 const QRect &workArea = m_workAreas[desktop];
2264                 r.pos.x = workArea.x();
2265                 r.pos.y = workArea.y();
2266                 r.size.width = workArea.width();
2267                 r.size.height = workArea.height();
2268                 rootInfo()->setWorkArea(desktop->x11DesktopNumber(), r);
2269             }
2270         }
2271 
2272         for (auto it = m_allClients.constBegin();
2273                 it != m_allClients.constEnd();
2274                 ++it) {
2275             (*it)->checkWorkspacePosition();
2276         }
2277 
2278         m_oldRestrictedAreas.clear(); // reset, no longer valid or needed
2279     }
2280 }
2281 
2282 /**
2283  * Returns the area available for clients. This is the desktop
2284  * geometry minus windows on the dock. Placement algorithms should
2285  * refer to this rather than Screens::geometry.
2286  */
clientArea(clientAreaOption opt,const AbstractOutput * output,const VirtualDesktop * desktop) const2287 QRect Workspace::clientArea(clientAreaOption opt, const AbstractOutput *output, const VirtualDesktop *desktop) const
2288 {
2289     QRect workArea;
2290 
2291     const AbstractOutput *effectiveOutput = output;
2292     if (is_multihead) {
2293         effectiveOutput = kwinApp()->platform()->findOutput(screen_number);
2294     }
2295 
2296     QRect screenArea = m_screenAreas[desktop][effectiveOutput];
2297     if (screenArea.isNull()) { // screens may be missing during KWin initialization or screen config changes
2298         screenArea = effectiveOutput->geometry();
2299     }
2300 
2301     if (is_multihead) {
2302         workArea = m_workAreas[desktop];
2303         if (workArea.isNull()) {
2304             workArea = effectiveOutput->geometry();
2305         }
2306     } else {
2307         workArea = m_workAreas[desktop];
2308         if (workArea.isNull()) {
2309             workArea = QRect(QPoint(0, 0), m_geometry.size());
2310         }
2311     }
2312 
2313     switch(opt) {
2314     case MaximizeArea:
2315     case PlacementArea:
2316         return screenArea;
2317     case MaximizeFullArea:
2318     case FullScreenArea:
2319     case MovementArea:
2320     case ScreenArea:
2321         return effectiveOutput->geometry();
2322     case WorkArea:
2323         if (is_multihead)
2324             return screenArea;
2325         else
2326             return workArea;
2327     case FullArea:
2328         return QRect(QPoint(0, 0), m_geometry.size());
2329 
2330     default:
2331         Q_UNREACHABLE();
2332     }
2333 }
2334 
clientArea(clientAreaOption opt,int screen,int desktop) const2335 QRect Workspace::clientArea(clientAreaOption opt, int screen, int desktop) const
2336 {
2337     VirtualDesktop *virtualDesktop;
2338     AbstractOutput *output;
2339 
2340     if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) {
2341         virtualDesktop = VirtualDesktopManager::self()->currentDesktop();
2342     } else {
2343         virtualDesktop = VirtualDesktopManager::self()->desktopForX11Id(desktop);
2344         Q_ASSERT(virtualDesktop);
2345     }
2346 
2347     if (screen == -1) {
2348         output = activeOutput();
2349     } else {
2350         output = kwinApp()->platform()->findOutput(screen);
2351         Q_ASSERT(output);
2352     }
2353 
2354     return clientArea(opt, output, virtualDesktop);
2355 }
2356 
clientArea(clientAreaOption opt,const QPoint & p,int desktop) const2357 QRect Workspace::clientArea(clientAreaOption opt, const QPoint& p, int desktop) const
2358 {
2359     return clientArea(opt, screens()->number(p), desktop);
2360 }
2361 
clientArea(clientAreaOption opt,const Toplevel * window) const2362 QRect Workspace::clientArea(clientAreaOption opt, const Toplevel *window) const
2363 {
2364     return clientArea(opt, window, window->frameGeometry().center());
2365 }
2366 
clientArea(clientAreaOption opt,const Toplevel * window,const AbstractOutput * output) const2367 QRect Workspace::clientArea(clientAreaOption opt, const Toplevel *window, const AbstractOutput *output) const
2368 {
2369     return clientArea(opt, window, output->geometry().center());
2370 }
2371 
clientArea(clientAreaOption opt,const Toplevel * window,const QPoint & pos) const2372 QRect Workspace::clientArea(clientAreaOption opt, const Toplevel *window, const QPoint &pos) const
2373 {
2374     return clientArea(opt, screens()->number(pos), window->desktop());
2375 }
2376 
geometry() const2377 QRect Workspace::geometry() const
2378 {
2379     return m_geometry;
2380 }
2381 
strutsToRegion(StrutAreas areas,const StrutRects & strut)2382 static QRegion strutsToRegion(StrutAreas areas, const StrutRects &strut)
2383 {
2384     QRegion region;
2385     for (const StrutRect &rect : strut) {
2386         if (areas & rect.area()) {
2387             region += rect;
2388         }
2389     }
2390     return region;
2391 }
2392 
restrictedMoveArea(const VirtualDesktop * desktop,StrutAreas areas) const2393 QRegion Workspace::restrictedMoveArea(const VirtualDesktop *desktop, StrutAreas areas) const
2394 {
2395     return strutsToRegion(areas, m_restrictedAreas[desktop]);
2396 }
2397 
inUpdateClientArea() const2398 bool Workspace::inUpdateClientArea() const
2399 {
2400     return !m_oldRestrictedAreas.isEmpty();
2401 }
2402 
previousRestrictedMoveArea(const VirtualDesktop * desktop,StrutAreas areas) const2403 QRegion Workspace::previousRestrictedMoveArea(const VirtualDesktop *desktop, StrutAreas areas) const
2404 {
2405     return strutsToRegion(areas, m_oldRestrictedAreas[desktop]);
2406 }
2407 
previousScreenSizes() const2408 QVector< QRect > Workspace::previousScreenSizes() const
2409 {
2410     return oldscreensizes;
2411 }
2412 
oldDisplayWidth() const2413 int Workspace::oldDisplayWidth() const
2414 {
2415     return olddisplaysize.width();
2416 }
2417 
oldDisplayHeight() const2418 int Workspace::oldDisplayHeight() const
2419 {
2420     return olddisplaysize.height();
2421 }
2422 
activeOutput() const2423 AbstractOutput *Workspace::activeOutput() const
2424 {
2425     if (options->activeMouseScreen()) {
2426         return kwinApp()->platform()->outputAt(Cursors::self()->mouse()->pos());
2427     }
2428 
2429     AbstractClient *client = Workspace::self()->activeClient();
2430     if (active_client && !client->isOnOutput(m_activeOutput)) {
2431         return client->output();
2432     }
2433 
2434     return m_activeOutput;
2435 }
2436 
setActiveOutput(AbstractOutput * output)2437 void Workspace::setActiveOutput(AbstractOutput *output)
2438 {
2439     m_activeOutput = output;
2440 }
2441 
setActiveOutput(const QPoint & pos)2442 void Workspace::setActiveOutput(const QPoint &pos)
2443 {
2444     setActiveOutput(kwinApp()->platform()->outputAt(pos));
2445 }
2446 
2447 /**
2448  * Client \a c is moved around to position \a pos. This gives the
2449  * workspace the opportunity to interveniate and to implement
2450  * snap-to-windows functionality.
2451  *
2452  * The parameter \a snapAdjust is a multiplier used to calculate the
2453  * effective snap zones. When 1.0, it means that the snap zones will be
2454  * used without change.
2455  */
adjustClientPosition(AbstractClient * c,QPoint pos,bool unrestricted,double snapAdjust)2456 QPoint Workspace::adjustClientPosition(AbstractClient* c, QPoint pos, bool unrestricted, double snapAdjust)
2457 {
2458     QSize borderSnapZone(options->borderSnapZone(), options->borderSnapZone());
2459     QRect maxRect;
2460     int guideMaximized = MaximizeRestore;
2461     if (c->maximizeMode() != MaximizeRestore) {
2462         maxRect = clientArea(MaximizeArea, c, pos + c->rect().center());
2463         QRect geo = c->frameGeometry();
2464         if (c->maximizeMode() & MaximizeHorizontal && (geo.x() == maxRect.left() || geo.right() == maxRect.right())) {
2465             guideMaximized |= MaximizeHorizontal;
2466             borderSnapZone.setWidth(qMax(borderSnapZone.width() + 2, maxRect.width() / 16));
2467         }
2468         if (c->maximizeMode() & MaximizeVertical && (geo.y() == maxRect.top() || geo.bottom() == maxRect.bottom())) {
2469             guideMaximized |= MaximizeVertical;
2470             borderSnapZone.setHeight(qMax(borderSnapZone.height() + 2, maxRect.height() / 16));
2471         }
2472     }
2473 
2474     if (options->windowSnapZone() || !borderSnapZone.isNull() || options->centerSnapZone()) {
2475 
2476         const bool sOWO = options->isSnapOnlyWhenOverlapping();
2477         const AbstractOutput *output = kwinApp()->platform()->outputAt(pos + c->rect().center());
2478         if (maxRect.isNull())
2479             maxRect = clientArea(MovementArea, c, output);
2480         const int xmin = maxRect.left();
2481         const int xmax = maxRect.right() + 1;             //desk size
2482         const int ymin = maxRect.top();
2483         const int ymax = maxRect.bottom() + 1;
2484 
2485         const int cx(pos.x());
2486         const int cy(pos.y());
2487         const int cw(c->width());
2488         const int ch(c->height());
2489         const int rx(cx + cw);
2490         const int ry(cy + ch);               //these don't change
2491 
2492         int nx(cx), ny(cy);                         //buffers
2493         int deltaX(xmax);
2494         int deltaY(ymax);   //minimum distance to other clients
2495 
2496         int lx, ly, lrx, lry; //coords and size for the comparison client, l
2497 
2498         // border snap
2499         const int snapX = borderSnapZone.width() * snapAdjust; //snap trigger
2500         const int snapY = borderSnapZone.height() * snapAdjust;
2501         if (snapX || snapY) {
2502             QRect geo = c->frameGeometry();
2503             QMargins frameMargins = c->frameMargins();
2504 
2505             // snap to titlebar / snap to window borders on inner screen edges
2506             AbstractClient::Position titlePos = c->titlebarPosition();
2507             if (frameMargins.left() && (titlePos == AbstractClient::PositionLeft || (c->maximizeMode() & MaximizeHorizontal) ||
2508                                         screens()->intersecting(geo.translated(maxRect.x() - (frameMargins.left() + geo.x()), 0)) > 1)) {
2509                 frameMargins.setLeft(0);
2510             }
2511             if (frameMargins.right() && (titlePos == AbstractClient::PositionRight || (c->maximizeMode() & MaximizeHorizontal) ||
2512                                          screens()->intersecting(geo.translated(maxRect.right() + frameMargins.right() - geo.right(), 0)) > 1)) {
2513                 frameMargins.setRight(0);
2514             }
2515             if (frameMargins.top() && (titlePos == AbstractClient::PositionTop || (c->maximizeMode() & MaximizeVertical) ||
2516                                        screens()->intersecting(geo.translated(0, maxRect.y() - (frameMargins.top() + geo.y()))) > 1)) {
2517                 frameMargins.setTop(0);
2518             }
2519             if (frameMargins.bottom() && (titlePos == AbstractClient::PositionBottom || (c->maximizeMode() & MaximizeVertical) ||
2520                                           screens()->intersecting(geo.translated(0, maxRect.bottom() + frameMargins.bottom() - geo.bottom())) > 1)) {
2521                 frameMargins.setBottom(0);
2522             }
2523             if ((sOWO ? (cx < xmin) : true) && (qAbs(xmin - cx) < snapX)) {
2524                 deltaX = xmin - cx;
2525                 nx = xmin - frameMargins.left();
2526             }
2527             if ((sOWO ? (rx > xmax) : true) && (qAbs(rx - xmax) < snapX) && (qAbs(xmax - rx) < deltaX)) {
2528                 deltaX = rx - xmax;
2529                 nx = xmax - cw + frameMargins.right();
2530             }
2531 
2532             if ((sOWO ? (cy < ymin) : true) && (qAbs(ymin - cy) < snapY)) {
2533                 deltaY = ymin - cy;
2534                 ny = ymin - frameMargins.top();
2535             }
2536             if ((sOWO ? (ry > ymax) : true) && (qAbs(ry - ymax) < snapY) && (qAbs(ymax - ry) < deltaY)) {
2537                 deltaY = ry - ymax;
2538                 ny = ymax - ch + frameMargins.bottom();
2539             }
2540         }
2541 
2542         // windows snap
2543         int snap = options->windowSnapZone() * snapAdjust;
2544         if (snap) {
2545             for (auto l = m_allClients.constBegin(); l != m_allClients.constEnd(); ++l) {
2546                 if ((*l) == c)
2547                     continue;
2548                 if ((*l)->isMinimized())
2549                     continue; // is minimized
2550                 if (!(*l)->isShown(false))
2551                     continue;
2552                 if (!(*l)->isOnCurrentDesktop())
2553                     continue; // wrong virtual desktop
2554                 if (!(*l)->isOnCurrentActivity())
2555                     continue; // wrong activity
2556                 if ((*l)->isDesktop() || (*l)->isSplash() || (*l)->isNotification() ||
2557                         (*l)->isCriticalNotification() || (*l)->isOnScreenDisplay())
2558                     continue;
2559 
2560                 lx = (*l)->x();
2561                 ly = (*l)->y();
2562                 lrx = lx + (*l)->width();
2563                 lry = ly + (*l)->height();
2564 
2565                 if (!(guideMaximized & MaximizeHorizontal) &&
2566                     (((cy <= lry) && (cy  >= ly)) || ((ry >= ly) && (ry  <= lry)) || ((cy <= ly) && (ry >= lry)))) {
2567                     if ((sOWO ? (cx < lrx) : true) && (qAbs(lrx - cx) < snap) && (qAbs(lrx - cx) < deltaX)) {
2568                         deltaX = qAbs(lrx - cx);
2569                         nx = lrx;
2570                     }
2571                     if ((sOWO ? (rx > lx) : true) && (qAbs(rx - lx) < snap) && (qAbs(rx - lx) < deltaX)) {
2572                         deltaX = qAbs(rx - lx);
2573                         nx = lx - cw;
2574                     }
2575                 }
2576 
2577                 if (!(guideMaximized & MaximizeVertical) &&
2578                     (((cx <= lrx) && (cx  >= lx)) || ((rx >= lx) && (rx  <= lrx)) || ((cx <= lx) && (rx >= lrx)))) {
2579                     if ((sOWO ? (cy < lry) : true) && (qAbs(lry - cy) < snap) && (qAbs(lry - cy) < deltaY)) {
2580                         deltaY = qAbs(lry - cy);
2581                         ny = lry;
2582                     }
2583                     //if ( (qAbs( ry-ly ) < snap) && (qAbs( ry - ly ) < deltaY ))
2584                     if ((sOWO ? (ry > ly) : true) && (qAbs(ry - ly) < snap) && (qAbs(ry - ly) < deltaY)) {
2585                         deltaY = qAbs(ry - ly);
2586                         ny = ly - ch;
2587                     }
2588                 }
2589 
2590                 // Corner snapping
2591                 if (!(guideMaximized & MaximizeVertical) && (nx == lrx || nx + cw == lx)) {
2592                     if ((sOWO ? (ry > lry) : true) && (qAbs(lry - ry) < snap) && (qAbs(lry - ry) < deltaY)) {
2593                         deltaY = qAbs(lry - ry);
2594                         ny = lry - ch;
2595                     }
2596                     if ((sOWO ? (cy < ly) : true) && (qAbs(cy - ly) < snap) && (qAbs(cy - ly) < deltaY)) {
2597                         deltaY = qAbs(cy - ly);
2598                         ny = ly;
2599                     }
2600                 }
2601                 if (!(guideMaximized & MaximizeHorizontal) && (ny == lry || ny + ch == ly)) {
2602                     if ((sOWO ? (rx > lrx) : true) && (qAbs(lrx - rx) < snap) && (qAbs(lrx - rx) < deltaX)) {
2603                         deltaX = qAbs(lrx - rx);
2604                         nx = lrx - cw;
2605                     }
2606                     if ((sOWO ? (cx < lx) : true) && (qAbs(cx - lx) < snap) && (qAbs(cx - lx) < deltaX)) {
2607                         deltaX = qAbs(cx - lx);
2608                         nx = lx;
2609                     }
2610                 }
2611             }
2612         }
2613 
2614         // center snap
2615         snap = options->centerSnapZone() * snapAdjust; //snap trigger
2616         if (snap) {
2617             int diffX = qAbs((xmin + xmax) / 2 - (cx + cw / 2));
2618             int diffY = qAbs((ymin + ymax) / 2 - (cy + ch / 2));
2619             if (diffX < snap && diffY < snap && diffX < deltaX && diffY < deltaY) {
2620                 // Snap to center of screen
2621                 nx = (xmin + xmax) / 2 - cw / 2;
2622                 ny = (ymin + ymax) / 2 - ch / 2;
2623             } else if (options->borderSnapZone()) {
2624                 // Enhance border snap
2625                 if ((nx == xmin || nx == xmax - cw) && diffY < snap && diffY < deltaY) {
2626                     // Snap to vertical center on screen edge
2627                     ny = (ymin + ymax) / 2 - ch / 2;
2628                 } else if (((unrestricted ? ny == ymin : ny <= ymin) || ny == ymax - ch) &&
2629                           diffX < snap && diffX < deltaX) {
2630                     // Snap to horizontal center on screen edge
2631                     nx = (xmin + xmax) / 2 - cw / 2;
2632                 }
2633             }
2634         }
2635 
2636         pos = QPoint(nx, ny);
2637     }
2638     return pos;
2639 }
2640 
adjustClientSize(AbstractClient * c,QRect moveResizeGeom,int mode)2641 QRect Workspace::adjustClientSize(AbstractClient* c, QRect moveResizeGeom, int mode)
2642 {
2643     //adapted from adjustClientPosition on 29May2004
2644     //this function is called when resizing a window and will modify
2645     //the new dimensions to snap to other windows/borders if appropriate
2646     if (options->windowSnapZone() || options->borderSnapZone()) {  // || options->centerSnapZone )
2647         const bool sOWO = options->isSnapOnlyWhenOverlapping();
2648 
2649         const QRect maxRect = clientArea(MovementArea, c, c->rect().center());
2650         const int xmin = maxRect.left();
2651         const int xmax = maxRect.right();               //desk size
2652         const int ymin = maxRect.top();
2653         const int ymax = maxRect.bottom();
2654 
2655         const int cx(moveResizeGeom.left());
2656         const int cy(moveResizeGeom.top());
2657         const int rx(moveResizeGeom.right());
2658         const int ry(moveResizeGeom.bottom());
2659 
2660         int newcx(cx), newcy(cy);                         //buffers
2661         int newrx(rx), newry(ry);
2662         int deltaX(xmax);
2663         int deltaY(ymax);   //minimum distance to other clients
2664 
2665         int lx, ly, lrx, lry; //coords and size for the comparison client, l
2666 
2667         // border snap
2668         int snap = options->borderSnapZone(); //snap trigger
2669         if (snap) {
2670             deltaX = int(snap);
2671             deltaY = int(snap);
2672 
2673 #define SNAP_BORDER_TOP \
2674     if ((sOWO?(newcy<ymin):true) && (qAbs(ymin-newcy)<deltaY)) \
2675     { \
2676         deltaY = qAbs(ymin-newcy); \
2677         newcy = ymin; \
2678     }
2679 
2680 #define SNAP_BORDER_BOTTOM \
2681     if ((sOWO?(newry>ymax):true) && (qAbs(ymax-newry)<deltaY)) \
2682     { \
2683         deltaY = qAbs(ymax-newcy); \
2684         newry = ymax; \
2685     }
2686 
2687 #define SNAP_BORDER_LEFT \
2688     if ((sOWO?(newcx<xmin):true) && (qAbs(xmin-newcx)<deltaX)) \
2689     { \
2690         deltaX = qAbs(xmin-newcx); \
2691         newcx = xmin; \
2692     }
2693 
2694 #define SNAP_BORDER_RIGHT \
2695     if ((sOWO?(newrx>xmax):true) && (qAbs(xmax-newrx)<deltaX)) \
2696     { \
2697         deltaX = qAbs(xmax-newrx); \
2698         newrx = xmax; \
2699     }
2700             switch(mode) {
2701             case AbstractClient::PositionBottomRight:
2702                 SNAP_BORDER_BOTTOM
2703                 SNAP_BORDER_RIGHT
2704                 break;
2705             case AbstractClient::PositionRight:
2706                 SNAP_BORDER_RIGHT
2707                 break;
2708             case AbstractClient::PositionBottom:
2709                 SNAP_BORDER_BOTTOM
2710                 break;
2711             case AbstractClient::PositionTopLeft:
2712                 SNAP_BORDER_TOP
2713                 SNAP_BORDER_LEFT
2714                 break;
2715             case AbstractClient::PositionLeft:
2716                 SNAP_BORDER_LEFT
2717                 break;
2718             case AbstractClient::PositionTop:
2719                 SNAP_BORDER_TOP
2720                 break;
2721             case AbstractClient::PositionTopRight:
2722                 SNAP_BORDER_TOP
2723                 SNAP_BORDER_RIGHT
2724                 break;
2725             case AbstractClient::PositionBottomLeft:
2726                 SNAP_BORDER_BOTTOM
2727                 SNAP_BORDER_LEFT
2728                 break;
2729             default:
2730                 abort();
2731                 break;
2732             }
2733 
2734 
2735         }
2736 
2737         // windows snap
2738         snap = options->windowSnapZone();
2739         if (snap) {
2740             deltaX = int(snap);
2741             deltaY = int(snap);
2742             for (auto l = m_allClients.constBegin(); l != m_allClients.constEnd(); ++l) {
2743                 if ((*l)->isOnCurrentDesktop() &&
2744                         !(*l)->isMinimized()
2745                         && (*l) != c) {
2746                     lx = (*l)->x() - 1;
2747                     ly = (*l)->y() - 1;
2748                     lrx = (*l)->x() + (*l)->width();
2749                     lry = (*l)->y() + (*l)->height();
2750 
2751 #define WITHIN_HEIGHT ((( newcy <= lry ) && ( newcy  >= ly  ))  || \
2752                        (( newry >= ly  ) && ( newry  <= lry ))  || \
2753                        (( newcy <= ly  ) && ( newry >= lry  )) )
2754 
2755 #define WITHIN_WIDTH  ( (( cx <= lrx ) && ( cx  >= lx  ))  || \
2756                         (( rx >= lx  ) && ( rx  <= lrx ))  || \
2757                         (( cx <= lx  ) && ( rx >= lrx  )) )
2758 
2759 #define SNAP_WINDOW_TOP  if ( (sOWO?(newcy<lry):true) \
2760                               && WITHIN_WIDTH  \
2761                               && (qAbs( lry - newcy ) < deltaY) ) {  \
2762     deltaY = qAbs( lry - newcy ); \
2763     newcy=lry; \
2764 }
2765 
2766 #define SNAP_WINDOW_BOTTOM  if ( (sOWO?(newry>ly):true)  \
2767                                  && WITHIN_WIDTH  \
2768                                  && (qAbs( ly - newry ) < deltaY) ) {  \
2769     deltaY = qAbs( ly - newry );  \
2770     newry=ly;  \
2771 }
2772 
2773 #define SNAP_WINDOW_LEFT  if ( (sOWO?(newcx<lrx):true)  \
2774                                && WITHIN_HEIGHT  \
2775                                && (qAbs( lrx - newcx ) < deltaX)) {  \
2776     deltaX = qAbs( lrx - newcx );  \
2777     newcx=lrx;  \
2778 }
2779 
2780 #define SNAP_WINDOW_RIGHT  if ( (sOWO?(newrx>lx):true)  \
2781                                 && WITHIN_HEIGHT  \
2782                                 && (qAbs( lx - newrx ) < deltaX))  \
2783 {  \
2784     deltaX = qAbs( lx - newrx );  \
2785     newrx=lx;  \
2786 }
2787 
2788 #define SNAP_WINDOW_C_TOP  if ( (sOWO?(newcy<ly):true)  \
2789                                 && (newcx == lrx || newrx == lx)  \
2790                                 && qAbs(ly-newcy) < deltaY ) {  \
2791     deltaY = qAbs( ly - newcy + 1 ); \
2792     newcy = ly + 1; \
2793 }
2794 
2795 #define SNAP_WINDOW_C_BOTTOM  if ( (sOWO?(newry>lry):true)  \
2796                                    && (newcx == lrx || newrx == lx)  \
2797                                    && qAbs(lry-newry) < deltaY ) {  \
2798     deltaY = qAbs( lry - newry - 1 ); \
2799     newry = lry - 1; \
2800 }
2801 
2802 #define SNAP_WINDOW_C_LEFT  if ( (sOWO?(newcx<lx):true)  \
2803                                  && (newcy == lry || newry == ly)  \
2804                                  && qAbs(lx-newcx) < deltaX ) {  \
2805     deltaX = qAbs( lx - newcx + 1 ); \
2806     newcx = lx + 1; \
2807 }
2808 
2809 #define SNAP_WINDOW_C_RIGHT  if ( (sOWO?(newrx>lrx):true)  \
2810                                   && (newcy == lry || newry == ly)  \
2811                                   && qAbs(lrx-newrx) < deltaX ) {  \
2812     deltaX = qAbs( lrx - newrx - 1 ); \
2813     newrx = lrx - 1; \
2814 }
2815 
2816                     switch(mode) {
2817                     case AbstractClient::PositionBottomRight:
2818                         SNAP_WINDOW_BOTTOM
2819                         SNAP_WINDOW_RIGHT
2820                         SNAP_WINDOW_C_BOTTOM
2821                         SNAP_WINDOW_C_RIGHT
2822                         break;
2823                     case AbstractClient::PositionRight:
2824                         SNAP_WINDOW_RIGHT
2825                         SNAP_WINDOW_C_RIGHT
2826                         break;
2827                     case AbstractClient::PositionBottom:
2828                         SNAP_WINDOW_BOTTOM
2829                         SNAP_WINDOW_C_BOTTOM
2830                         break;
2831                     case AbstractClient::PositionTopLeft:
2832                         SNAP_WINDOW_TOP
2833                         SNAP_WINDOW_LEFT
2834                         SNAP_WINDOW_C_TOP
2835                         SNAP_WINDOW_C_LEFT
2836                         break;
2837                     case AbstractClient::PositionLeft:
2838                         SNAP_WINDOW_LEFT
2839                         SNAP_WINDOW_C_LEFT
2840                         break;
2841                     case AbstractClient::PositionTop:
2842                         SNAP_WINDOW_TOP
2843                         SNAP_WINDOW_C_TOP
2844                         break;
2845                     case AbstractClient::PositionTopRight:
2846                         SNAP_WINDOW_TOP
2847                         SNAP_WINDOW_RIGHT
2848                         SNAP_WINDOW_C_TOP
2849                         SNAP_WINDOW_C_RIGHT
2850                         break;
2851                     case AbstractClient::PositionBottomLeft:
2852                         SNAP_WINDOW_BOTTOM
2853                         SNAP_WINDOW_LEFT
2854                         SNAP_WINDOW_C_BOTTOM
2855                         SNAP_WINDOW_C_LEFT
2856                         break;
2857                     default:
2858                         abort();
2859                         break;
2860                     }
2861                 }
2862             }
2863         }
2864 
2865         // center snap
2866         //snap = options->centerSnapZone;
2867         //if (snap)
2868         //    {
2869         //    // Don't resize snap to center as it interferes too much
2870         //    // There are two ways of implementing this if wanted:
2871         //    // 1) Snap only to the same points that the move snap does, and
2872         //    // 2) Snap to the horizontal and vertical center lines of the screen
2873         //    }
2874 
2875         moveResizeGeom = QRect(QPoint(newcx, newcy), QPoint(newrx, newry));
2876     }
2877     return moveResizeGeom;
2878 }
2879 
2880 /**
2881  * Marks the client as being moved or resized by the user.
2882  */
setMoveResizeClient(AbstractClient * c)2883 void Workspace::setMoveResizeClient(AbstractClient *c)
2884 {
2885     Q_ASSERT(!c || !movingClient); // Catch attempts to move a second
2886     // window while still moving the first one.
2887     movingClient = c;
2888     if (movingClient)
2889         ++block_focus;
2890     else
2891         --block_focus;
2892 }
2893 
2894 // When kwin crashes, windows will not be gravitated back to their original position
2895 // and will remain offset by the size of the decoration. So when restarting, fix this
2896 // (the property with the size of the frame remains on the window after the crash).
fixPositionAfterCrash(xcb_window_t w,const xcb_get_geometry_reply_t * geometry)2897 void Workspace::fixPositionAfterCrash(xcb_window_t w, const xcb_get_geometry_reply_t *geometry)
2898 {
2899     NETWinInfo i(connection(), w, rootWindow(), NET::WMFrameExtents, NET::Properties2());
2900     NETStrut frame = i.frameExtents();
2901 
2902     if (frame.left != 0 || frame.top != 0) {
2903         // left and top needed due to narrowing conversations restrictions in C++11
2904         const uint32_t left = frame.left;
2905         const uint32_t top = frame.top;
2906         const uint32_t values[] = { geometry->x - left, geometry->y - top };
2907         xcb_configure_window(connection(), w, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values);
2908     }
2909 }
2910 
2911 } // namespace
2912