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