KWin
Loading...
Searching...
No Matches
tabbox.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6 SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
7 SPDX-FileCopyrightText: 2009 Martin Gräßlin <mgraesslin@kde.org>
8
9 SPDX-License-Identifier: GPL-2.0-or-later
10*/
11
12//#define QT_CLEAN_NAMESPACE
13// own
14#include "tabbox.h"
15// tabbox
16#include "tabbox/clientmodel.h"
18#include "tabbox/tabboxconfig.h"
19#include "tabbox/x11_filter.h"
20// kwin
21#if KWIN_BUILD_ACTIVITIES
22#include "activities.h"
23#endif
24#include "compositor.h"
26#include "focuschain.h"
27#include "input.h"
28#include "keyboard_input.h"
29#include "pointer_input.h"
30#include "screenedge.h"
31#include "utils/xcbutils.h"
32#include "virtualdesktops.h"
33#include "workspace.h"
34#include "x11window.h"
35// Qt
36#include <QAction>
37#include <QKeyEvent>
38// KDE
39#include <KConfig>
40#include <KConfigGroup>
41#include <KGlobalAccel>
42#include <KLazyLocalizedString>
43#include <KLocalizedString>
44#include <kkeyserver.h>
45// X11
46#include <X11/keysym.h>
47#include <X11/keysymdef.h>
48// xcb
49#include <xcb/xcb_keysyms.h>
50
51// specify externals before namespace
52
53namespace KWin
54{
55
56namespace TabBox
57{
58
64
68
70{
71 return workspace()->outputs().indexOf(workspace()->activeOutput());
72}
73
75{
76 if (!client->isOnAllDesktops()) {
77 return client->desktops().last()->name();
78 }
79 return VirtualDesktopManager::self()->currentDesktop()->name();
80}
81
86
91
96
101
102bool TabBoxHandlerImpl::checkDesktop(Window *client) const
103{
104 switch (config().clientDesktopMode()) {
106 return true;
108 return !client->isOnCurrentDesktop();
109 default: // TabBoxConfig::OnlyCurrentDesktopClients
110 return client->isOnCurrentDesktop();
111 }
112}
113
114bool TabBoxHandlerImpl::checkActivity(Window *client) const
115{
116 switch (config().clientActivitiesMode()) {
118 return true;
120 return !client->isOnCurrentActivity();
121 default: // TabBoxConfig::OnlyCurrentActivityClients
122 return client->isOnCurrentActivity();
123 }
124}
125
126bool TabBoxHandlerImpl::checkApplications(Window *client) const
127{
128 const auto list = clientList();
129
130 switch (config().clientApplicationsMode()) {
132 // check if the list already contains an entry of this application
133 for (const Window *other : list) {
135 return false;
136 }
137 }
138 return true;
140 const Window *active = tabBox->activeClient();
142 }
143 default: // TabBoxConfig::AllWindowsAllApplications
144 return true;
145 }
146}
147
148bool TabBoxHandlerImpl::checkMinimized(Window *client) const
149{
150 switch (config().clientMinimizedMode()) {
152 return !client->isMinimized();
154 return client->isMinimized();
155 default: // TabBoxConfig::IgnoreMinimizedStatus
156 return true;
157 }
158}
159
160bool TabBoxHandlerImpl::checkMultiScreen(Window *client) const
161{
162 switch (config().clientMultiScreenMode()) {
164 return true;
166 return client->output() != workspace()->activeOutput();
167 default: // TabBoxConfig::OnlyCurrentScreenClients
168 return client->output() == workspace()->activeOutput();
169 }
170}
171
173{
174 if (!client || client->isDeleted()) {
175 return nullptr;
176 }
177 Window *ret = nullptr;
178
179 bool addClient = checkDesktop(client)
180 && checkActivity(client)
181 && checkApplications(client)
182 && checkMinimized(client)
183 && checkMultiScreen(client);
184 addClient = addClient && client->wantsTabFocus() && !client->skipSwitcher();
185 if (addClient) {
186 // don't add windows that have modal dialogs
187 Window *modal = client->findModal();
188 if (modal == nullptr || modal == client) {
189 ret = client;
190 } else {
191 if (clientList().contains(modal)) {
192 ret = modal;
193 } else {
194 // nothing
195 }
196 }
197 }
198 return ret;
199}
200
201QList<Window *> TabBoxHandlerImpl::stackingOrder() const
202{
203 const QList<Window *> stacking = Workspace::self()->stackingOrder();
204 QList<Window *> ret;
205 for (Window *window : stacking) {
206 if (window->isClient()) {
207 ret.append(window);
208 }
209 }
210 return ret;
211}
212
217
222
224{
225 Workspace::self()->restack(c, under, true);
226}
227
228void TabBoxHandlerImpl::elevateClient(Window *c, QWindow *tabbox, bool b) const
229{
230 c->elevate(b);
231 if (Window *w = Workspace::self()->findInternal(tabbox)) {
232 w->elevate(b);
233 }
234}
235
237{
238 c->cancelShadeHoverTimer(); // stop core shading action
239 if (!b && c->shadeMode() == ShadeNormal) {
241 } else if (b && c->shadeMode() == ShadeHover) {
243 }
244}
245
247{
249 for (Window *window : stackingOrder) {
250 if (window->isClient() && window->isDesktop() && window->isOnCurrentDesktop() && window->output() == workspace()->activeOutput()) {
251 return window;
252 }
253 }
254 return nullptr;
255}
256
258{
259 m_tabBox->accept();
260}
261
262void TabBoxHandlerImpl::highlightWindows(Window *window, QWindow *controller)
263{
264 if (!effects) {
265 return;
266 }
267 QList<EffectWindow *> windows;
268 if (window) {
269 windows << window->effectWindow();
270 }
271 if (Window *t = workspace()->findInternal(controller)) {
272 windows << t->effectWindow();
273 }
274 effects->highlightWindows(windows);
275}
276
278{
279 return m_tabBox->noModifierGrab();
280}
281
282/*********************************************************
283 * TabBox
284 *********************************************************/
285
287 : m_displayRefcount(0)
288 , m_tabGrab(false)
289 , m_noModifierGrab(false)
290 , m_forcedGlobalMouseGrab(false)
291 , m_ready(false)
292{
293 m_isShown = false;
294 m_defaultConfig = TabBoxConfig();
303
304 m_alternativeConfig = TabBoxConfig();
313
314 m_defaultCurrentApplicationConfig = m_defaultConfig;
316
317 m_alternativeCurrentApplicationConfig = m_alternativeConfig;
318 m_alternativeCurrentApplicationConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsCurrentApplication);
319
320 m_tabBox = new TabBoxHandlerImpl(this);
321 QTimer::singleShot(0, this, &TabBox::handlerReady);
322
323 m_tabBoxMode = TabBoxWindowsMode; // init variables
324 connect(&m_delayedShowTimer, &QTimer::timeout, this, &TabBox::show);
325 connect(Workspace::self(), &Workspace::configChanged, this, &TabBox::reconfigure);
326}
327
328TabBox::~TabBox() = default;
329
331{
332 m_tabBox->setConfig(m_defaultConfig);
333 reconfigure();
334 m_ready = true;
335}
336
337template<typename Slot>
338void TabBox::key(const KLazyLocalizedString &actionName, Slot slot, const QKeySequence &shortcut)
339{
340 QAction *a = new QAction(this);
341 a->setProperty("componentName", QStringLiteral("kwin"));
342 a->setObjectName(QString::fromUtf8(actionName.untranslatedText()));
343 a->setText(actionName.toString());
344 KGlobalAccel::self()->setGlobalShortcut(a, QList<QKeySequence>() << shortcut);
345 connect(a, &QAction::triggered, this, slot);
346 auto cuts = KGlobalAccel::self()->shortcut(a);
347 globalShortcutChanged(a, cuts.isEmpty() ? QKeySequence() : cuts.first());
348}
349
350static constexpr const auto s_windows = kli18n("Walk Through Windows");
351static constexpr const auto s_windowsRev = kli18n("Walk Through Windows (Reverse)");
352static constexpr const auto s_windowsAlt = kli18n("Walk Through Windows Alternative");
353static constexpr const auto s_windowsAltRev = kli18n("Walk Through Windows Alternative (Reverse)");
354static constexpr const auto s_app = kli18n("Walk Through Windows of Current Application");
355static constexpr const auto s_appRev = kli18n("Walk Through Windows of Current Application (Reverse)");
356static constexpr const auto s_appAlt = kli18n("Walk Through Windows of Current Application Alternative");
357static constexpr const auto s_appAltRev = kli18n("Walk Through Windows of Current Application Alternative (Reverse)");
358
360{
361 key(s_windows, &TabBox::slotWalkThroughWindows, Qt::ALT | Qt::Key_Tab);
362 key(s_windowsRev, &TabBox::slotWalkBackThroughWindows, Qt::ALT | Qt::SHIFT | Qt::Key_Tab);
363 key(s_app, &TabBox::slotWalkThroughCurrentAppWindows, Qt::ALT | Qt::Key_QuoteLeft);
364 key(s_appRev, &TabBox::slotWalkBackThroughCurrentAppWindows, Qt::ALT | Qt::Key_AsciiTilde);
369
370 connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutChanged, this, &TabBox::globalShortcutChanged);
371}
372
373void TabBox::globalShortcutChanged(QAction *action, const QKeySequence &seq)
374{
375 if (qstrcmp(qPrintable(action->objectName()), s_windows.untranslatedText()) == 0) {
376 m_cutWalkThroughWindows = seq;
377 } else if (qstrcmp(qPrintable(action->objectName()), s_windowsRev.untranslatedText()) == 0) {
378 m_cutWalkThroughWindowsReverse = seq;
379 } else if (qstrcmp(qPrintable(action->objectName()), s_app.untranslatedText()) == 0) {
380 m_cutWalkThroughCurrentAppWindows = seq;
381 } else if (qstrcmp(qPrintable(action->objectName()), s_appRev.untranslatedText()) == 0) {
382 m_cutWalkThroughCurrentAppWindowsReverse = seq;
383 } else if (qstrcmp(qPrintable(action->objectName()), s_windowsAlt.untranslatedText()) == 0) {
384 m_cutWalkThroughWindowsAlternative = seq;
385 } else if (qstrcmp(qPrintable(action->objectName()), s_windowsAltRev.untranslatedText()) == 0) {
386 m_cutWalkThroughWindowsAlternativeReverse = seq;
387 } else if (qstrcmp(qPrintable(action->objectName()), s_appAlt.untranslatedText()) == 0) {
388 m_cutWalkThroughCurrentAppWindowsAlternative = seq;
389 } else if (qstrcmp(qPrintable(action->objectName()), s_appAltRev.untranslatedText()) == 0) {
390 m_cutWalkThroughCurrentAppWindowsAlternativeReverse = seq;
391 }
392}
393
395{
396 m_tabBoxMode = mode;
397 switch (mode) {
399 m_tabBox->setConfig(m_defaultConfig);
400 break;
402 m_tabBox->setConfig(m_alternativeConfig);
403 break;
405 m_tabBox->setConfig(m_defaultCurrentApplicationConfig);
406 break;
408 m_tabBox->setConfig(m_alternativeCurrentApplicationConfig);
409 break;
410 }
411}
412
413void TabBox::reset(bool partial_reset)
414{
415 m_tabBox->createModel(partial_reset);
416 if (!partial_reset) {
417 if (Workspace::self()->activeWindow()) {
418 setCurrentClient(Workspace::self()->activeWindow());
419 }
420 // it's possible that the active client is not part of the model
421 // in that case the index is invalid
422 if (!m_tabBox->currentIndex().isValid()) {
423 setCurrentIndex(m_tabBox->first());
424 }
425 } else {
426 if (!m_tabBox->currentIndex().isValid() || !m_tabBox->client(m_tabBox->currentIndex())) {
427 setCurrentIndex(m_tabBox->first());
428 }
429 }
430
431 Q_EMIT tabBoxUpdated();
432}
433
434void TabBox::nextPrev(bool next)
435{
436 setCurrentIndex(m_tabBox->nextPrev(next), false);
437 Q_EMIT tabBoxUpdated();
438}
439
441{
442 if (Window *client = m_tabBox->client(m_tabBox->currentIndex())) {
443 if (!Workspace::self()->hasWindow(client)) {
444 return nullptr;
445 }
446 return client;
447 } else {
448 return nullptr;
449 }
450}
451
453{
454 return m_tabBox->clientList();
455}
456
458{
459 setCurrentIndex(m_tabBox->index(newClient));
460}
461
462void TabBox::setCurrentIndex(QModelIndex index, bool notifyEffects)
463{
464 if (!index.isValid()) {
465 return;
466 }
467 m_tabBox->setCurrentIndex(index);
468 if (notifyEffects) {
469 Q_EMIT tabBoxUpdated();
470 }
471}
472
474{
475 Q_EMIT tabBoxAdded(m_tabBoxMode);
476 if (isDisplayed()) {
477 m_isShown = false;
478 return;
479 }
481 reference();
482 m_isShown = true;
483 m_tabBox->show();
484}
485
486void TabBox::hide(bool abort)
487{
488 m_delayedShowTimer.stop();
489 if (m_isShown) {
490 m_isShown = false;
491 unreference();
492 }
493 Q_EMIT tabBoxClosed();
494 if (isDisplayed()) {
495 qCDebug(KWIN_TABBOX) << "Tab box was not properly closed by an effect";
496 }
497 m_tabBox->hide(abort);
498}
499
500void TabBox::reconfigure()
501{
502 KSharedConfigPtr c = kwinApp()->config();
503 KConfigGroup config = c->group(QStringLiteral("TabBox"));
504
505 loadConfig(c->group(QStringLiteral("TabBox")), m_defaultConfig);
506 loadConfig(c->group(QStringLiteral("TabBoxAlternative")), m_alternativeConfig);
507
508 m_defaultCurrentApplicationConfig = m_defaultConfig;
510 m_alternativeCurrentApplicationConfig = m_alternativeConfig;
511 m_alternativeCurrentApplicationConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsCurrentApplication);
512
513 m_tabBox->setConfig(m_defaultConfig);
514
515 m_delayShowTime = config.readEntry<int>("DelayTime", 90);
516
517 QList<ElectricBorder> *borders = &m_borderActivate;
518 QString borderConfig = QStringLiteral("BorderActivate");
519 for (int i = 0; i < 2; ++i) {
520 for (ElectricBorder border : std::as_const(*borders)) {
521 workspace()->screenEdges()->unreserve(border, this);
522 }
523 borders->clear();
524 QStringList list = config.readEntry(borderConfig, QStringList());
525 for (const QString &s : std::as_const(list)) {
526 bool ok;
527 const int i = s.toInt(&ok);
528 if (!ok) {
529 continue;
530 }
531 borders->append(ElectricBorder(i));
532 workspace()->screenEdges()->reserve(ElectricBorder(i), this, "toggle");
533 }
534 borders = &m_borderAlternativeActivate;
535 borderConfig = QStringLiteral("BorderAlternativeActivate");
536 }
537
538 auto touchConfig = [this, config](const QString &key, QHash<ElectricBorder, QAction *> &actions, TabBoxMode mode, const QStringList &defaults = QStringList{}) {
539 // fist erase old config
540 for (auto it = actions.begin(); it != actions.end();) {
541 delete it.value();
542 it = actions.erase(it);
543 }
544 // now new config
545 const QStringList list = config.readEntry(key, defaults);
546 for (const auto &s : list) {
547 bool ok;
548 const int i = s.toInt(&ok);
549 if (!ok) {
550 continue;
551 }
552 QAction *a = new QAction(this);
553 connect(a, &QAction::triggered, this, std::bind(&TabBox::toggleMode, this, mode));
555 actions.insert(ElectricBorder(i), a);
556 }
557 };
558 touchConfig(QStringLiteral("TouchBorderActivate"), m_touchActivate, TabBoxWindowsMode);
559 touchConfig(QStringLiteral("TouchBorderAlternativeActivate"), m_touchAlternativeActivate, TabBoxWindowsAlternativeMode);
560}
561
562void TabBox::loadConfig(const KConfigGroup &config, TabBoxConfig &tabBoxConfig)
563{
564 tabBoxConfig.setClientDesktopMode(TabBoxConfig::ClientDesktopMode(
565 config.readEntry<int>("DesktopMode", TabBoxConfig::defaultDesktopMode())));
566 tabBoxConfig.setClientActivitiesMode(TabBoxConfig::ClientActivitiesMode(
567 config.readEntry<int>("ActivitiesMode", TabBoxConfig::defaultActivitiesMode())));
568 tabBoxConfig.setClientApplicationsMode(TabBoxConfig::ClientApplicationsMode(
569 config.readEntry<int>("ApplicationsMode", TabBoxConfig::defaultApplicationsMode())));
570 tabBoxConfig.setOrderMinimizedMode(TabBoxConfig::OrderMinimizedMode(
571 config.readEntry<int>("OrderMinimizedMode", TabBoxConfig::defaultOrderMinimizedMode())));
572 tabBoxConfig.setClientMinimizedMode(TabBoxConfig::ClientMinimizedMode(
573 config.readEntry<int>("MinimizedMode", TabBoxConfig::defaultMinimizedMode())));
574 tabBoxConfig.setShowDesktopMode(TabBoxConfig::ShowDesktopMode(
575 config.readEntry<int>("ShowDesktopMode", TabBoxConfig::defaultShowDesktopMode())));
576 tabBoxConfig.setClientMultiScreenMode(TabBoxConfig::ClientMultiScreenMode(
577 config.readEntry<int>("MultiScreenMode", TabBoxConfig::defaultMultiScreenMode())));
578 tabBoxConfig.setClientSwitchingMode(TabBoxConfig::ClientSwitchingMode(
579 config.readEntry<int>("SwitchingMode", TabBoxConfig::defaultSwitchingMode())));
580
581 tabBoxConfig.setShowTabBox(config.readEntry<bool>("ShowTabBox",
583 tabBoxConfig.setHighlightWindows(config.readEntry<bool>("HighlightWindows",
585
586 tabBoxConfig.setLayoutName(config.readEntry<QString>("LayoutName", TabBoxConfig::defaultLayoutName()));
587}
588
590{
591 if (isDisplayed() || m_delayedShowTimer.isActive()) {
592 // already called show - no need to call it twice
593 return;
594 }
595
596 if (!m_delayShowTime) {
597 show();
598 return;
599 }
600
601 m_delayedShowTimer.setSingleShot(true);
602 m_delayedShowTimer.start(m_delayShowTime);
603}
604
605bool TabBox::handleMouseEvent(QMouseEvent *event)
606{
607 if (!m_isShown && isDisplayed()) {
608 // tabbox has been replaced, check effects
609 if (effects && effects->checkInputWindowEvent(event)) {
610 return true;
611 }
612 }
613 switch (event->type()) {
614 case QEvent::MouseMove:
615 if (!m_tabBox->containsPos(event->globalPos())) {
616 // filter out all events which are not on the TabBox window.
617 // We don't want windows to react on the mouse events
618 return true;
619 }
620 return false;
621 case QEvent::MouseButtonPress:
622 if ((!m_isShown && isDisplayed()) || !m_tabBox->containsPos(event->globalPos())) {
623 close(); // click outside closes tab
624 return true;
625 }
626 // fall through
627 case QEvent::MouseButtonRelease:
628 default:
629 // we do not filter it out, the intenal filter takes care
630 return false;
631 }
632 return false;
633}
634
635bool TabBox::handleWheelEvent(QWheelEvent *event)
636{
637 if (!m_isShown && isDisplayed()) {
638 // tabbox has been replaced, check effects
639 if (effects && effects->checkInputWindowEvent(event)) {
640 return true;
641 }
642 }
643 if (event->angleDelta().y() == 0) {
644 return false;
645 }
646 const QModelIndex index = m_tabBox->nextPrev(event->angleDelta().y() > 0);
647 if (index.isValid()) {
648 setCurrentIndex(index);
649 }
650 return true;
651}
652
653void TabBox::grabbedKeyEvent(QKeyEvent *event)
654{
655 Q_EMIT tabBoxKeyEvent(event);
656 if (!m_isShown && isDisplayed()) {
657 // tabbox has been replaced, check effects
658 return;
659 }
660 if (m_noModifierGrab) {
661 if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return || event->key() == Qt::Key_Space) {
662 accept();
663 return;
664 }
665 }
666 m_tabBox->grabbedKeyEvent(event);
667}
668
670{
672 {
673 xcb_key_symbols_free(symbols);
674 }
675};
676
680static bool areKeySymXsDepressed(const uint keySyms[], int nKeySyms)
681{
682 Xcb::QueryKeymap keys;
683
684 std::unique_ptr<xcb_key_symbols_t, KeySymbolsDeleter> symbols(xcb_key_symbols_alloc(connection()));
685 if (!symbols || !keys) {
686 return false;
687 }
688 const auto keymap = keys->keys;
689
690 bool depressed = false;
691 for (int iKeySym = 0; iKeySym < nKeySyms; iKeySym++) {
692 uint keySymX = keySyms[iKeySym];
693 xcb_keycode_t *keyCodes = xcb_key_symbols_get_keycode(symbols.get(), keySymX);
694 if (!keyCodes) {
695 continue;
696 }
697
698 int j = 0;
699 while (keyCodes[j] != XCB_NO_SYMBOL) {
700 const xcb_keycode_t keyCodeX = keyCodes[j++];
701 int i = keyCodeX / 8;
702 char mask = 1 << (keyCodeX - (i * 8));
703
704 if (i < 0 || i >= 32) {
705 continue;
706 }
707
708 qCDebug(KWIN_TABBOX) << iKeySym << ": keySymX=0x" << QString::number(keySymX, 16)
709 << " i=" << i << " mask=0x" << QString::number(mask, 16)
710 << " keymap[i]=0x" << QString::number(keymap[i], 16);
711
712 if (keymap[i] & mask) {
713 depressed = true;
714 break;
715 }
716 }
717
718 free(keyCodes);
719 }
720
721 return depressed;
722}
723
724static bool areModKeysDepressedX11(const QKeySequence &seq)
725{
726 uint rgKeySyms[10];
727 int nKeySyms = 0;
728 int mod = seq[seq.count() - 1] & Qt::KeyboardModifierMask;
729
730 if (mod & Qt::SHIFT) {
731 rgKeySyms[nKeySyms++] = XK_Shift_L;
732 rgKeySyms[nKeySyms++] = XK_Shift_R;
733 }
734 if (mod & Qt::CTRL) {
735 rgKeySyms[nKeySyms++] = XK_Control_L;
736 rgKeySyms[nKeySyms++] = XK_Control_R;
737 }
738 if (mod & Qt::ALT) {
739 rgKeySyms[nKeySyms++] = XK_Alt_L;
740 rgKeySyms[nKeySyms++] = XK_Alt_R;
741 }
742 if (mod & Qt::META) {
743 // It would take some code to determine whether the Win key
744 // is associated with Super or Meta, so check for both.
745 // See bug #140023 for details.
746 rgKeySyms[nKeySyms++] = XK_Super_L;
747 rgKeySyms[nKeySyms++] = XK_Super_R;
748 rgKeySyms[nKeySyms++] = XK_Meta_L;
749 rgKeySyms[nKeySyms++] = XK_Meta_R;
750 }
751
752 return areKeySymXsDepressed(rgKeySyms, nKeySyms);
753}
754
755static bool areModKeysDepressedWayland(const QKeySequence &seq)
756{
757 const int mod = seq[seq.count() - 1] & Qt::KeyboardModifierMask;
758 const Qt::KeyboardModifiers mods = input()->modifiersRelevantForGlobalShortcuts();
759 if ((mod & Qt::SHIFT) && mods.testFlag(Qt::ShiftModifier)) {
760 return true;
761 }
762 if ((mod & Qt::CTRL) && mods.testFlag(Qt::ControlModifier)) {
763 return true;
764 }
765 if ((mod & Qt::ALT) && mods.testFlag(Qt::AltModifier)) {
766 return true;
767 }
768 if ((mod & Qt::META) && mods.testFlag(Qt::MetaModifier)) {
769 return true;
770 }
771 return false;
772}
773
774static bool areModKeysDepressed(const QKeySequence &seq)
775{
776 if (seq.isEmpty()) {
777 return false;
778 }
779 if (kwinApp()->shouldUseWaylandForCompositing()) {
780 return areModKeysDepressedWayland(seq);
781 } else {
782 return areModKeysDepressedX11(seq);
783 }
784}
785
786void TabBox::navigatingThroughWindows(bool forward, const QKeySequence &shortcut, TabBoxMode mode)
787{
788 if (!m_ready || isGrabbed()) {
789 return;
790 }
792 // ungrabXKeyboard(); // need that because of accelerator raw mode
793 // CDE style raise / lower
794 CDEWalkThroughWindows(forward);
795 } else {
796 if (areModKeysDepressed(shortcut)) {
797 if (startKDEWalkThroughWindows(mode)) {
798 KDEWalkThroughWindows(forward);
799 }
800 } else {
801 // if the shortcut has no modifiers, don't show the tabbox,
802 // don't grab, but simply go to the next window
803 KDEOneStepThroughWindows(forward, mode);
804 }
805 }
806}
807
809{
810 navigatingThroughWindows(true, m_cutWalkThroughWindows, TabBoxWindowsMode);
811}
812
814{
815 navigatingThroughWindows(false, m_cutWalkThroughWindowsReverse, TabBoxWindowsMode);
816}
817
819{
820 navigatingThroughWindows(true, m_cutWalkThroughWindowsAlternative, TabBoxWindowsAlternativeMode);
821}
822
824{
825 navigatingThroughWindows(false, m_cutWalkThroughWindowsAlternativeReverse, TabBoxWindowsAlternativeMode);
826}
827
829{
830 navigatingThroughWindows(true, m_cutWalkThroughCurrentAppWindows, TabBoxCurrentAppWindowsMode);
831}
832
834{
835 navigatingThroughWindows(false, m_cutWalkThroughCurrentAppWindowsReverse, TabBoxCurrentAppWindowsMode);
836}
837
839{
840 navigatingThroughWindows(true, m_cutWalkThroughCurrentAppWindowsAlternative, TabBoxCurrentAppWindowsAlternativeMode);
841}
842
844{
845 navigatingThroughWindows(false, m_cutWalkThroughCurrentAppWindowsAlternativeReverse, TabBoxCurrentAppWindowsAlternativeMode);
846}
847
848void TabBox::shadeActivate(Window *c)
849{
850 if ((c->shadeMode() == ShadeNormal || c->shadeMode() == ShadeHover) && options->isShadeHover()) {
852 }
853}
854
856{
857 if (m_borderAlternativeActivate.contains(eb)) {
858 return toggleMode(TabBoxWindowsAlternativeMode);
859 } else {
860 return toggleMode(TabBoxWindowsMode);
861 }
862}
863
864bool TabBox::toggleMode(TabBoxMode mode)
865{
867 return false; // not supported.
868 }
869 if (isDisplayed()) {
870 accept();
871 return true;
872 }
873 if (!establishTabBoxGrab()) {
874 return false;
875 }
876 m_noModifierGrab = m_tabGrab = true;
877 setMode(mode);
878 reset();
879 show();
880 return true;
881}
882
883bool TabBox::startKDEWalkThroughWindows(TabBoxMode mode)
884{
885 if (!establishTabBoxGrab()) {
886 return false;
887 }
888 m_tabGrab = true;
889 m_noModifierGrab = false;
890 setMode(mode);
891 reset();
892 return true;
893}
894
895void TabBox::KDEWalkThroughWindows(bool forward)
896{
897 nextPrev(forward);
898 delayedShow();
899}
900
901void TabBox::CDEWalkThroughWindows(bool forward)
902{
903 Window *c = nullptr;
904 // this function find the first suitable client for unreasonable focus
905 // policies - the topmost one, with some exceptions (can't be keepabove/below,
906 // otherwise it gets stuck on them)
907 // Q_ASSERT(Workspace::self()->block_stacking_updates == 0);
908 for (int i = Workspace::self()->stackingOrder().size() - 1; i >= 0; --i) {
909 auto t = Workspace::self()->stackingOrder().at(i);
910 if (t->isClient() && t->isOnCurrentActivity() && t->isOnCurrentDesktop() && !t->isSpecialWindow()
911 && !t->isShade() && t->isShown() && t->wantsTabFocus()
912 && !t->keepAbove() && !t->keepBelow()) {
913 c = t;
914 break;
915 }
916 }
917 Window *nc = c;
918 bool options_traverse_all;
919 {
920 KConfigGroup group(kwinApp()->config(), QStringLiteral("TabBox"));
921 options_traverse_all = group.readEntry("TraverseAll", false);
922 }
923
924 Window *firstClient = nullptr;
925 do {
926 nc = forward ? nextClientStatic(nc) : previousClientStatic(nc);
927 if (!firstClient) {
928 // When we see our first client for the second time,
929 // it's time to stop.
930 firstClient = nc;
931 } else if (nc == firstClient) {
932 // No candidates found.
933 nc = nullptr;
934 break;
935 }
936 } while (nc && nc != c && ((!options_traverse_all && !nc->isOnCurrentDesktop()) || nc->isMinimized() || !nc->wantsTabFocus() || nc->keepAbove() || nc->keepBelow() || !nc->isOnCurrentActivity()));
937 if (nc) {
938 if (c && c != nc) {
940 }
943 shadeActivate(nc);
944 } else {
945 if (!nc->isOnCurrentDesktop()) {
946 VirtualDesktopManager::self()->setCurrent(nc->desktops().constLast());
947 }
949 }
950 }
951}
952
953void TabBox::KDEOneStepThroughWindows(bool forward, TabBoxMode mode)
954{
955 setMode(mode);
956 reset();
957 nextPrev(forward);
958 if (Window *c = currentClient()) {
960 shadeActivate(c);
961 }
962}
963
964void TabBox::keyPress(int keyQt)
965{
966 enum Direction {
967 Backward = -1,
968 Steady = 0,
969 Forward = 1,
970 };
971 Direction direction(Steady);
972
973 auto contains = [](const QKeySequence &shortcut, int key) -> bool {
974 for (int i = 0; i < shortcut.count(); ++i) {
975 if (shortcut[i] == key) {
976 return true;
977 }
978 }
979 return false;
980 };
981
982 // tests whether a shortcut matches and handles pitfalls on ShiftKey invocation
983 auto directionFor = [keyQt, contains](const QKeySequence &forward, const QKeySequence &backward) -> Direction {
984 if (contains(forward, keyQt)) {
985 return Forward;
986 }
987 if (contains(backward, keyQt)) {
988 return Backward;
989 }
990 if (!(keyQt & Qt::ShiftModifier)) {
991 return Steady;
992 }
993
994 // Before testing the unshifted key (Ctrl+A vs. Ctrl+Shift+a etc.),
995 // see whether this is +Shift+Tab/Backtab and test that against
996 // +Shift+Backtab/Tab as well
997 Qt::KeyboardModifiers mods = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier | Qt::KeypadModifier | Qt::GroupSwitchModifier;
998 mods &= keyQt;
999 if (((keyQt & ~mods) == Qt::Key_Tab) || ((keyQt & ~mods) == Qt::Key_Backtab)) {
1000 if (contains(forward, mods | Qt::Key_Backtab) || contains(forward, mods | Qt::Key_Tab)) {
1001 return Forward;
1002 }
1003 if (contains(backward, mods | Qt::Key_Backtab) || contains(backward, mods | Qt::Key_Tab)) {
1004 return Backward;
1005 }
1006 }
1007
1008 // if the shortcuts do not match, try matching again after filtering the shift key from keyQt
1009 // it is needed to handle correctly the ALT+~ shorcut for example as it is coded as ALT+SHIFT+~ in keyQt
1010 if (contains(forward, keyQt & ~Qt::ShiftModifier)) {
1011 return Forward;
1012 }
1013 if (contains(backward, keyQt & ~Qt::ShiftModifier)) {
1014 return Backward;
1015 }
1016
1017 return Steady;
1018 };
1019
1020 if (m_tabGrab) {
1021 static const int ModeCount = 4;
1022 static const TabBoxMode modes[ModeCount] = {
1025 const QKeySequence cuts[2 * ModeCount] = {
1026 // forward
1027 m_cutWalkThroughWindows, m_cutWalkThroughWindowsAlternative,
1028 m_cutWalkThroughCurrentAppWindows, m_cutWalkThroughCurrentAppWindowsAlternative,
1029 // backward
1030 m_cutWalkThroughWindowsReverse, m_cutWalkThroughWindowsAlternativeReverse,
1031 m_cutWalkThroughCurrentAppWindowsReverse, m_cutWalkThroughCurrentAppWindowsAlternativeReverse};
1032 bool testedCurrent = false; // in case of collision, prefer to stay in the current mode
1033 int i = 0, j = 0;
1034 while (true) {
1035 if (!testedCurrent && modes[i] != mode()) {
1036 ++j;
1037 i = (i + 1) % ModeCount;
1038 continue;
1039 }
1040 if (testedCurrent && modes[i] == mode()) {
1041 break;
1042 }
1043 testedCurrent = true;
1044 direction = directionFor(cuts[i], cuts[i + ModeCount]);
1045 if (direction != Steady) {
1046 if (modes[i] != mode()) {
1047 accept(false);
1048 setMode(modes[i]);
1049 auto replayWithChangedTabboxMode = [this, direction]() {
1050 reset();
1051 nextPrev(direction == Forward);
1052 };
1053 QTimer::singleShot(50, this, replayWithChangedTabboxMode);
1054 }
1055 break;
1056 } else if (++j > 2 * ModeCount) { // guarding counter for invalid modes
1057 qCDebug(KWIN_TABBOX) << "Invalid TabBoxMode";
1058 return;
1059 }
1060 i = (i + 1) % ModeCount;
1061 }
1062 if (direction != Steady) {
1063 qCDebug(KWIN_TABBOX) << "== " << cuts[i].toString() << " or " << cuts[i + ModeCount].toString();
1064 KDEWalkThroughWindows(direction == Forward);
1065 }
1066 }
1067
1068 if (m_tabGrab) {
1069 if (((keyQt & ~Qt::KeyboardModifierMask) == Qt::Key_Escape) && direction == Steady) {
1070 // if Escape is part of the shortcut, don't cancel
1071 close(true);
1072 } else if (direction == Steady) {
1073 QKeyEvent event(QEvent::KeyPress, keyQt & ~Qt::KeyboardModifierMask, Qt::NoModifier);
1074 grabbedKeyEvent(&event);
1075 }
1076 }
1077}
1078
1079void TabBox::close(bool abort)
1080{
1081 if (isGrabbed()) {
1082 removeTabBoxGrab();
1083 }
1084 hide(abort);
1086 m_tabGrab = false;
1087 m_noModifierGrab = false;
1088}
1089
1090void TabBox::accept(bool closeTabBox)
1091{
1092 Window *c = currentClient();
1093 if (closeTabBox) {
1094 close();
1095 }
1096 if (c) {
1098 shadeActivate(c);
1099 if (c->isDesktop()) {
1100 Workspace::self()->setShowingDesktop(!Workspace::self()->showingDesktop(), !m_defaultConfig.isHighlightWindows());
1101 }
1102 }
1103}
1104
1106{
1107 if (m_noModifierGrab) {
1108 return;
1109 }
1110 if (m_tabGrab) {
1111 accept();
1112 }
1113}
1114
1120{
1121 const auto &list = Workspace::self()->windows();
1122 if (!c || list.isEmpty()) {
1123 return nullptr;
1124 }
1125 const int reference = list.indexOf(c);
1126 if (reference == -1) {
1127 return list.first();
1128 }
1129 for (int i = reference + 1; i < list.count(); ++i) {
1130 Window *candidate = list[i];
1131 if (candidate->isClient()) {
1132 return candidate;
1133 }
1134 }
1135 // wrap around
1136 for (int i = 0; i < reference; ++i) {
1137 Window *candidate = list[i];
1138 if (candidate->isClient()) {
1139 return candidate;
1140 }
1141 }
1142 return nullptr;
1143}
1144
1150{
1151 const auto &list = Workspace::self()->windows();
1152 if (!c || list.isEmpty()) {
1153 return nullptr;
1154 }
1155 const int reference = list.indexOf(c);
1156 if (reference == -1) {
1157 return list.last();
1158 }
1159 for (int i = reference - 1; i >= 0; --i) {
1160 Window *candidate = list[i];
1161 if (candidate->isClient()) {
1162 return candidate;
1163 }
1164 }
1165 // wrap around
1166 for (int i = list.size() - 1; i > reference; --i) {
1167 Window *candidate = list[i];
1168 if (candidate->isClient()) {
1169 return candidate;
1170 }
1171 }
1172 return nullptr;
1173}
1174
1175bool TabBox::establishTabBoxGrab()
1176{
1177 if (kwinApp()->shouldUseWaylandForCompositing()) {
1178 m_forcedGlobalMouseGrab = true;
1179 return true;
1180 }
1181 kwinApp()->updateXTime();
1182 if (!grabXKeyboard()) {
1183 return false;
1184 }
1185 // Don't try to establish a global mouse grab using XGrabPointer, as that would prevent
1186 // using Alt+Tab while DND (#44972). However force passive grabs on all windows
1187 // in order to catch MouseRelease events and close the tabbox (#67416).
1188 // All clients already have passive grabs in their wrapper windows, so check only
1189 // the active client, which may not have it.
1190 Q_ASSERT(!m_forcedGlobalMouseGrab);
1191 m_forcedGlobalMouseGrab = true;
1192 if (Workspace::self()->activeWindow() != nullptr) {
1194 }
1195 m_x11EventFilter = std::make_unique<X11Filter>();
1196 return true;
1197}
1198
1199void TabBox::removeTabBoxGrab()
1200{
1201 if (kwinApp()->shouldUseWaylandForCompositing()) {
1202 m_forcedGlobalMouseGrab = false;
1203 return;
1204 }
1205 kwinApp()->updateXTime();
1207 Q_ASSERT(m_forcedGlobalMouseGrab);
1208 m_forcedGlobalMouseGrab = false;
1209 if (Workspace::self()->activeWindow() != nullptr) {
1211 }
1212 m_x11EventFilter.reset();
1213}
1214} // namespace TabBox
1215} // namespace
1216
1217#include "moc_tabbox.cpp"
static bool compositing()
Static check to test whether the Compositor is available and active.
Definition compositor.h:78
void highlightWindows(const QList< EffectWindow * > &windows)
bool checkInputWindowEvent(QMouseEvent *e)
bool contains(Window *window) const
Checks whether the most recently used focus chain contains the given window.
Definition focuschain.h:209
Window * firstMostRecentlyUsed() const
Returns the first Window in the most recently used focus chain. First Window in this case means reall...
Window * nextMostRecentlyUsed(Window *reference) const
Queries the most recently used focus chain for the next Window after the given reference Window.
PointerInputRedirection * pointer() const
Definition input.h:220
Qt::KeyboardModifiers modifiersRelevantForGlobalShortcuts() const
Definition input.cpp:3309
bool isShadeHover() const
Definition options.h:302
bool focusPolicyIsReasonable
Definition options.h:113
void reserve(ElectricBorder border, QObject *object, const char *callback)
void unreserve(ElectricBorder border, QObject *object)
void reserveTouch(ElectricBorder border, QAction *action, TouchCallback::CallbackFunction callback=nullptr)
@ AllActivitiesClients
Windows from all Activities are included.
@ OnlyCurrentActivityClients
Only Windows on current activity are included.
@ ExcludeCurrentActivityClients
Exclude Windows on current activity.
static ClientSwitchingMode defaultSwitchingMode()
@ OnlyMinimizedClients
Only minimized Windows are included.
@ IgnoreMinimizedStatus
Windows are included no matter they are minimized or not.
@ ExcludeMinimizedClients
Exclude minimized Windows.
void setClientMinimizedMode(ClientMinimizedMode minimizedMode)
void setClientActivitiesMode(ClientActivitiesMode activitiesMode)
@ OneWindowPerApplication
Only one Window for each application is included.
@ AllWindowsAllApplications
Windows from all applications are included.
@ AllWindowsCurrentApplication
Only Windows for the current application are included.
static ClientActivitiesMode defaultActivitiesMode()
void setClientApplicationsMode(ClientApplicationsMode applicationsMode)
static ClientMultiScreenMode defaultMultiScreenMode()
@ AllDesktopsClients
Windows from all desktops are included.
@ OnlyCurrentDesktopClients
Only Windows on current desktop are included.
@ ExcludeCurrentDesktopClients
Exclude Windows on current desktop.
static OrderMinimizedMode defaultOrderMinimizedMode()
static ClientApplicationsMode defaultApplicationsMode()
@ NoGroupByMinimized
Windows are not grouped by whether they are minimized.
static QString defaultLayoutName()
void setClientMultiScreenMode(ClientMultiScreenMode multiScreenMode)
@ FocusChainSwitching
Sort by recently used. Most recently used Window is the first.
static ShowDesktopMode defaultShowDesktopMode()
static ClientMinimizedMode defaultMinimizedMode()
void setOrderMinimizedMode(OrderMinimizedMode orderMinimizedMode)
void setShowDesktopMode(ShowDesktopMode showDesktopMode)
@ DoNotShowDesktopClient
A Window representing the desktop is not included.
@ ExcludeCurrentScreenClients
Exclude Windows from the current screen.
@ IgnoreMultiScreen
Windows are included independently of the screen they are on.
static bool defaultHighlightWindow()
void setClientSwitchingMode(ClientSwitchingMode switchingMode)
static bool defaultShowTabBox()
void setClientDesktopMode(ClientDesktopMode desktopMode)
static ClientDesktopMode defaultDesktopMode()
void createModel(bool partialReset=false)
const QModelIndex & currentIndex() const
bool containsPos(const QPoint &pos) const
void setConfig(const TabBoxConfig &config)
void hide(bool abort=false)
QList< Window * > clientList() const
virtual void grabbedKeyEvent(QKeyEvent *event) const
QModelIndex nextPrev(bool forward) const
Window * client(const QModelIndex &index) const
QModelIndex index(Window *client) const
const TabBoxConfig & config() const
virtual Window * activeClient() const =0
void setCurrentIndex(const QModelIndex &index)
Window * desktopClient() const override
Definition tabbox.cpp:246
QList< Window * > stackingOrder() const override
Definition tabbox.cpp:201
void shadeClient(Window *c, bool b) const override
Definition tabbox.cpp:236
void activateAndClose() override
Definition tabbox.cpp:257
Window * nextClientFocusChain(Window *client) const override
Definition tabbox.cpp:82
void restack(Window *c, Window *under) override
Definition tabbox.cpp:223
Window * clientToAddToList(Window *client) const override
Definition tabbox.cpp:172
void elevateClient(Window *c, QWindow *tabbox, bool elevate) const override
Definition tabbox.cpp:228
bool isKWinCompositing() const override
Definition tabbox.cpp:213
TabBoxHandlerImpl(TabBox *tabBox)
Definition tabbox.cpp:59
int activeScreen() const override
Definition tabbox.cpp:69
QString desktopName(Window *client) const override
Definition tabbox.cpp:74
Window * firstClientFocusChain() const override
Definition tabbox.cpp:87
Window * activeClient() const override
Definition tabbox.cpp:97
void highlightWindows(Window *window=nullptr, QWindow *controller=nullptr) override
Definition tabbox.cpp:262
bool noModifierGrab() const override
Definition tabbox.cpp:277
bool isInFocusChain(Window *client) const override
Definition tabbox.cpp:92
void raiseClient(Window *client) const override
Definition tabbox.cpp:218
bool handleMouseEvent(QMouseEvent *event)
Definition tabbox.cpp:605
Window * previousClientStatic(Window *) const
Definition tabbox.cpp:1149
QList< Window * > currentClientList()
Definition tabbox.cpp:452
Window * currentClient()
Definition tabbox.cpp:440
bool toggle(ElectricBorder eb)
Definition tabbox.cpp:855
TabBoxMode mode() const
Definition tabbox.h:97
void close(bool abort=false)
Definition tabbox.cpp:1079
void grabbedKeyEvent(QKeyEvent *event)
Definition tabbox.cpp:653
void slotWalkBackThroughCurrentAppWindows()
Definition tabbox.cpp:833
void hide(bool abort=false)
Definition tabbox.cpp:486
void setCurrentIndex(QModelIndex index, bool notifyEffects=true)
Definition tabbox.cpp:462
void slotWalkThroughWindowsAlternative()
Definition tabbox.cpp:818
void slotWalkThroughCurrentAppWindows()
Definition tabbox.cpp:828
void keyPress(int key)
Definition tabbox.cpp:964
void reset(bool partial_reset=false)
Definition tabbox.cpp:413
void nextPrev(bool next=true)
Definition tabbox.cpp:434
void accept(bool closeTabBox=true)
Definition tabbox.cpp:1090
Window * nextClientStatic(Window *) const
Definition tabbox.cpp:1119
void slotWalkBackThroughWindowsAlternative()
Definition tabbox.cpp:823
void slotWalkThroughCurrentAppWindowsAlternative()
Definition tabbox.cpp:838
void slotWalkBackThroughWindows()
Definition tabbox.cpp:813
void slotWalkBackThroughCurrentAppWindowsAlternative()
Definition tabbox.cpp:843
void setMode(TabBoxMode mode)
Definition tabbox.cpp:394
void setCurrentClient(Window *newClient)
Definition tabbox.cpp:457
bool isDisplayed() const
Definition tabbox.h:160
void tabBoxKeyEvent(QKeyEvent *)
bool isGrabbed() const
Definition tabbox.h:177
void slotWalkThroughWindows()
Definition tabbox.cpp:808
bool handleWheelEvent(QWheelEvent *event)
Definition tabbox.cpp:635
bool noModifierGrab() const
Definition tabbox.h:194
bool skipSwitcher
Definition window.h:320
virtual bool isClient() const
Definition window.cpp:302
void setShade(bool set)
Definition window.cpp:863
virtual void updateMouseGrab()
Definition window.cpp:422
EffectWindow * effectWindow()
Definition window.cpp:279
bool wantsTabFocus() const
Definition window.cpp:697
bool isOnCurrentActivity() const
Definition window.cpp:3097
static bool belongToSameApplication(const Window *c1, const Window *c2, SameApplicationChecks checks=SameApplicationChecks())
Definition window.cpp:426
bool isOnAllDesktops() const
Definition window.h:2032
bool isOnCurrentDesktop() const
Definition window.cpp:848
void elevate(bool elevate)
Definition window.cpp:312
bool isMinimized() const
Definition window.h:988
bool isDesktop() const
Definition window.h:1922
QList< KWin::VirtualDesktop * > desktops
Definition window.h:295
ShadeMode shadeMode() const
Definition window.cpp:853
void cancelShadeHoverTimer()
Definition window.cpp:941
KWin::Output * output
Definition window.h:111
virtual Window * findModal(bool allow_itself=false)=0
bool isDeleted() const
Definition window.cpp:540
Window * activeWindow() const
Definition workspace.h:767
ScreenEdges * screenEdges() const
void activateWindow(Window *window, bool force=false)
const QList< Window * > & stackingOrder() const
Definition workspace.h:788
Output * activeOutput() const
void restack(Window *window, Window *under, bool force=false)
Definition layers.cpp:437
void raiseWindow(Window *window, bool nogroup=false)
Definition layers.cpp:354
static Workspace * self()
Definition workspace.h:91
FocusChain * focusChain() const
void lowerWindow(Window *window, bool nogroup=false)
Definition layers.cpp:296
QList< Output * > outputs() const
Definition workspace.h:762
const QList< Window * > windows() const
Definition workspace.h:248
void configChanged()
void setShowingDesktop(bool showing, bool animated=true)
TabBoxHandler * tabBox
@ ShadeActivated
Definition common.h:66
@ ShadeHover
Definition common.h:65
@ ShadeNormal
Definition common.h:64
ElectricBorder
Definition globals.h:60
Workspace * workspace()
Definition workspace.h:830
KWIN_EXPORT xcb_connection_t * connection()
Definition xcb.h:19
Options * options
Definition main.cpp:73
TabBoxMode
Definition globals.h:88
@ TabBoxCurrentAppWindowsMode
Definition globals.h:91
@ TabBoxCurrentAppWindowsAlternativeMode
Definition globals.h:92
@ TabBoxWindowsAlternativeMode
Definition globals.h:90
@ TabBoxWindowsMode
Definition globals.h:89
void KWIN_EXPORT ungrabXKeyboard()
Definition common.cpp:121
InputRedirection * input()
Definition input.h:549
EffectsHandler * effects
bool KWIN_EXPORT grabXKeyboard(xcb_window_t w=XCB_WINDOW_NONE)
Definition common.cpp:90
void operator()(xcb_key_symbols_t *symbols)
Definition tabbox.cpp:671
struct _XCBKeySymbols xcb_key_symbols_t