/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2009 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // own #include "tabboxhandler.h" #include #include #include "xcbutils.h" // tabbox #include "clientmodel.h" #include "desktopmodel.h" #include "tabboxconfig.h" #include "thumbnailitem.h" #include "scripting/scripting.h" #include "switcheritem.h" #include "tabbox_logging.h" // Qt #include #include #include #include #include #include #include #include #include #include // KDE #include #include #include #include namespace KWin { namespace TabBox { class TabBoxHandlerPrivate { public: TabBoxHandlerPrivate(TabBoxHandler *q); ~TabBoxHandlerPrivate(); /** * Updates the current highlight window state */ void updateHighlightWindows(); /** * Ends window highlighting */ void endHighlightWindows(bool abort = false); void show(); QQuickWindow *window() const; SwitcherItem *switcherItem() const; ClientModel* clientModel() const; DesktopModel* desktopModel() const; TabBoxHandler *q; // public pointer // members TabBoxConfig config; QScopedPointer m_qmlContext; QScopedPointer m_qmlComponent; QObject *m_mainItem; QMap m_clientTabBoxes; QMap m_desktopTabBoxes; ClientModel* m_clientModel; DesktopModel* m_desktopModel; QModelIndex index; /** * Indicates if the tabbox is shown. */ bool isShown; TabBoxClient *lastRaisedClient, *lastRaisedClientSucc; Xcb::Atom m_highlightWindowsAtom; private: QObject *createSwitcherItem(bool desktopMode); }; TabBoxHandlerPrivate::TabBoxHandlerPrivate(TabBoxHandler *q) : m_qmlContext() , m_qmlComponent() , m_mainItem(nullptr) , m_highlightWindowsAtom(QByteArrayLiteral("_KDE_WINDOW_HIGHLIGHT")) { this->q = q; isShown = false; lastRaisedClient = nullptr; lastRaisedClientSucc = nullptr; config = TabBoxConfig(); m_clientModel = new ClientModel(q); m_desktopModel = new DesktopModel(q); } TabBoxHandlerPrivate::~TabBoxHandlerPrivate() { for (auto it = m_clientTabBoxes.constBegin(); it != m_clientTabBoxes.constEnd(); ++it) { delete it.value(); } for (auto it = m_desktopTabBoxes.constBegin(); it != m_desktopTabBoxes.constEnd(); ++it) { delete it.value(); } } QQuickWindow *TabBoxHandlerPrivate::window() const { if (!m_mainItem) { return nullptr; } if (QQuickWindow *w = qobject_cast(m_mainItem)) { return w; } return m_mainItem->findChild(); } #ifndef KWIN_UNIT_TEST SwitcherItem *TabBoxHandlerPrivate::switcherItem() const { if (!m_mainItem) { return nullptr; } if (SwitcherItem *i = qobject_cast(m_mainItem)) { return i; } else if (QQuickWindow *w = qobject_cast(m_mainItem)) { return w->contentItem()->findChild(); } return m_mainItem->findChild(); } #endif ClientModel* TabBoxHandlerPrivate::clientModel() const { return m_clientModel; } DesktopModel* TabBoxHandlerPrivate::desktopModel() const { return m_desktopModel; } void TabBoxHandlerPrivate::updateHighlightWindows() { if (!isShown || config.tabBoxMode() != TabBoxConfig::ClientTabBox) return; TabBoxClient *currentClient = q->client(index); QWindow *w = window(); if (q->isKWinCompositing()) { if (lastRaisedClient) q->elevateClient(lastRaisedClient, w ? w->winId() : 0, false); lastRaisedClient = currentClient; if (currentClient) q->elevateClient(currentClient, w ? w->winId() : 0, true); } else { if (lastRaisedClient) { q->shadeClient(lastRaisedClient, true); if (lastRaisedClientSucc) q->restack(lastRaisedClient, lastRaisedClientSucc); // TODO lastRaisedClient->setMinimized( lastRaisedClientWasMinimized ); } lastRaisedClient = currentClient; if (lastRaisedClient) { q->shadeClient(lastRaisedClient, false); // TODO if ( (lastRaisedClientWasMinimized = lastRaisedClient->isMinimized()) ) // lastRaisedClient->setMinimized( false ); TabBoxClientList order = q->stackingOrder(); int succIdx = order.count() + 1; for (int i=0; iraiseClient(lastRaisedClient); } } xcb_window_t wId; QVector< xcb_window_t > data; if (config.isShowTabBox() && w) { wId = w->winId(); data.resize(2); data[ 1 ] = wId; } else { wId = rootWindow(); data.resize(1); } data[ 0 ] = currentClient ? currentClient->window() : 0L; xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, wId, m_highlightWindowsAtom, m_highlightWindowsAtom, 32, data.size(), data.constData()); } void TabBoxHandlerPrivate::endHighlightWindows(bool abort) { TabBoxClient *currentClient = q->client(index); if (config.isHighlightWindows() && q->isKWinCompositing()) { foreach (const QWeakPointer &clientPointer, q->stackingOrder()) { if (QSharedPointer client = clientPointer.toStrongRef()) if (client != currentClient) // to not mess up with wanted ShadeActive/ShadeHover state q->shadeClient(client.data(), true); } } QWindow *w = window(); if (currentClient) q->elevateClient(currentClient, w ? w->winId() : 0, false); if (abort && lastRaisedClient && lastRaisedClientSucc) q->restack(lastRaisedClient, lastRaisedClientSucc); lastRaisedClient = nullptr; lastRaisedClientSucc = nullptr; // highlight windows xcb_delete_property(connection(), config.isShowTabBox() && w ? w->winId() : rootWindow(), m_highlightWindowsAtom); } #ifndef KWIN_UNIT_TEST QObject *TabBoxHandlerPrivate::createSwitcherItem(bool desktopMode) { // first try look'n'feel package QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("plasma/look-and-feel/%1/contents/%2") .arg(config.layoutName()) .arg(desktopMode ? QStringLiteral("desktopswitcher/DesktopSwitcher.qml") : QStringLiteral("windowswitcher/WindowSwitcher.qml"))); if (file.isNull()) { const QString folderName = QStringLiteral(KWIN_NAME) + (desktopMode ? QStringLiteral("/desktoptabbox/") : QStringLiteral("/tabbox/")); auto findSwitcher = [this, desktopMode, folderName] { const QString type = desktopMode ? QStringLiteral("KWin/DesktopSwitcher") : QStringLiteral("KWin/WindowSwitcher"); auto offers = KPackage::PackageLoader::self()->findPackages(type, folderName, [this] (const KPluginMetaData &data) { return data.pluginId().compare(config.layoutName(), Qt::CaseInsensitive) == 0; } ); if (offers.isEmpty()) { // load default offers = KPackage::PackageLoader::self()->findPackages(type, folderName, [this] (const KPluginMetaData &data) { return data.pluginId().compare(QStringLiteral("informative"), Qt::CaseInsensitive) == 0; } ); if (offers.isEmpty()) { qCDebug(KWIN_TABBOX) << "could not find default window switcher layout"; return KPluginMetaData(); } } return offers.first(); }; auto service = findSwitcher(); if (!service.isValid()) { return nullptr; } if (service.value(QStringLiteral("X-Plasma-API")) != QStringLiteral("declarativeappletscript")) { qCDebug(KWIN_TABBOX) << "Window Switcher Layout is no declarativeappletscript"; return nullptr; } auto findScriptFile = [desktopMode, service, folderName] { const QString pluginName = service.pluginId(); const QString scriptName = service.value(QStringLiteral("X-Plasma-MainScript")); return QStandardPaths::locate(QStandardPaths::GenericDataLocation, folderName + pluginName + QStringLiteral("/contents/") + scriptName); }; file = findScriptFile(); } if (file.isNull()) { qCDebug(KWIN_TABBOX) << "Could not find QML file for window switcher"; return nullptr; } m_qmlComponent->loadUrl(QUrl::fromLocalFile(file)); if (m_qmlComponent->isError()) { qCDebug(KWIN_TABBOX) << "Component failed to load: " << m_qmlComponent->errors(); QStringList args; args << QStringLiteral("--passivepopup") << i18n("The Window Switcher installation is broken, resources are missing.\n" "Contact your distribution about this.") << QStringLiteral("20"); KProcess::startDetached(QStringLiteral("kdialog"), args); } else { QObject *object = m_qmlComponent->create(m_qmlContext.data()); if (desktopMode) { m_desktopTabBoxes.insert(config.layoutName(), object); } else { m_clientTabBoxes.insert(config.layoutName(), object); } return object; } return nullptr; } #endif void TabBoxHandlerPrivate::show() { #ifndef KWIN_UNIT_TEST if (m_qmlContext.isNull()) { qmlRegisterType("org.kde.kwin", 2, 0, "Switcher"); m_qmlContext.reset(new QQmlContext(Scripting::self()->qmlEngine())); } if (m_qmlComponent.isNull()) { m_qmlComponent.reset(new QQmlComponent(Scripting::self()->qmlEngine())); } const bool desktopMode = (config.tabBoxMode() == TabBoxConfig::DesktopTabBox); auto findMainItem = [this](const QMap &tabBoxes) -> QObject* { auto it = tabBoxes.constFind(config.layoutName()); if (it != tabBoxes.constEnd()) { return it.value(); } return nullptr; }; m_mainItem = nullptr; m_mainItem = desktopMode ? findMainItem(m_desktopTabBoxes) : findMainItem(m_clientTabBoxes); if (!m_mainItem) { m_mainItem = createSwitcherItem(desktopMode); if (!m_mainItem) { return; } } if (SwitcherItem *item = switcherItem()) { if (!item->model()) { QAbstractItemModel *model = nullptr; if (desktopMode) { model = desktopModel(); } else { model = clientModel(); } item->setModel(model); } item->setAllDesktops(config.clientDesktopMode() == TabBoxConfig::AllDesktopsClients); item->setCurrentIndex(index.row()); // everything is prepared, so let's make the whole thing visible item->setVisible(true); } #endif } /*********************************************** * TabBoxHandler ***********************************************/ TabBoxHandler::TabBoxHandler(QObject *parent) : QObject(parent) { KWin::TabBox::tabBox = this; d = new TabBoxHandlerPrivate(this); } TabBoxHandler::~TabBoxHandler() { delete d; } const KWin::TabBox::TabBoxConfig& TabBoxHandler::config() const { return d->config; } void TabBoxHandler::setConfig(const TabBoxConfig& config) { d->config = config; emit configChanged(); } void TabBoxHandler::show() { d->isShown = true; d->lastRaisedClient = nullptr; d->lastRaisedClientSucc = nullptr; if (d->config.isShowTabBox()) { d->show(); } if (d->config.isHighlightWindows()) { Xcb::sync(); // TODO this should be // QMetaObject::invokeMethod(this, "initHighlightWindows", Qt::QueuedConnection); // but we somehow need to cross > 1 event cycle (likely because of queued invocation in the effects) // to ensure the EffectWindow is present when updateHighlightWindows, thus elevating the window/tabbox QTimer::singleShot(1, this, SLOT(initHighlightWindows())); } } void TabBoxHandler::initHighlightWindows() { if (isKWinCompositing()) { foreach (const QWeakPointer &clientPointer, stackingOrder()) { if (QSharedPointer client = clientPointer.toStrongRef()) shadeClient(client.data(), false); } } d->updateHighlightWindows(); } void TabBoxHandler::hide(bool abort) { d->isShown = false; if (d->config.isHighlightWindows()) { d->endHighlightWindows(abort); } #ifndef KWIN_UNIT_TEST if (SwitcherItem *item = d->switcherItem()) { item->setVisible(false); } #endif if (QQuickWindow *w = d->window()) { w->hide(); w->destroy(); } d->m_mainItem = nullptr; } QModelIndex TabBoxHandler::nextPrev(bool forward) const { QModelIndex ret; QAbstractItemModel* model; switch(d->config.tabBoxMode()) { case TabBoxConfig::ClientTabBox: model = d->clientModel(); break; case TabBoxConfig::DesktopTabBox: model = d->desktopModel(); break; default: return d->index; } if (forward) { int column = d->index.column() + 1; int row = d->index.row(); if (column == model->columnCount()) { column = 0; row++; if (row == model->rowCount()) row = 0; } ret = model->index(row, column); if (!ret.isValid()) ret = model->index(0, 0); } else { int column = d->index.column() - 1; int row = d->index.row(); if (column < 0) { column = model->columnCount() - 1; row--; if (row < 0) row = model->rowCount() - 1; } ret = model->index(row, column); if (!ret.isValid()) { row = model->rowCount() - 1; for (int i = model->columnCount() - 1; i >= 0; i--) { ret = model->index(row, i); if (ret.isValid()) break; } } } if (ret.isValid()) return ret; else return d->index; } QModelIndex TabBoxHandler::desktopIndex(int desktop) const { if (d->config.tabBoxMode() != TabBoxConfig::DesktopTabBox) return QModelIndex(); return d->desktopModel()->desktopIndex(desktop); } QList< int > TabBoxHandler::desktopList() const { if (d->config.tabBoxMode() != TabBoxConfig::DesktopTabBox) return QList< int >(); return d->desktopModel()->desktopList(); } int TabBoxHandler::desktop(const QModelIndex& index) const { if (!index.isValid() || (d->config.tabBoxMode() != TabBoxConfig::DesktopTabBox)) return -1; QVariant ret = d->desktopModel()->data(index, DesktopModel::DesktopRole); if (ret.isValid()) return ret.toInt(); else return -1; } void TabBoxHandler::setCurrentIndex(const QModelIndex& index) { if (d->index == index) { return; } if (!index.isValid()) { return; } d->index = index; if (d->config.tabBoxMode() == TabBoxConfig::ClientTabBox) { if (d->config.isHighlightWindows()) { d->updateHighlightWindows(); } } emit selectedIndexChanged(); } const QModelIndex& TabBoxHandler::currentIndex() const { return d->index; } void TabBoxHandler::grabbedKeyEvent(QKeyEvent* event) const { if (!d->m_mainItem || !d->window()) { return; } const QList items = d->window()->contentItem()->findChildren(QString(), Qt::FindDirectChildrenOnly); for (QQuickItem *item : items) { d->window()->sendEvent(item, event); if (event->isAccepted()) { break; } } } bool TabBoxHandler::containsPos(const QPoint& pos) const { if (!d->m_mainItem) { return false; } QWindow *w = d->window(); if (w) { return w->geometry().contains(pos); } return false; } QModelIndex TabBoxHandler::index(QWeakPointer client) const { return d->clientModel()->index(client); } TabBoxClientList TabBoxHandler::clientList() const { if (d->config.tabBoxMode() != TabBoxConfig::ClientTabBox) return TabBoxClientList(); return d->clientModel()->clientList(); } TabBoxClient* TabBoxHandler::client(const QModelIndex& index) const { if ((!index.isValid()) || (d->config.tabBoxMode() != TabBoxConfig::ClientTabBox)) return nullptr; TabBoxClient* c = static_cast< TabBoxClient* >( d->clientModel()->data(index, ClientModel::ClientRole).value()); return c; } void TabBoxHandler::createModel(bool partialReset) { switch(d->config.tabBoxMode()) { case TabBoxConfig::ClientTabBox: { d->clientModel()->createClientList(partialReset); // TODO: C++11 use lambda function bool lastRaised = false; bool lastRaisedSucc = false; foreach (const QWeakPointer &clientPointer, stackingOrder()) { QSharedPointer client = clientPointer.toStrongRef(); if (!client) { continue; } if (client.data() == d->lastRaisedClient) { lastRaised = true; } if (client.data() == d->lastRaisedClientSucc) { lastRaisedSucc = true; } } if (d->lastRaisedClient && !lastRaised) d->lastRaisedClient = nullptr; if (d->lastRaisedClientSucc && !lastRaisedSucc) d->lastRaisedClientSucc = nullptr; break; } case TabBoxConfig::DesktopTabBox: d->desktopModel()->createDesktopList(); break; } } QModelIndex TabBoxHandler::first() const { QAbstractItemModel* model; switch(d->config.tabBoxMode()) { case TabBoxConfig::ClientTabBox: model = d->clientModel(); break; case TabBoxConfig::DesktopTabBox: model = d->desktopModel(); break; default: return QModelIndex(); } return model->index(0, 0); } TabBoxHandler* tabBox = nullptr; TabBoxClient::TabBoxClient() { } TabBoxClient::~TabBoxClient() { } } // namespace TabBox } // namespace KWin