kwin/tabbox/tabboxhandler.cpp

654 lines
20 KiB
C++
Raw Normal View History

/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2009 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
// own
#include "tabboxhandler.h"
#include <config-kwin.h>
#include <kwinglobals.h>
#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 <QKeyEvent>
#include <QModelIndex>
#include <QStandardPaths>
#include <QTimer>
#include <QQmlContext>
#include <QQmlComponent>
#include <QQmlEngine>
#include <QQuickItem>
#include <QQuickWindow>
#include <X11/Xlib.h>
// KDE
#include <KLocalizedString>
#include <KProcess>
#include <KPackage/Package>
#include <KPackage/PackageLoader>
namespace KWin
{
namespace TabBox
{
class TabBoxHandlerPrivate
2011-01-30 14:34:42 +00:00
{
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;
2011-01-30 14:34:42 +00:00
ClientModel* clientModel() const;
DesktopModel* desktopModel() const;
TabBoxHandler *q; // public pointer
// members
TabBoxConfig config;
QScopedPointer<QQmlContext> m_qmlContext;
QScopedPointer<QQmlComponent> m_qmlComponent;
QObject *m_mainItem;
QMap<QString, QObject*> m_clientTabBoxes;
QMap<QString, QObject*> m_desktopTabBoxes;
ClientModel* m_clientModel;
DesktopModel* m_desktopModel;
2011-01-30 14:34:42 +00:00
QModelIndex index;
/**
* Indicates if the tabbox is shown.
*/
bool isShown;
TabBoxClient *lastRaisedClient, *lastRaisedClientSucc;
[tabbox] Intercept QWheelEvents on QQuickWindow for scrolling Summary: The TabBox performs the scrolling of the items by itself in order to support wheel events even if the mouse is not on the TabBox. For that KWin grabs pointer events on X11 (on Wayland an input filter is used) and forwards them to the TabBox. Qt uses Xinput2 for scrolling on the QQuickWindow. Due to that KWin does not get any xcb core button press/release events when scrolling inside the QQuickWindow and thus scrolling doesn't work. There are three possible approaches to fix this: 1. Implement scrolling support in each of the QML switchers 2. Add an xinput2 filter to TabBox 3. Intercept the QWheelEvents on the QQuickWindow The first approach has the disadvantage that all themes need adjustment and that there might be behaviorial difference whether one scrolls on the TabBox window or outside the window. The second approach would be most in line with the other filters, but is difficult due to the nature of xinput2 (no xcb bindings, etc). Thus the third approach might be the best solution. Wheel events are only delivered to the QQuickWindow if the native events were not already intercepted, thus we know it won't have side effects for the case that Wayland is used or xinput2 is not supported. The implementation installs an event filter on the QQuickWindow which gets created when showing the TabBox and inside the filter waits till there is an angleDelta of +/-120 and scrolls by one per every 120 angle delta as described in the QWheelEvent documentation. BUG: 369661 FIXED-IN: 5.8.2 Test Plan: Scrolled with touchpad and mouse wheel. Reviewers: #kwin, #plasma, broulik Subscribers: plasma-devel, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D2953
2016-10-06 06:06:25 +00:00
int wheelAngleDelta = 0;
private:
QObject *createSwitcherItem(bool desktopMode);
2011-01-30 14:34:42 +00:00
};
TabBoxHandlerPrivate::TabBoxHandlerPrivate(TabBoxHandler *q)
: m_qmlContext()
, m_qmlComponent()
, m_mainItem(nullptr)
2011-01-30 14:34:42 +00:00
{
this->q = q;
isShown = false;
lastRaisedClient = nullptr;
lastRaisedClientSucc = nullptr;
config = TabBoxConfig();
m_clientModel = new ClientModel(q);
m_desktopModel = new DesktopModel(q);
2011-01-30 14:34:42 +00:00
}
TabBoxHandlerPrivate::~TabBoxHandlerPrivate()
2011-01-30 14:34:42 +00:00
{
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<QQuickWindow*>(m_mainItem)) {
return w;
}
return m_mainItem->findChild<QQuickWindow*>();
}
#ifndef KWIN_UNIT_TEST
SwitcherItem *TabBoxHandlerPrivate::switcherItem() const
{
if (!m_mainItem) {
return nullptr;
}
if (SwitcherItem *i = qobject_cast<SwitcherItem*>(m_mainItem)) {
return i;
} else if (QQuickWindow *w = qobject_cast<QQuickWindow*>(m_mainItem)) {
return w->contentItem()->findChild<SwitcherItem*>();
}
return m_mainItem->findChild<SwitcherItem*>();
2011-01-30 14:34:42 +00:00
}
#endif
ClientModel* TabBoxHandlerPrivate::clientModel() const
2011-01-30 14:34:42 +00:00
{
return m_clientModel;
2011-01-30 14:34:42 +00:00
}
DesktopModel* TabBoxHandlerPrivate::desktopModel() const
2011-01-30 14:34:42 +00:00
{
return m_desktopModel;
2011-01-30 14:34:42 +00:00
}
void TabBoxHandlerPrivate::updateHighlightWindows()
2011-01-30 14:34:42 +00:00
{
if (!isShown || config.tabBoxMode() != TabBoxConfig::ClientTabBox)
return;
2011-01-30 14:34:42 +00:00
TabBoxClient *currentClient = q->client(index);
QWindow *w = window();
2011-01-30 14:34:42 +00:00
if (q->isKWinCompositing()) {
if (lastRaisedClient)
q->elevateClient(lastRaisedClient, w, false);
lastRaisedClient = currentClient;
if (currentClient)
q->elevateClient(currentClient, w, true);
} else {
2011-01-30 14:34:42 +00:00
if (lastRaisedClient) {
q->shadeClient(lastRaisedClient, true);
2011-01-30 14:34:42 +00:00
if (lastRaisedClientSucc)
q->restack(lastRaisedClient, lastRaisedClientSucc);
// TODO lastRaisedClient->setMinimized( lastRaisedClientWasMinimized );
}
lastRaisedClient = currentClient;
2011-01-30 14:34:42 +00:00
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; i<order.count(); ++i) {
if (order.at(i).data() == lastRaisedClient) {
succIdx = i + 1;
break;
}
}
lastRaisedClientSucc = (succIdx < order.count()) ? order.at(succIdx).data() : nullptr;
2011-01-30 14:34:42 +00:00
q->raiseClient(lastRaisedClient);
}
2011-01-30 14:34:42 +00:00
}
if (config.isShowTabBox() && w) {
q->highlightWindows(currentClient, w);
2011-01-30 14:34:42 +00:00
} else {
q->highlightWindows(currentClient);
}
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
void TabBoxHandlerPrivate::endHighlightWindows(bool abort)
{
TabBoxClient *currentClient = q->client(index);
if (config.isHighlightWindows() && q->isKWinCompositing()) {
foreach (const QWeakPointer<TabBoxClient> &clientPointer, q->stackingOrder()) {
if (QSharedPointer<TabBoxClient> 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, false);
2011-01-30 14:34:42 +00:00
if (abort && lastRaisedClient && lastRaisedClientSucc)
q->restack(lastRaisedClient, lastRaisedClientSucc);
lastRaisedClient = nullptr;
lastRaisedClientSucc = nullptr;
// highlight windows
q->highlightWindows();
}
#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 = QLatin1String(KWIN_NAME) + (desktopMode ? QLatin1String("/desktoptabbox/") : QLatin1String("/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")) != QLatin1String("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 + QLatin1String("/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<SwitcherItem>("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<QString, QObject *> &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);
}
[tabbox] Intercept QWheelEvents on QQuickWindow for scrolling Summary: The TabBox performs the scrolling of the items by itself in order to support wheel events even if the mouse is not on the TabBox. For that KWin grabs pointer events on X11 (on Wayland an input filter is used) and forwards them to the TabBox. Qt uses Xinput2 for scrolling on the QQuickWindow. Due to that KWin does not get any xcb core button press/release events when scrolling inside the QQuickWindow and thus scrolling doesn't work. There are three possible approaches to fix this: 1. Implement scrolling support in each of the QML switchers 2. Add an xinput2 filter to TabBox 3. Intercept the QWheelEvents on the QQuickWindow The first approach has the disadvantage that all themes need adjustment and that there might be behaviorial difference whether one scrolls on the TabBox window or outside the window. The second approach would be most in line with the other filters, but is difficult due to the nature of xinput2 (no xcb bindings, etc). Thus the third approach might be the best solution. Wheel events are only delivered to the QQuickWindow if the native events were not already intercepted, thus we know it won't have side effects for the case that Wayland is used or xinput2 is not supported. The implementation installs an event filter on the QQuickWindow which gets created when showing the TabBox and inside the filter waits till there is an angleDelta of +/-120 and scrolls by one per every 120 angle delta as described in the QWheelEvent documentation. BUG: 369661 FIXED-IN: 5.8.2 Test Plan: Scrolled with touchpad and mouse wheel. Reviewers: #kwin, #plasma, broulik Subscribers: plasma-devel, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D2953
2016-10-06 06:06:25 +00:00
if (QWindow *w = window()) {
wheelAngleDelta = 0;
w->installEventFilter(q);
}
#endif
2011-01-30 14:34:42 +00:00
}
/***********************************************
* TabBoxHandler
***********************************************/
TabBoxHandler::TabBoxHandler(QObject *parent)
: QObject(parent)
2011-01-30 14:34:42 +00:00
{
KWin::TabBox::tabBox = this;
2011-01-30 14:34:42 +00:00
d = new TabBoxHandlerPrivate(this);
}
TabBoxHandler::~TabBoxHandler()
2011-01-30 14:34:42 +00:00
{
delete d;
2011-01-30 14:34:42 +00:00
}
const KWin::TabBox::TabBoxConfig& TabBoxHandler::config() const
2011-01-30 14:34:42 +00:00
{
return d->config;
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
void TabBoxHandler::setConfig(const TabBoxConfig& config)
{
d->config = config;
emit configChanged();
2011-01-30 14:34:42 +00:00
}
void TabBoxHandler::show()
2011-01-30 14:34:42 +00:00
{
d->isShown = true;
d->lastRaisedClient = nullptr;
d->lastRaisedClientSucc = nullptr;
2011-01-30 14:34:42 +00:00
if (d->config.isShowTabBox()) {
d->show();
2011-01-30 14:34:42 +00:00
}
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()));
}
2011-01-30 14:34:42 +00:00
}
void TabBoxHandler::initHighlightWindows()
{
if (isKWinCompositing()) {
foreach (const QWeakPointer<TabBoxClient> &clientPointer, stackingOrder()) {
if (QSharedPointer<TabBoxClient> client = clientPointer.toStrongRef())
shadeClient(client.data(), false);
}
}
d->updateHighlightWindows();
}
2011-01-30 14:34:42 +00:00
void TabBoxHandler::hide(bool abort)
{
d->isShown = false;
2011-01-30 14:34:42 +00:00
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;
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
QModelIndex TabBoxHandler::nextPrev(bool forward) const
{
QModelIndex ret;
QAbstractItemModel* model;
2011-01-30 14:34:42 +00:00
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();
2011-01-30 14:34:42 +00:00
if (column == model->columnCount()) {
column = 0;
row++;
2011-01-30 14:34:42 +00:00
if (row == model->rowCount())
row = 0;
}
2011-01-30 14:34:42 +00:00
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();
2011-01-30 14:34:42 +00:00
if (column < 0) {
column = model->columnCount() - 1;
row--;
2011-01-30 14:34:42 +00:00
if (row < 0)
row = model->rowCount() - 1;
2011-01-30 14:34:42 +00:00
}
ret = model->index(row, column);
if (!ret.isValid()) {
row = model->rowCount() - 1;
2011-01-30 14:34:42 +00:00
for (int i = model->columnCount() - 1; i >= 0; i--) {
ret = model->index(row, i);
if (ret.isValid())
break;
}
}
2011-01-30 14:34:42 +00:00
}
if (ret.isValid())
return ret;
else
return d->index;
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
QModelIndex TabBoxHandler::desktopIndex(int desktop) const
{
if (d->config.tabBoxMode() != TabBoxConfig::DesktopTabBox)
return QModelIndex();
2011-01-30 14:34:42 +00:00
return d->desktopModel()->desktopIndex(desktop);
}
QList< int > TabBoxHandler::desktopList() const
2011-01-30 14:34:42 +00:00
{
if (d->config.tabBoxMode() != TabBoxConfig::DesktopTabBox)
return QList< int >();
return d->desktopModel()->desktopList();
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
int TabBoxHandler::desktop(const QModelIndex& index) const
{
if (!index.isValid() || (d->config.tabBoxMode() != TabBoxConfig::DesktopTabBox))
return -1;
2011-01-30 14:34:42 +00:00
QVariant ret = d->desktopModel()->data(index, DesktopModel::DesktopRole);
if (ret.isValid())
return ret.toInt();
else
return -1;
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
void TabBoxHandler::setCurrentIndex(const QModelIndex& index)
{
if (d->index == index) {
return;
}
if (!index.isValid()) {
return;
}
d->index = index;
2011-01-30 14:34:42 +00:00
if (d->config.tabBoxMode() == TabBoxConfig::ClientTabBox) {
if (d->config.isHighlightWindows()) {
d->updateHighlightWindows();
}
}
emit selectedIndexChanged();
2011-01-30 14:34:42 +00:00
}
const QModelIndex& TabBoxHandler::currentIndex() const
{
return d->index;
}
void TabBoxHandler::grabbedKeyEvent(QKeyEvent* event) const
2011-01-30 14:34:42 +00:00
{
if (!d->m_mainItem || !d->window()) {
return;
2011-01-30 14:34:42 +00:00
}
const QList<QQuickItem*> items = d->window()->contentItem()->findChildren<QQuickItem*>(QString(), Qt::FindDirectChildrenOnly);
for (QQuickItem *item : items) {
d->window()->sendEvent(item, event);
if (event->isAccepted()) {
break;
}
}
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
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;
2011-01-30 14:34:42 +00:00
}
QModelIndex TabBoxHandler::index(QWeakPointer<KWin::TabBox::TabBoxClient> client) const
2011-01-30 14:34:42 +00:00
{
return d->clientModel()->index(client);
}
TabBoxClientList TabBoxHandler::clientList() const
2011-01-30 14:34:42 +00:00
{
if (d->config.tabBoxMode() != TabBoxConfig::ClientTabBox)
return TabBoxClientList();
return d->clientModel()->clientList();
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
TabBoxClient* TabBoxHandler::client(const QModelIndex& index) const
{
if ((!index.isValid()) ||
(d->config.tabBoxMode() != TabBoxConfig::ClientTabBox))
return nullptr;
TabBoxClient* c = static_cast< TabBoxClient* >(
2011-01-30 14:34:42 +00:00
d->clientModel()->data(index, ClientModel::ClientRole).value<void *>());
return c;
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
void TabBoxHandler::createModel(bool partialReset)
{
switch(d->config.tabBoxMode()) {
case TabBoxConfig::ClientTabBox: {
2011-01-30 14:34:42 +00:00
d->clientModel()->createClientList(partialReset);
// TODO: C++11 use lambda function
bool lastRaised = false;
bool lastRaisedSucc = false;
foreach (const QWeakPointer<TabBoxClient> &clientPointer, stackingOrder()) {
QSharedPointer<TabBoxClient> 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;
2011-01-30 14:34:42 +00:00
break;
}
2011-01-30 14:34:42 +00:00
case TabBoxConfig::DesktopTabBox:
d->desktopModel()->createDesktopList();
break;
}
2011-01-30 14:34:42 +00:00
}
QModelIndex TabBoxHandler::first() const
2011-01-30 14:34:42 +00:00
{
QAbstractItemModel* model;
2011-01-30 14:34:42 +00:00
switch(d->config.tabBoxMode()) {
case TabBoxConfig::ClientTabBox:
model = d->clientModel();
break;
case TabBoxConfig::DesktopTabBox:
model = d->desktopModel();
break;
default:
return QModelIndex();
}
2011-01-30 14:34:42 +00:00
return model->index(0, 0);
}
[tabbox] Intercept QWheelEvents on QQuickWindow for scrolling Summary: The TabBox performs the scrolling of the items by itself in order to support wheel events even if the mouse is not on the TabBox. For that KWin grabs pointer events on X11 (on Wayland an input filter is used) and forwards them to the TabBox. Qt uses Xinput2 for scrolling on the QQuickWindow. Due to that KWin does not get any xcb core button press/release events when scrolling inside the QQuickWindow and thus scrolling doesn't work. There are three possible approaches to fix this: 1. Implement scrolling support in each of the QML switchers 2. Add an xinput2 filter to TabBox 3. Intercept the QWheelEvents on the QQuickWindow The first approach has the disadvantage that all themes need adjustment and that there might be behaviorial difference whether one scrolls on the TabBox window or outside the window. The second approach would be most in line with the other filters, but is difficult due to the nature of xinput2 (no xcb bindings, etc). Thus the third approach might be the best solution. Wheel events are only delivered to the QQuickWindow if the native events were not already intercepted, thus we know it won't have side effects for the case that Wayland is used or xinput2 is not supported. The implementation installs an event filter on the QQuickWindow which gets created when showing the TabBox and inside the filter waits till there is an angleDelta of +/-120 and scrolls by one per every 120 angle delta as described in the QWheelEvent documentation. BUG: 369661 FIXED-IN: 5.8.2 Test Plan: Scrolled with touchpad and mouse wheel. Reviewers: #kwin, #plasma, broulik Subscribers: plasma-devel, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D2953
2016-10-06 06:06:25 +00:00
bool TabBoxHandler::eventFilter(QObject *watched, QEvent *e)
{
if (e->type() == QEvent::Wheel && watched == d->window()) {
QWheelEvent *event = static_cast<QWheelEvent*>(e);
// On x11 the delta for vertical scrolling might also be on X for whatever reason
const int delta = qAbs(event->angleDelta().x()) > qAbs(event->angleDelta().y()) ? event->angleDelta().x() : event->angleDelta().y();
d->wheelAngleDelta += delta;
while (d->wheelAngleDelta <= -120) {
d->wheelAngleDelta += 120;
const QModelIndex index = nextPrev(true);
if (index.isValid()) {
setCurrentIndex(index);
}
}
while (d->wheelAngleDelta >= 120) {
d->wheelAngleDelta -= 120;
const QModelIndex index = nextPrev(false);
if (index.isValid()) {
setCurrentIndex(index);
}
}
return true;
}
// pass on
return QObject::eventFilter(watched, e);
}
TabBoxHandler* tabBox = nullptr;
TabBoxClient::TabBoxClient()
2011-01-30 14:34:42 +00:00
{
}
TabBoxClient::~TabBoxClient()
2011-01-30 14:34:42 +00:00
{
}
} // namespace TabBox
} // namespace KWin