/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich SPDX-FileCopyrightText: 2003 Lubos Lunak SPDX-FileCopyrightText: 2009 Martin Gräßlin SPDX-License-Identifier: GPL-2.0-or-later */ //#define QT_CLEAN_NAMESPACE // own #include "tabbox.h" // tabbox #include "tabbox/clientmodel.h" #include "tabbox/desktopmodel.h" #include "tabbox/tabboxconfig.h" #include "tabbox/desktopchain.h" #include "tabbox/tabbox_logging.h" #include "tabbox/x11_filter.h" // kwin #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "x11client.h" #include "effects.h" #include "input.h" #include "keyboard_input.h" #include "pointer_input.h" #include "focuschain.h" #include "screenedge.h" #include "screens.h" #include "unmanaged.h" #include "virtualdesktops.h" #include "workspace.h" #include "xcbutils.h" // Qt #include #include // KDE #include #include #include #include #include // X11 #include #include // xcb #include // specify externals before namespace namespace KWin { namespace TabBox { TabBoxHandlerImpl::TabBoxHandlerImpl(TabBox* tabBox) : TabBoxHandler(tabBox) , m_tabBox(tabBox) , m_desktopFocusChain(new DesktopChainManager(this)) { // connects for DesktopFocusChainManager VirtualDesktopManager *vds = VirtualDesktopManager::self(); connect(vds, &VirtualDesktopManager::countChanged, m_desktopFocusChain, &DesktopChainManager::resize); connect(vds, &VirtualDesktopManager::currentChanged, m_desktopFocusChain, &DesktopChainManager::addDesktop); #ifdef KWIN_BUILD_ACTIVITIES if (Activities::self()) { connect(Activities::self(), &Activities::currentChanged, m_desktopFocusChain, &DesktopChainManager::useChain); } #endif } TabBoxHandlerImpl::~TabBoxHandlerImpl() { } int TabBoxHandlerImpl::activeScreen() const { return screens()->current(); } int TabBoxHandlerImpl::currentDesktop() const { return VirtualDesktopManager::self()->current(); } QString TabBoxHandlerImpl::desktopName(TabBoxClient* client) const { if (TabBoxClientImpl* c = static_cast< TabBoxClientImpl* >(client)) { if (!c->client()->isOnAllDesktops()) return VirtualDesktopManager::self()->name(c->client()->desktop()); } return VirtualDesktopManager::self()->name(VirtualDesktopManager::self()->current()); } QString TabBoxHandlerImpl::desktopName(int desktop) const { return VirtualDesktopManager::self()->name(desktop); } QWeakPointer TabBoxHandlerImpl::nextClientFocusChain(TabBoxClient* client) const { if (TabBoxClientImpl* c = static_cast< TabBoxClientImpl* >(client)) { auto next = FocusChain::self()->nextMostRecentlyUsed(c->client()); if (next) return next->tabBoxClient(); } return QWeakPointer(); } QWeakPointer< TabBoxClient > TabBoxHandlerImpl::firstClientFocusChain() const { if (auto c = FocusChain::self()->firstMostRecentlyUsed()) { return QWeakPointer(c->tabBoxClient()); } else { return QWeakPointer(); } } bool TabBoxHandlerImpl::isInFocusChain(TabBoxClient *client) const { if (TabBoxClientImpl *c = static_cast(client)) { return FocusChain::self()->contains(c->client()); } return false; } int TabBoxHandlerImpl::nextDesktopFocusChain(int desktop) const { return m_desktopFocusChain->next(desktop); } int TabBoxHandlerImpl::numberOfDesktops() const { return VirtualDesktopManager::self()->count(); } QWeakPointer TabBoxHandlerImpl::activeClient() const { if (Workspace::self()->activeClient()) return Workspace::self()->activeClient()->tabBoxClient(); else return QWeakPointer(); } bool TabBoxHandlerImpl::checkDesktop(TabBoxClient* client, int desktop) const { auto current = (static_cast< TabBoxClientImpl* >(client))->client(); switch (config().clientDesktopMode()) { case TabBoxConfig::AllDesktopsClients: return true; case TabBoxConfig::ExcludeCurrentDesktopClients: return !current->isOnDesktop(desktop); default: // TabBoxConfig::OnlyCurrentDesktopClients return current->isOnDesktop(desktop); } } bool TabBoxHandlerImpl::checkActivity(TabBoxClient* client) const { auto current = (static_cast< TabBoxClientImpl* >(client))->client(); switch (config().clientActivitiesMode()) { case TabBoxConfig::AllActivitiesClients: return true; case TabBoxConfig::ExcludeCurrentActivityClients: return !current->isOnCurrentActivity(); default: // TabBoxConfig::OnlyCurrentActivityClients return current->isOnCurrentActivity(); } } bool TabBoxHandlerImpl::checkApplications(TabBoxClient* client) const { auto current = (static_cast< TabBoxClientImpl* >(client))->client(); TabBoxClientImpl* c; QListIterator< QWeakPointer > i(clientList()); switch (config().clientApplicationsMode()) { case TabBoxConfig::OneWindowPerApplication: // check if the list already contains an entry of this application while (i.hasNext()) { QSharedPointer client = i.next().toStrongRef(); if (!client) { continue; } if ((c = dynamic_cast< TabBoxClientImpl* >(client.data()))) { if (AbstractClient::belongToSameApplication(c->client(), current, AbstractClient::SameApplicationCheck::AllowCrossProcesses)) { return false; } } } return true; case TabBoxConfig::AllWindowsCurrentApplication: { QSharedPointer pointer = tabBox->activeClient().toStrongRef(); if (!pointer) { return false; } if ((c = dynamic_cast< TabBoxClientImpl* >(pointer.data()))) { if (AbstractClient::belongToSameApplication(c->client(), current, AbstractClient::SameApplicationCheck::AllowCrossProcesses)) { return true; } } return false; } default: // TabBoxConfig::AllWindowsAllApplications return true; } } bool TabBoxHandlerImpl::checkMinimized(TabBoxClient* client) const { switch (config().clientMinimizedMode()) { case TabBoxConfig::ExcludeMinimizedClients: return !client->isMinimized(); case TabBoxConfig::OnlyMinimizedClients: return client->isMinimized(); default: // TabBoxConfig::IgnoreMinimizedStatus return true; } } bool TabBoxHandlerImpl::checkMultiScreen(TabBoxClient* client) const { auto current = (static_cast< TabBoxClientImpl* >(client))->client(); switch (config().clientMultiScreenMode()) { case TabBoxConfig::IgnoreMultiScreen: return true; case TabBoxConfig::ExcludeCurrentScreenClients: return current->screen() != screens()->current(); default: // TabBoxConfig::OnlyCurrentScreenClients return current->screen() == screens()->current(); } } QWeakPointer TabBoxHandlerImpl::clientToAddToList(TabBoxClient* client, int desktop) const { if (!client) { return QWeakPointer(); } AbstractClient* ret = nullptr; AbstractClient* current = (static_cast< TabBoxClientImpl* >(client))->client(); bool addClient = checkDesktop(client, desktop) && checkActivity(client) && checkApplications(client) && checkMinimized(client) && checkMultiScreen(client); addClient = addClient && current->wantsTabFocus() && !current->skipSwitcher(); if (addClient) { // don't add windows that have modal dialogs AbstractClient* modal = current->findModal(); if (modal == nullptr || modal == current) ret = current; else if (!clientList().contains(modal->tabBoxClient())) ret = modal; else { // nothing } } if (ret) return ret->tabBoxClient(); else return QWeakPointer(); } TabBoxClientList TabBoxHandlerImpl::stackingOrder() const { QList stacking = Workspace::self()->stackingOrder(); TabBoxClientList ret; foreach (Toplevel *toplevel, stacking) { if (auto client = qobject_cast(toplevel)) { ret.append(client->tabBoxClient()); } } return ret; } bool TabBoxHandlerImpl::isKWinCompositing() const { return Workspace::self()->compositing(); } void TabBoxHandlerImpl::raiseClient(TabBoxClient* c) const { Workspace::self()->raiseClient(static_cast(c)->client()); } void TabBoxHandlerImpl::restack(TabBoxClient *c, TabBoxClient *under) { Workspace::self()->restack(static_cast(c)->client(), static_cast(under)->client(), true); } void TabBoxHandlerImpl::elevateClient(TabBoxClient *c, QWindow *tabbox, bool b) const { auto cl = static_cast(c)->client(); cl->elevate(b); if (Toplevel *w = Workspace::self()->findInternal(tabbox)) w->elevate(b); } void TabBoxHandlerImpl::shadeClient(TabBoxClient *c, bool b) const { AbstractClient *client = static_cast(c)->client(); client->cancelShadeHoverTimer(); // stop core shading action if (!b && client->shadeMode() == ShadeNormal) client->setShade(ShadeHover); else if (b && client->shadeMode() == ShadeHover) client->setShade(ShadeNormal); } QWeakPointer TabBoxHandlerImpl::desktopClient() const { foreach (Toplevel *toplevel, Workspace::self()->stackingOrder()) { auto client = qobject_cast(toplevel); if (client && client->isDesktop() && client->isOnCurrentDesktop() && client->screen() == screens()->current()) { return client->tabBoxClient(); } } return QWeakPointer(); } void TabBoxHandlerImpl::activateAndClose() { m_tabBox->accept(); } void TabBoxHandlerImpl::highlightWindows(TabBoxClient *window, QWindow *controller) { if (!effects) { return; } QVector windows; if (window) { windows << static_cast(window)->client()->effectWindow(); } if (Toplevel *t = workspace()->findInternal(controller)) { windows << t->effectWindow(); } static_cast(effects)->highlightWindows(windows); } bool TabBoxHandlerImpl::noModifierGrab() const { return m_tabBox->noModifierGrab(); } /********************************************************* * TabBoxClientImpl *********************************************************/ TabBoxClientImpl::TabBoxClientImpl(AbstractClient *client) : TabBoxClient() , m_client(client) { } TabBoxClientImpl::~TabBoxClientImpl() { } QString TabBoxClientImpl::caption() const { if (m_client->isDesktop()) return i18nc("Special entry in alt+tab list for minimizing all windows", "Show Desktop"); return m_client->caption(); } QIcon TabBoxClientImpl::icon() const { if (m_client->isDesktop()) { return QIcon::fromTheme(QStringLiteral("user-desktop")); } return m_client->icon(); } bool TabBoxClientImpl::isMinimized() const { return m_client->isMinimized(); } int TabBoxClientImpl::x() const { return m_client->x(); } int TabBoxClientImpl::y() const { return m_client->y(); } int TabBoxClientImpl::width() const { return m_client->width(); } int TabBoxClientImpl::height() const { return m_client->height(); } bool TabBoxClientImpl::isCloseable() const { return m_client->isCloseable(); } void TabBoxClientImpl::close() { m_client->closeWindow(); } bool TabBoxClientImpl::isFirstInTabBox() const { return m_client->isFirstInTabBox(); } QUuid TabBoxClientImpl::internalId() const { return m_client->internalId(); } /********************************************************* * TabBox *********************************************************/ TabBox *TabBox::s_self = nullptr; TabBox *TabBox::create(QObject *parent) { Q_ASSERT(!s_self); s_self = new TabBox(parent); return s_self; } TabBox::TabBox(QObject *parent) : QObject(parent) , m_displayRefcount(0) , m_desktopGrab(false) , m_tabGrab(false) , m_noModifierGrab(false) , m_forcedGlobalMouseGrab(false) , m_ready(false) { m_isShown = false; m_defaultConfig = TabBoxConfig(); m_defaultConfig.setTabBoxMode(TabBoxConfig::ClientTabBox); m_defaultConfig.setClientDesktopMode(TabBoxConfig::OnlyCurrentDesktopClients); m_defaultConfig.setClientActivitiesMode(TabBoxConfig::OnlyCurrentActivityClients); m_defaultConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsAllApplications); m_defaultConfig.setClientMinimizedMode(TabBoxConfig::IgnoreMinimizedStatus); m_defaultConfig.setShowDesktopMode(TabBoxConfig::DoNotShowDesktopClient); m_defaultConfig.setClientMultiScreenMode(TabBoxConfig::IgnoreMultiScreen); m_defaultConfig.setClientSwitchingMode(TabBoxConfig::FocusChainSwitching); m_alternativeConfig = TabBoxConfig(); m_alternativeConfig.setTabBoxMode(TabBoxConfig::ClientTabBox); m_alternativeConfig.setClientDesktopMode(TabBoxConfig::AllDesktopsClients); m_alternativeConfig.setClientActivitiesMode(TabBoxConfig::OnlyCurrentActivityClients); m_alternativeConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsAllApplications); m_alternativeConfig.setClientMinimizedMode(TabBoxConfig::IgnoreMinimizedStatus); m_alternativeConfig.setShowDesktopMode(TabBoxConfig::DoNotShowDesktopClient); m_alternativeConfig.setClientMultiScreenMode(TabBoxConfig::IgnoreMultiScreen); m_alternativeConfig.setClientSwitchingMode(TabBoxConfig::FocusChainSwitching); m_defaultCurrentApplicationConfig = m_defaultConfig; m_defaultCurrentApplicationConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsCurrentApplication); m_alternativeCurrentApplicationConfig = m_alternativeConfig; m_alternativeCurrentApplicationConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsCurrentApplication); m_desktopConfig = TabBoxConfig(); m_desktopConfig.setTabBoxMode(TabBoxConfig::DesktopTabBox); m_desktopConfig.setShowTabBox(true); m_desktopConfig.setShowDesktopMode(TabBoxConfig::DoNotShowDesktopClient); m_desktopConfig.setDesktopSwitchingMode(TabBoxConfig::MostRecentlyUsedDesktopSwitching); m_desktopListConfig = TabBoxConfig(); m_desktopListConfig.setTabBoxMode(TabBoxConfig::DesktopTabBox); m_desktopListConfig.setShowTabBox(true); m_desktopListConfig.setShowDesktopMode(TabBoxConfig::DoNotShowDesktopClient); m_desktopListConfig.setDesktopSwitchingMode(TabBoxConfig::StaticDesktopSwitching); m_tabBox = new TabBoxHandlerImpl(this); QTimer::singleShot(0, this, &TabBox::handlerReady); m_tabBoxMode = TabBoxDesktopMode; // init variables connect(&m_delayedShowTimer, &QTimer::timeout, this, &TabBox::show); connect(Workspace::self(), &Workspace::configChanged, this, &TabBox::reconfigure); } TabBox::~TabBox() { s_self = nullptr; } void TabBox::handlerReady() { m_tabBox->setConfig(m_defaultConfig); reconfigure(); m_ready = true; } template void TabBox::key(const char *actionName, Slot slot, const QKeySequence &shortcut) { QAction *a = new QAction(this); a->setProperty("componentName", QStringLiteral(KWIN_NAME)); a->setObjectName(QString::fromUtf8(actionName)); a->setText(i18n(actionName)); KGlobalAccel::self()->setGlobalShortcut(a, QList() << shortcut); input()->registerShortcut(shortcut, a, TabBox::self(), slot); auto cuts = KGlobalAccel::self()->shortcut(a); globalShortcutChanged(a, cuts.isEmpty() ? QKeySequence() : cuts.first()); } static const char s_windows[] = I18N_NOOP("Walk Through Windows"); static const char s_windowsRev[] = I18N_NOOP("Walk Through Windows (Reverse)"); static const char s_windowsAlt[] = I18N_NOOP("Walk Through Windows Alternative"); static const char s_windowsAltRev[] = I18N_NOOP("Walk Through Windows Alternative (Reverse)"); static const char s_app[] = I18N_NOOP("Walk Through Windows of Current Application"); static const char s_appRev[] = I18N_NOOP("Walk Through Windows of Current Application (Reverse)"); static const char s_appAlt[] = I18N_NOOP("Walk Through Windows of Current Application Alternative"); static const char s_appAltRev[] = I18N_NOOP("Walk Through Windows of Current Application Alternative (Reverse)"); static const char s_desktops[] = I18N_NOOP("Walk Through Desktops"); static const char s_desktopsRev[] = I18N_NOOP("Walk Through Desktops (Reverse)"); static const char s_desktopList[] = I18N_NOOP("Walk Through Desktop List"); static const char s_desktopListRev[] = I18N_NOOP("Walk Through Desktop List (Reverse)"); void TabBox::initShortcuts() { key(s_windows, &TabBox::slotWalkThroughWindows, Qt::ALT + Qt::Key_Tab); key(s_windowsRev, &TabBox::slotWalkBackThroughWindows, Qt::ALT + Qt::SHIFT + Qt::Key_Backtab); key(s_app, &TabBox::slotWalkThroughCurrentAppWindows, Qt::ALT + Qt::Key_QuoteLeft); key(s_appRev, &TabBox::slotWalkBackThroughCurrentAppWindows, Qt::ALT + Qt::Key_AsciiTilde); key(s_windowsAlt, &TabBox::slotWalkThroughWindowsAlternative); key(s_windowsAltRev, &TabBox::slotWalkBackThroughWindowsAlternative); key(s_appAlt, &TabBox::slotWalkThroughCurrentAppWindowsAlternative); key(s_appAltRev, &TabBox::slotWalkBackThroughCurrentAppWindowsAlternative); key(s_desktops, &TabBox::slotWalkThroughDesktops); key(s_desktopsRev, &TabBox::slotWalkBackThroughDesktops); key(s_desktopList, &TabBox::slotWalkThroughDesktopList); key(s_desktopListRev, &TabBox::slotWalkBackThroughDesktopList); connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutChanged, this, &TabBox::globalShortcutChanged); } void TabBox::globalShortcutChanged(QAction *action, const QKeySequence &seq) { if (qstrcmp(qPrintable(action->objectName()), s_windows) == 0) { m_cutWalkThroughWindows = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_windowsRev) == 0) { m_cutWalkThroughWindowsReverse = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_app) == 0) { m_cutWalkThroughCurrentAppWindows = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_appRev) == 0) { m_cutWalkThroughCurrentAppWindowsReverse = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_windowsAlt) == 0) { m_cutWalkThroughWindowsAlternative = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_windowsAltRev) == 0) { m_cutWalkThroughWindowsAlternativeReverse = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_appAlt) == 0) { m_cutWalkThroughCurrentAppWindowsAlternative = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_appAltRev) == 0) { m_cutWalkThroughCurrentAppWindowsAlternativeReverse = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_desktops) == 0) { m_cutWalkThroughDesktops = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_desktopsRev) == 0) { m_cutWalkThroughDesktopsReverse = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_desktopList) == 0) { m_cutWalkThroughDesktopList = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_desktopListRev) == 0) { m_cutWalkThroughDesktopListReverse = seq; } } void TabBox::setMode(TabBoxMode mode) { m_tabBoxMode = mode; switch(mode) { case TabBoxWindowsMode: m_tabBox->setConfig(m_defaultConfig); break; case TabBoxWindowsAlternativeMode: m_tabBox->setConfig(m_alternativeConfig); break; case TabBoxCurrentAppWindowsMode: m_tabBox->setConfig(m_defaultCurrentApplicationConfig); break; case TabBoxCurrentAppWindowsAlternativeMode: m_tabBox->setConfig(m_alternativeCurrentApplicationConfig); break; case TabBoxDesktopMode: m_tabBox->setConfig(m_desktopConfig); break; case TabBoxDesktopListMode: m_tabBox->setConfig(m_desktopListConfig); break; } } void TabBox::reset(bool partial_reset) { switch(m_tabBox->config().tabBoxMode()) { case TabBoxConfig::ClientTabBox: m_tabBox->createModel(partial_reset); if (!partial_reset) { if (Workspace::self()->activeClient()) setCurrentClient(Workspace::self()->activeClient()); // it's possible that the active client is not part of the model // in that case the index is invalid if (!m_tabBox->currentIndex().isValid()) setCurrentIndex(m_tabBox->first()); } else { if (!m_tabBox->currentIndex().isValid() || !m_tabBox->client(m_tabBox->currentIndex())) setCurrentIndex(m_tabBox->first()); } break; case TabBoxConfig::DesktopTabBox: m_tabBox->createModel(); if (!partial_reset) setCurrentDesktop(VirtualDesktopManager::self()->current()); break; } emit tabBoxUpdated(); } void TabBox::nextPrev(bool next) { setCurrentIndex(m_tabBox->nextPrev(next), false); emit tabBoxUpdated(); } AbstractClient* TabBox::currentClient() { if (TabBoxClientImpl* client = static_cast< TabBoxClientImpl* >(m_tabBox->client(m_tabBox->currentIndex()))) { if (!Workspace::self()->hasClient(client->client())) return nullptr; return client->client(); } else return nullptr; } QList TabBox::currentClientList() { TabBoxClientList list = m_tabBox->clientList(); QList ret; foreach (const QWeakPointer &clientPointer, list) { QSharedPointer client = clientPointer.toStrongRef(); if (!client) continue; if (const TabBoxClientImpl* c = static_cast< const TabBoxClientImpl* >(client.data())) ret.append(c->client()); } return ret; } int TabBox::currentDesktop() { return m_tabBox->desktop(m_tabBox->currentIndex()); } QList< int > TabBox::currentDesktopList() { return m_tabBox->desktopList(); } void TabBox::setCurrentClient(AbstractClient *newClient) { setCurrentIndex(m_tabBox->index(newClient->tabBoxClient())); } void TabBox::setCurrentDesktop(int newDesktop) { setCurrentIndex(m_tabBox->desktopIndex(newDesktop)); } void TabBox::setCurrentIndex(QModelIndex index, bool notifyEffects) { if (!index.isValid()) return; m_tabBox->setCurrentIndex(index); if (notifyEffects) { emit tabBoxUpdated(); } } void TabBox::show() { emit tabBoxAdded(m_tabBoxMode); if (isDisplayed()) { m_isShown = false; return; } workspace()->setShowingDesktop(false); reference(); m_isShown = true; m_tabBox->show(); } void TabBox::hide(bool abort) { m_delayedShowTimer.stop(); if (m_isShown) { m_isShown = false; unreference(); } emit tabBoxClosed(); if (isDisplayed()) qCDebug(KWIN_TABBOX) << "Tab box was not properly closed by an effect"; m_tabBox->hide(abort); if (kwinApp()->x11Connection()) { Xcb::sync(); } } void TabBox::reconfigure() { KSharedConfigPtr c = kwinApp()->config(); KConfigGroup config = c->group("TabBox"); loadConfig(c->group("TabBox"), m_defaultConfig); loadConfig(c->group("TabBoxAlternative"), m_alternativeConfig); m_defaultCurrentApplicationConfig = m_defaultConfig; m_defaultCurrentApplicationConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsCurrentApplication); m_alternativeCurrentApplicationConfig = m_alternativeConfig; m_alternativeCurrentApplicationConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsCurrentApplication); m_tabBox->setConfig(m_defaultConfig); m_delayShow = config.readEntry("ShowDelay", true); m_delayShowTime = config.readEntry("DelayTime", 90); const QString defaultDesktopLayout = QStringLiteral("org.kde.breeze.desktop"); m_desktopConfig.setLayoutName(config.readEntry("DesktopLayout", defaultDesktopLayout)); m_desktopListConfig.setLayoutName(config.readEntry("DesktopListLayout", defaultDesktopLayout)); QList *borders = &m_borderActivate; QString borderConfig = QStringLiteral("BorderActivate"); for (int i = 0; i < 2; ++i) { foreach (ElectricBorder border, *borders) { ScreenEdges::self()->unreserve(border, this); } borders->clear(); QStringList list = config.readEntry(borderConfig, QStringList()); foreach (const QString &s, list) { bool ok; const int i = s.toInt(&ok); if (!ok) continue; borders->append(ElectricBorder(i)); ScreenEdges::self()->reserve(ElectricBorder(i), this, "toggle"); } borders = &m_borderAlternativeActivate; borderConfig = QStringLiteral("BorderAlternativeActivate"); } auto touchConfig = [this, config] (const QString &key, QHash &actions, TabBoxMode mode, const QStringList &defaults = QStringList{}) { // fist erase old config for (auto it = actions.begin(); it != actions.end(); ) { delete it.value(); it = actions.erase(it); } // now new config const QStringList list = config.readEntry(key, defaults); for (const auto &s : list) { bool ok; const int i = s.toInt(&ok); if (!ok) { continue; } QAction *a = new QAction(this); connect(a, &QAction::triggered, this, std::bind(&TabBox::toggleMode, this, mode)); ScreenEdges::self()->reserveTouch(ElectricBorder(i), a); actions.insert(ElectricBorder(i), a); } }; touchConfig(QStringLiteral("TouchBorderActivate"), m_touchActivate, TabBoxWindowsMode, QStringList{QString::number(int(ElectricLeft))}); touchConfig(QStringLiteral("TouchBorderAlternativeActivate"), m_touchAlternativeActivate, TabBoxWindowsAlternativeMode); } void TabBox::loadConfig(const KConfigGroup& config, TabBoxConfig& tabBoxConfig) { tabBoxConfig.setClientDesktopMode(TabBoxConfig::ClientDesktopMode( config.readEntry("DesktopMode", TabBoxConfig::defaultDesktopMode()))); tabBoxConfig.setClientActivitiesMode(TabBoxConfig::ClientActivitiesMode( config.readEntry("ActivitiesMode", TabBoxConfig::defaultActivitiesMode()))); tabBoxConfig.setClientApplicationsMode(TabBoxConfig::ClientApplicationsMode( config.readEntry("ApplicationsMode", TabBoxConfig::defaultApplicationsMode()))); tabBoxConfig.setClientMinimizedMode(TabBoxConfig::ClientMinimizedMode( config.readEntry("MinimizedMode", TabBoxConfig::defaultMinimizedMode()))); tabBoxConfig.setShowDesktopMode(TabBoxConfig::ShowDesktopMode( config.readEntry("ShowDesktopMode", TabBoxConfig::defaultShowDesktopMode()))); tabBoxConfig.setClientMultiScreenMode(TabBoxConfig::ClientMultiScreenMode( config.readEntry("MultiScreenMode", TabBoxConfig::defaultMultiScreenMode()))); tabBoxConfig.setClientSwitchingMode(TabBoxConfig::ClientSwitchingMode( config.readEntry("SwitchingMode", TabBoxConfig::defaultSwitchingMode()))); tabBoxConfig.setShowTabBox(config.readEntry("ShowTabBox", TabBoxConfig::defaultShowTabBox())); tabBoxConfig.setHighlightWindows(config.readEntry("HighlightWindows", TabBoxConfig::defaultHighlightWindow())); tabBoxConfig.setLayoutName(config.readEntry("LayoutName", TabBoxConfig::defaultLayoutName())); } void TabBox::delayedShow() { if (isDisplayed() || m_delayedShowTimer.isActive()) // already called show - no need to call it twice return; if (!m_delayShowTime) { show(); return; } m_delayedShowTimer.setSingleShot(true); m_delayedShowTimer.start(m_delayShowTime); } bool TabBox::handleMouseEvent(QMouseEvent *event) { if (!m_isShown && isDisplayed()) { // tabbox has been replaced, check effects if (effects && static_cast(effects)->checkInputWindowEvent(event)) { return true; } } switch (event->type()) { case QEvent::MouseMove: if (!m_tabBox->containsPos(event->globalPos())) { // filter out all events which are not on the TabBox window. // We don't want windows to react on the mouse events return true; } return false; case QEvent::MouseButtonPress: if ((!m_isShown && isDisplayed()) || !m_tabBox->containsPos(event->globalPos())) { close(); // click outside closes tab return true; } // fall through case QEvent::MouseButtonRelease: default: // we do not filter it out, the intenal filter takes care return false; } return false; } bool TabBox::handleWheelEvent(QWheelEvent *event) { if (!m_isShown && isDisplayed()) { // tabbox has been replaced, check effects if (effects && static_cast(effects)->checkInputWindowEvent(event)) { return true; } } if (event->angleDelta().y() == 0) { return false; } const QModelIndex index = m_tabBox->nextPrev(event->angleDelta().y() > 0); if (index.isValid()) { setCurrentIndex(index); } return true; } void TabBox::grabbedKeyEvent(QKeyEvent* event) { emit tabBoxKeyEvent(event); if (!m_isShown && isDisplayed()) { // tabbox has been replaced, check effects return; } if (m_noModifierGrab) { if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return || event->key() == Qt::Key_Space) { accept(); return; } } m_tabBox->grabbedKeyEvent(event); } struct KeySymbolsDeleter { static inline void cleanup(xcb_key_symbols_t *symbols) { xcb_key_symbols_free(symbols); } }; /** * Handles alt-tab / control-tab */ static bool areKeySymXsDepressed(const uint keySyms[], int nKeySyms) { Xcb::QueryKeymap keys; QScopedPointer symbols(xcb_key_symbols_alloc(connection())); if (symbols.isNull() || !keys) { return false; } const auto keymap = keys->keys; bool depressed = false; for (int iKeySym = 0; iKeySym < nKeySyms; iKeySym++) { uint keySymX = keySyms[ iKeySym ]; xcb_keycode_t *keyCodes = xcb_key_symbols_get_keycode(symbols.data(), keySymX); if (!keyCodes) { continue; } int j = 0; while (keyCodes[j] != XCB_NO_SYMBOL) { const xcb_keycode_t keyCodeX = keyCodes[j++]; int i = keyCodeX / 8; char mask = 1 << (keyCodeX - (i * 8)); if (i < 0 || i >= 32) { continue; } qCDebug(KWIN_TABBOX) << iKeySym << ": keySymX=0x" << QString::number(keySymX, 16) << " i=" << i << " mask=0x" << QString::number(mask, 16) << " keymap[i]=0x" << QString::number(keymap[i], 16); if (keymap[i] & mask) { depressed = true; break; } } free(keyCodes); } return depressed; } static bool areModKeysDepressedX11(const QKeySequence &seq) { uint rgKeySyms[10]; int nKeySyms = 0; int mod = seq[seq.count()-1] & Qt::KeyboardModifierMask; if (mod & Qt::SHIFT) { rgKeySyms[nKeySyms++] = XK_Shift_L; rgKeySyms[nKeySyms++] = XK_Shift_R; } if (mod & Qt::CTRL) { rgKeySyms[nKeySyms++] = XK_Control_L; rgKeySyms[nKeySyms++] = XK_Control_R; } if (mod & Qt::ALT) { rgKeySyms[nKeySyms++] = XK_Alt_L; rgKeySyms[nKeySyms++] = XK_Alt_R; } if (mod & Qt::META) { // It would take some code to determine whether the Win key // is associated with Super or Meta, so check for both. // See bug #140023 for details. rgKeySyms[nKeySyms++] = XK_Super_L; rgKeySyms[nKeySyms++] = XK_Super_R; rgKeySyms[nKeySyms++] = XK_Meta_L; rgKeySyms[nKeySyms++] = XK_Meta_R; } return areKeySymXsDepressed(rgKeySyms, nKeySyms); } static bool areModKeysDepressedWayland(const QKeySequence &seq) { const int mod = seq[seq.count()-1] & Qt::KeyboardModifierMask; const Qt::KeyboardModifiers mods = input()->modifiersRelevantForGlobalShortcuts(); if ((mod & Qt::SHIFT) && mods.testFlag(Qt::ShiftModifier)) { return true; } if ((mod & Qt::CTRL) && mods.testFlag(Qt::ControlModifier)) { return true; } if ((mod & Qt::ALT) && mods.testFlag(Qt::AltModifier)) { return true; } if ((mod & Qt::META) && mods.testFlag(Qt::MetaModifier)) { return true; } return false; } static bool areModKeysDepressed(const QKeySequence& seq) { if (seq.isEmpty()) return false; if (kwinApp()->shouldUseWaylandForCompositing()) { return areModKeysDepressedWayland(seq); } else { return areModKeysDepressedX11(seq); } } void TabBox::navigatingThroughWindows(bool forward, const QKeySequence &shortcut, TabBoxMode mode) { if (!m_ready || isGrabbed() || !Workspace::self()->isOnCurrentHead()) { return; } if (!options->focusPolicyIsReasonable()) { //ungrabXKeyboard(); // need that because of accelerator raw mode // CDE style raise / lower CDEWalkThroughWindows(forward); } else { if (areModKeysDepressed(shortcut)) { if (startKDEWalkThroughWindows(mode)) KDEWalkThroughWindows(forward); } else // if the shortcut has no modifiers, don't show the tabbox, // don't grab, but simply go to the next window KDEOneStepThroughWindows(forward, mode); } } void TabBox::slotWalkThroughWindows() { navigatingThroughWindows(true, m_cutWalkThroughWindows, TabBoxWindowsMode); } void TabBox::slotWalkBackThroughWindows() { navigatingThroughWindows(false, m_cutWalkThroughWindowsReverse, TabBoxWindowsMode); } void TabBox::slotWalkThroughWindowsAlternative() { navigatingThroughWindows(true, m_cutWalkThroughWindowsAlternative, TabBoxWindowsAlternativeMode); } void TabBox::slotWalkBackThroughWindowsAlternative() { navigatingThroughWindows(false, m_cutWalkThroughWindowsAlternativeReverse, TabBoxWindowsAlternativeMode); } void TabBox::slotWalkThroughCurrentAppWindows() { navigatingThroughWindows(true, m_cutWalkThroughCurrentAppWindows, TabBoxCurrentAppWindowsMode); } void TabBox::slotWalkBackThroughCurrentAppWindows() { navigatingThroughWindows(false, m_cutWalkThroughCurrentAppWindowsReverse, TabBoxCurrentAppWindowsMode); } void TabBox::slotWalkThroughCurrentAppWindowsAlternative() { navigatingThroughWindows(true, m_cutWalkThroughCurrentAppWindowsAlternative, TabBoxCurrentAppWindowsAlternativeMode); } void TabBox::slotWalkBackThroughCurrentAppWindowsAlternative() { navigatingThroughWindows(false, m_cutWalkThroughCurrentAppWindowsAlternativeReverse, TabBoxCurrentAppWindowsAlternativeMode); } void TabBox::slotWalkThroughDesktops() { if (!m_ready || isGrabbed() || !Workspace::self()->isOnCurrentHead()) { return; } if (areModKeysDepressed(m_cutWalkThroughDesktops)) { if (startWalkThroughDesktops()) walkThroughDesktops(true); } else { oneStepThroughDesktops(true); } } void TabBox::slotWalkBackThroughDesktops() { if (!m_ready || isGrabbed() || !Workspace::self()->isOnCurrentHead()) { return; } if (areModKeysDepressed(m_cutWalkThroughDesktopsReverse)) { if (startWalkThroughDesktops()) walkThroughDesktops(false); } else { oneStepThroughDesktops(false); } } void TabBox::slotWalkThroughDesktopList() { if (!m_ready || isGrabbed() || !Workspace::self()->isOnCurrentHead()) { return; } if (areModKeysDepressed(m_cutWalkThroughDesktopList)) { if (startWalkThroughDesktopList()) walkThroughDesktops(true); } else { oneStepThroughDesktopList(true); } } void TabBox::slotWalkBackThroughDesktopList() { if (!m_ready || isGrabbed() || !Workspace::self()->isOnCurrentHead()) { return; } if (areModKeysDepressed(m_cutWalkThroughDesktopListReverse)) { if (startWalkThroughDesktopList()) walkThroughDesktops(false); } else { oneStepThroughDesktopList(false); } } void TabBox::shadeActivate(AbstractClient *c) { if ((c->shadeMode() == ShadeNormal || c->shadeMode() == ShadeHover) && options->isShadeHover()) c->setShade(ShadeActivated); } bool TabBox::toggle(ElectricBorder eb) { if (m_borderAlternativeActivate.contains(eb)) { return toggleMode(TabBoxWindowsAlternativeMode); } else { return toggleMode(TabBoxWindowsMode); } } bool TabBox::toggleMode(TabBoxMode mode) { if (!options->focusPolicyIsReasonable()) return false; // not supported. if (isDisplayed()) { accept(); return true; } if (!establishTabBoxGrab()) return false; m_noModifierGrab = m_tabGrab = true; setMode(mode); reset(); show(); return true; } bool TabBox::startKDEWalkThroughWindows(TabBoxMode mode) { if (!establishTabBoxGrab()) return false; m_tabGrab = true; m_noModifierGrab = false; setMode(mode); reset(); return true; } bool TabBox::startWalkThroughDesktops(TabBoxMode mode) { if (!establishTabBoxGrab()) return false; m_desktopGrab = true; m_noModifierGrab = false; setMode(mode); reset(); return true; } bool TabBox::startWalkThroughDesktops() { return startWalkThroughDesktops(TabBoxDesktopMode); } bool TabBox::startWalkThroughDesktopList() { return startWalkThroughDesktops(TabBoxDesktopListMode); } void TabBox::KDEWalkThroughWindows(bool forward) { nextPrev(forward); delayedShow(); } void TabBox::walkThroughDesktops(bool forward) { nextPrev(forward); delayedShow(); } void TabBox::CDEWalkThroughWindows(bool forward) { AbstractClient* c = nullptr; // this function find the first suitable client for unreasonable focus // policies - the topmost one, with some exceptions (can't be keepabove/below, // otherwise it gets stuck on them) // Q_ASSERT(Workspace::self()->block_stacking_updates == 0); for (int i = Workspace::self()->stackingOrder().size() - 1; i >= 0 ; --i) { auto it = qobject_cast(Workspace::self()->stackingOrder().at(i)); if (it && it->isOnCurrentActivity() && it->isOnCurrentDesktop() && !it->isSpecialWindow() && it->isShown(false) && it->wantsTabFocus() && !it->keepAbove() && !it->keepBelow()) { c = it; break; } } AbstractClient* nc = c; bool options_traverse_all; { KConfigGroup group(kwinApp()->config(), "TabBox"); options_traverse_all = group.readEntry("TraverseAll", false); } AbstractClient* firstClient = nullptr; do { nc = forward ? nextClientStatic(nc) : previousClientStatic(nc); if (!firstClient) { // When we see our first client for the second time, // it's time to stop. firstClient = nc; } else if (nc == firstClient) { // No candidates found. nc = nullptr; break; } } while (nc && nc != c && ((!options_traverse_all && !nc->isOnDesktop(currentDesktop())) || nc->isMinimized() || !nc->wantsTabFocus() || nc->keepAbove() || nc->keepBelow() || !nc->isOnCurrentActivity())); if (nc) { if (c && c != nc) Workspace::self()->lowerClient(c); if (options->focusPolicyIsReasonable()) { Workspace::self()->activateClient(nc); shadeActivate(nc); } else { if (!nc->isOnDesktop(currentDesktop())) setCurrentDesktop(nc->desktop()); Workspace::self()->raiseClient(nc); } } } void TabBox::KDEOneStepThroughWindows(bool forward, TabBoxMode mode) { setMode(mode); reset(); nextPrev(forward); if (AbstractClient* c = currentClient()) { Workspace::self()->activateClient(c); shadeActivate(c); } } void TabBox::oneStepThroughDesktops(bool forward, TabBoxMode mode) { setMode(mode); reset(); nextPrev(forward); if (currentDesktop() != -1) setCurrentDesktop(currentDesktop()); } void TabBox::oneStepThroughDesktops(bool forward) { oneStepThroughDesktops(forward, TabBoxDesktopMode); } void TabBox::oneStepThroughDesktopList(bool forward) { oneStepThroughDesktops(forward, TabBoxDesktopListMode); } void TabBox::keyPress(int keyQt) { enum Direction { Backward = -1, Steady = 0, Forward = 1 }; Direction direction(Steady); auto contains = [](const QKeySequence &shortcut, int key) -> bool { for (int i = 0; i < shortcut.count(); ++i) { if (shortcut[i] == key) { return true; } } return false; }; // tests whether a shortcut matches and handles pitfalls on ShiftKey invocation auto directionFor = [keyQt, contains](const QKeySequence &forward, const QKeySequence &backward) -> Direction { if (contains(forward, keyQt)) return Forward; if (contains(backward, keyQt)) return Backward; if (!(keyQt & Qt::ShiftModifier)) return Steady; // Before testing the unshifted key (Ctrl+A vs. Ctrl+Shift+a etc.), see whether this is +Shift+Tab // and check that against +Shift+Backtab (as well) Qt::KeyboardModifiers mods = Qt::ShiftModifier|Qt::ControlModifier|Qt::AltModifier|Qt::MetaModifier|Qt::KeypadModifier|Qt::GroupSwitchModifier; mods &= keyQt; if ((keyQt & ~mods) == Qt::Key_Tab) { if (contains(forward, mods | Qt::Key_Backtab)) return Forward; if (contains(backward, mods | Qt::Key_Backtab)) return Backward; } // if the shortcuts do not match, try matching again after filtering the shift key from keyQt // it is needed to handle correctly the ALT+~ shorcut for example as it is coded as ALT+SHIFT+~ in keyQt if (contains(forward, keyQt & ~Qt::ShiftModifier)) return Forward; if (contains(backward, keyQt & ~Qt::ShiftModifier)) return Backward; return Steady; }; if (m_tabGrab) { static const int ModeCount = 4; static const TabBoxMode modes[ModeCount] = { TabBoxWindowsMode, TabBoxWindowsAlternativeMode, TabBoxCurrentAppWindowsMode, TabBoxCurrentAppWindowsAlternativeMode }; static const QKeySequence cuts[2*ModeCount] = { // forward m_cutWalkThroughWindows, m_cutWalkThroughWindowsAlternative, m_cutWalkThroughCurrentAppWindows, m_cutWalkThroughCurrentAppWindowsAlternative, // backward m_cutWalkThroughWindowsReverse, m_cutWalkThroughWindowsAlternativeReverse, m_cutWalkThroughCurrentAppWindowsReverse, m_cutWalkThroughCurrentAppWindowsAlternativeReverse }; bool testedCurrent = false; // in case of collision, prefer to stay in the current mode int i = 0, j = 0; while (true) { if (!testedCurrent && modes[i] != mode()) { ++j; i = (i+1) % ModeCount; continue; } if (testedCurrent && modes[i] == mode()) { break; } testedCurrent = true; direction = directionFor(cuts[i], cuts[i+ModeCount]); if (direction != Steady) { if (modes[i] != mode()) { accept(false); setMode(modes[i]); auto replayWithChangedTabboxMode = [this, direction]() { reset(); nextPrev(direction == Forward); }; QTimer::singleShot(50, this, replayWithChangedTabboxMode); } break; } else if (++j > 2*ModeCount) { // guarding counter for invalid modes qCDebug(KWIN_TABBOX) << "Invalid TabBoxMode"; return; } i = (i+1) % ModeCount; } if (direction != Steady) { qCDebug(KWIN_TABBOX) << "== " << cuts[i].toString() << " or " << cuts[i+ModeCount].toString(); KDEWalkThroughWindows(direction == Forward); } } else if (m_desktopGrab) { direction = directionFor(m_cutWalkThroughDesktops, m_cutWalkThroughDesktopsReverse); if (direction == Steady) direction = directionFor(m_cutWalkThroughDesktopList, m_cutWalkThroughDesktopListReverse); if (direction != Steady) walkThroughDesktops(direction == Forward); } if (m_desktopGrab || m_tabGrab) { if (((keyQt & ~Qt::KeyboardModifierMask) == Qt::Key_Escape) && direction == Steady) { // if Escape is part of the shortcut, don't cancel close(true); } else if (direction == Steady) { QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, keyQt & ~Qt::KeyboardModifierMask, Qt::NoModifier); grabbedKeyEvent(event); } } } void TabBox::close(bool abort) { if (isGrabbed()) { removeTabBoxGrab(); } hide(abort); input()->pointer()->setEnableConstraints(true); m_tabGrab = false; m_desktopGrab = false; m_noModifierGrab = false; } void TabBox::accept(bool closeTabBox) { AbstractClient *c = currentClient(); if (closeTabBox) close(); if (c) { Workspace::self()->activateClient(c); shadeActivate(c); if (c->isDesktop()) Workspace::self()->setShowingDesktop(!Workspace::self()->showingDesktop()); } } void TabBox::modifiersReleased() { if (m_noModifierGrab) { return; } if (m_tabGrab) { bool old_control_grab = m_desktopGrab; accept(); m_desktopGrab = old_control_grab; } if (m_desktopGrab) { bool old_tab_grab = m_tabGrab; int desktop = currentDesktop(); close(); m_tabGrab = old_tab_grab; if (desktop != -1) { setCurrentDesktop(desktop); VirtualDesktopManager::self()->setCurrent(desktop); } } } int TabBox::nextDesktopStatic(int iDesktop) const { DesktopNext functor; return functor(iDesktop, true); } int TabBox::previousDesktopStatic(int iDesktop) const { DesktopPrevious functor; return functor(iDesktop, true); } /** * Auxiliary functions to travers all clients according to the static * order. Useful for the CDE-style Alt-tab feature. */ AbstractClient* TabBox::nextClientStatic(AbstractClient* c) const { const auto &list = Workspace::self()->allClientList(); if (!c || list.isEmpty()) return nullptr; int pos = list.indexOf(c); if (pos == -1) return list.first(); ++pos; if (pos == list.count()) return list.first(); return list.at(pos); } /** * Auxiliary functions to travers all clients according to the static * order. Useful for the CDE-style Alt-tab feature. */ AbstractClient* TabBox::previousClientStatic(AbstractClient* c) const { const auto &list = Workspace::self()->allClientList(); if (!c || list.isEmpty()) return nullptr; int pos = list.indexOf(c); if (pos == -1) return list.last(); if (pos == 0) return list.last(); --pos; return list.at(pos); } bool TabBox::establishTabBoxGrab() { if (kwinApp()->shouldUseWaylandForCompositing()) { m_forcedGlobalMouseGrab = true; return true; } updateXTime(); if (!grabXKeyboard()) return false; // Don't try to establish a global mouse grab using XGrabPointer, as that would prevent // using Alt+Tab while DND (#44972). However force passive grabs on all windows // in order to catch MouseRelease events and close the tabbox (#67416). // All clients already have passive grabs in their wrapper windows, so check only // the active client, which may not have it. Q_ASSERT(!m_forcedGlobalMouseGrab); m_forcedGlobalMouseGrab = true; if (Workspace::self()->activeClient() != nullptr) Workspace::self()->activeClient()->updateMouseGrab(); m_x11EventFilter.reset(new X11Filter); return true; } void TabBox::removeTabBoxGrab() { if (kwinApp()->shouldUseWaylandForCompositing()) { m_forcedGlobalMouseGrab = false; return; } updateXTime(); ungrabXKeyboard(); Q_ASSERT(m_forcedGlobalMouseGrab); m_forcedGlobalMouseGrab = false; if (Workspace::self()->activeClient() != nullptr) Workspace::self()->activeClient()->updateMouseGrab(); m_x11EventFilter.reset(); } } // namespace TabBox } // namespace