KWin
Loading...
Searching...
No Matches
tabboxhandler.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: 2009 Martin Gräßlin <mgraesslin@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9#include "tabboxhandler.h"
10
11#include <config-kwin.h>
12
13// own
14#include "clientmodel.h"
15#include "scripting/scripting.h"
16#include "switcheritem.h"
17#include "tabbox_logging.h"
18#include "window.h"
19// Qt
20#include <QKeyEvent>
21#include <QQmlComponent>
22#include <QQmlContext>
23#include <QQmlEngine>
24#include <QQuickItem>
25#include <QQuickWindow>
26#include <QStandardPaths>
27#include <QTimer>
28#include <qpa/qwindowsysteminterface.h>
29// KDE
30#include <KLocalizedString>
31#include <KPackage/Package>
32#include <KPackage/PackageLoader>
33#include <KProcess>
34
35namespace KWin
36{
37namespace TabBox
38{
39
41{
42public:
44
46
54 void endHighlightWindows(bool abort = false);
55
56 void show();
57 QQuickWindow *window() const;
59
60 ClientModel *clientModel() const;
61
62 bool isHighlightWindows() const;
63
64 TabBoxHandler *q; // public pointer
65 // members
67 std::unique_ptr<QQmlContext> m_qmlContext;
68 std::unique_ptr<QQmlComponent> m_qmlComponent;
69 QObject *m_mainItem;
70 QMap<QString, QObject *> m_clientTabBoxes;
72 QModelIndex index;
76 bool isShown;
79
80private:
81 QObject *createSwitcherItem();
82};
83
85 : m_qmlContext()
86 , m_qmlComponent(nullptr)
87 , m_mainItem(nullptr)
88{
89 this->q = q;
90 isShown = false;
91 lastRaisedClient = nullptr;
92 lastRaisedClientSucc = nullptr;
95}
96
101
102QQuickWindow *TabBoxHandlerPrivate::window() const
103{
104 if (!m_mainItem) {
105 return nullptr;
106 }
107 if (QQuickWindow *w = qobject_cast<QQuickWindow *>(m_mainItem)) {
108 return w;
109 }
110 return m_mainItem->findChild<QQuickWindow *>();
111}
112
113#ifndef KWIN_UNIT_TEST
115{
116 if (!m_mainItem) {
117 return nullptr;
118 }
119 if (SwitcherItem *i = qobject_cast<SwitcherItem *>(m_mainItem)) {
120 return i;
121 } else if (QQuickWindow *w = qobject_cast<QQuickWindow *>(m_mainItem)) {
122 return w->contentItem()->findChild<SwitcherItem *>();
123 }
124 return m_mainItem->findChild<SwitcherItem *>();
125}
126#endif
127
132
134{
135 const QQuickWindow *w = window();
136 if (w && w->visibility() == QWindow::FullScreen) {
137 return false;
138 }
139 return config.isHighlightWindows();
140}
141
143{
144 if (!isShown) {
145 return;
146 }
147
148 Window *currentClient = q->client(index);
149 QWindow *w = window();
150
151 if (q->isKWinCompositing()) {
152 if (lastRaisedClient) {
153 q->elevateClient(lastRaisedClient, w, false);
154 }
155 lastRaisedClient = currentClient;
156 // don't elevate desktop
157 const auto desktop = q->desktopClient();
158 if (currentClient && (!desktop || currentClient->internalId() != desktop->internalId())) {
159 q->elevateClient(currentClient, w, true);
160 }
161 } else {
162 if (lastRaisedClient) {
166 }
167 // TODO lastRaisedClient->setMinimized( lastRaisedClientWasMinimized );
168 }
169
170 lastRaisedClient = currentClient;
171 if (lastRaisedClient) {
173 // TODO if ( (lastRaisedClientWasMinimized = lastRaisedClient->isMinimized()) )
174 // lastRaisedClient->setMinimized( false );
175 QList<Window *> order = q->stackingOrder();
176 int succIdx = order.count() + 1;
177 for (int i = 0; i < order.count(); ++i) {
178 if (order.at(i) == lastRaisedClient) {
179 succIdx = i + 1;
180 break;
181 }
182 }
183 lastRaisedClientSucc = (succIdx < order.count()) ? order.at(succIdx) : nullptr;
185 }
186 }
187
188 if (config.isShowTabBox() && w) {
189 q->highlightWindows(currentClient, w);
190 } else {
191 q->highlightWindows(currentClient);
192 }
193}
194
196{
197 Window *currentClient = q->client(index);
199 const auto stackingOrder = q->stackingOrder();
200 for (Window *window : stackingOrder) {
201 if (window != currentClient) { // to not mess up with wanted ShadeActive/ShadeHover state
202 q->shadeClient(window, true);
203 }
204 }
205 }
206 QWindow *w = window();
207 if (currentClient) {
208 q->elevateClient(currentClient, w, false);
209 }
210 if (abort && lastRaisedClient && lastRaisedClientSucc) {
212 }
213 lastRaisedClient = nullptr;
214 lastRaisedClientSucc = nullptr;
215 // highlight windows
217}
218
219#ifndef KWIN_UNIT_TEST
220QObject *TabBoxHandlerPrivate::createSwitcherItem()
221{
222 // first try look'n'feel package
223 QString file = QStandardPaths::locate(
224 QStandardPaths::GenericDataLocation,
225 QStringLiteral("plasma/look-and-feel/%1/contents/windowswitcher/WindowSwitcher.qml").arg(config.layoutName()));
226 if (file.isNull()) {
227 const QString type = QStringLiteral("KWin/WindowSwitcher");
228
229 KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(type, config.layoutName());
230
231 if (!pkg.isValid()) {
232 // load default
233 qCWarning(KWIN_TABBOX) << "Could not load window switcher package" << config.layoutName() << ". Falling back to default";
234 pkg = KPackage::PackageLoader::self()->loadPackage(type, TabBoxConfig::defaultLayoutName());
235 }
236
237 file = pkg.filePath("mainscript");
238 }
239 if (file.isNull()) {
240 qCDebug(KWIN_TABBOX) << "Could not find QML file for window switcher";
241 return nullptr;
242 }
243 m_qmlComponent->loadUrl(QUrl::fromLocalFile(file));
244 if (m_qmlComponent->isError()) {
245 qCWarning(KWIN_TABBOX) << "Component failed to load: " << m_qmlComponent->errors();
246 QStringList args;
247 args << QStringLiteral("--passivepopup") << i18n("The Window Switcher installation is broken, resources are missing.\n"
248 "Contact your distribution about this.")
249 << QStringLiteral("20");
250 KProcess::startDetached(QStringLiteral("kdialog"), args);
251 m_qmlComponent.reset(nullptr);
252 } else {
253 QObject *object = m_qmlComponent->create(m_qmlContext.get());
254 m_clientTabBoxes.insert(config.layoutName(), object);
255 return object;
256 }
257 return nullptr;
258}
259#endif
260
262{
263#ifndef KWIN_UNIT_TEST
264 if (!m_qmlContext) {
265 qmlRegisterType<SwitcherItem>("org.kde.kwin", 3, 0, "TabBoxSwitcher");
266 m_qmlContext = std::make_unique<QQmlContext>(Scripting::self()->qmlEngine());
267 }
268 if (!m_qmlComponent) {
269 m_qmlComponent = std::make_unique<QQmlComponent>(Scripting::self()->qmlEngine());
270 }
271 auto findMainItem = [this](const QMap<QString, QObject *> &tabBoxes) -> QObject * {
272 auto it = tabBoxes.constFind(config.layoutName());
273 if (it != tabBoxes.constEnd()) {
274 return it.value();
275 }
276 return nullptr;
277 };
278 m_mainItem = nullptr;
279 m_mainItem = findMainItem(m_clientTabBoxes);
280 if (!m_mainItem) {
281 m_mainItem = createSwitcherItem();
282 if (!m_mainItem) {
283 return;
284 }
285 }
286 if (SwitcherItem *item = switcherItem()) {
287 // In case the model isn't yet set (see below), index will be reset and therefore we
288 // need to save the current index row (https://bugs.kde.org/show_bug.cgi?id=333511).
289 int indexRow = index.row();
290 if (!item->model()) {
291 item->setModel(clientModel());
292 }
294 item->setCurrentIndex(indexRow);
295 item->setNoModifierGrab(q->noModifierGrab());
296 Q_EMIT item->aboutToShow();
297
298 // When SwitcherItem gets hidden, destroy also the window and main item
299 QObject::connect(item, &SwitcherItem::visibleChanged, q, [this, item]() {
300 if (!item->isVisible()) {
301 if (QQuickWindow *w = window()) {
302 w->hide();
303 w->destroy();
304 }
305 m_mainItem = nullptr;
306 }
307 });
308
309 // everything is prepared, so let's make the whole thing visible
310 item->setVisible(true);
311 }
312 if (QWindow *w = window()) {
313 wheelAngleDelta = 0;
314 w->installEventFilter(q);
315 // pretend to activate the window to enable accessibility notifications
316#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
317 QWindowSystemInterface::handleWindowActivated(w, Qt::TabFocusReason);
318#else
319 QWindowSystemInterface::handleFocusWindowChanged(w, Qt::TabFocusReason);
320#endif
321 }
322#endif
323}
324
325/***********************************************
326 * TabBoxHandler
327 ***********************************************/
328
330 : QObject(parent)
331{
333 d = new TabBoxHandlerPrivate(this);
334}
335
337{
338 delete d;
339}
340
342{
343 return d->config;
344}
345
347{
348 d->config = config;
349 Q_EMIT configChanged();
350}
351
353{
354 d->isShown = true;
355 d->lastRaisedClient = nullptr;
356 d->lastRaisedClientSucc = nullptr;
357 if (d->config.isShowTabBox()) {
358 d->show();
359 }
360 if (d->isHighlightWindows()) {
361 // TODO this should be
362 // QMetaObject::invokeMethod(this, "initHighlightWindows", Qt::QueuedConnection);
363 // but we somehow need to cross > 1 event cycle (likely because of queued invocation in the effects)
364 // to ensure the EffectWindow is present when updateHighlightWindows, thus elevating the window/tabbox
365 QTimer::singleShot(1, this, &TabBoxHandler::initHighlightWindows);
366 }
367}
368
369void TabBoxHandler::initHighlightWindows()
370{
371 if (isKWinCompositing()) {
372 const auto stack = stackingOrder();
373 for (Window *window : stack) {
374 shadeClient(window, false);
375 }
376 }
378}
379
380void TabBoxHandler::hide(bool abort)
381{
382 d->isShown = false;
383 if (d->isHighlightWindows()) {
384 d->endHighlightWindows(abort);
385 }
386#ifndef KWIN_UNIT_TEST
387 if (SwitcherItem *item = d->switcherItem()) {
388 Q_EMIT item->aboutToHide();
389 if (item->automaticallyHide()) {
390 item->setVisible(false);
391 }
392 }
393#endif
394}
395
396QModelIndex TabBoxHandler::nextPrev(bool forward) const
397{
398 QModelIndex ret;
399 QAbstractItemModel *model = d->clientModel();
400 if (forward) {
401 int column = d->index.column() + 1;
402 int row = d->index.row();
403 if (column == model->columnCount()) {
404 column = 0;
405 row++;
406 if (row == model->rowCount()) {
407 row = 0;
408 }
409 }
410 ret = model->index(row, column);
411 if (!ret.isValid()) {
412 ret = model->index(0, 0);
413 }
414 } else {
415 int column = d->index.column() - 1;
416 int row = d->index.row();
417 if (column < 0) {
418 column = model->columnCount() - 1;
419 row--;
420 if (row < 0) {
421 row = model->rowCount() - 1;
422 }
423 }
424 ret = model->index(row, column);
425 if (!ret.isValid()) {
426 row = model->rowCount() - 1;
427 for (int i = model->columnCount() - 1; i >= 0; i--) {
428 ret = model->index(row, i);
429 if (ret.isValid()) {
430 break;
431 }
432 }
433 }
434 }
435 if (ret.isValid()) {
436 return ret;
437 } else {
438 return d->index;
439 }
440}
441
442void TabBoxHandler::setCurrentIndex(const QModelIndex &index)
443{
444 if (d->index == index) {
445 return;
446 }
447 if (!index.isValid()) {
448 return;
449 }
450 d->index = index;
451 if (d->isHighlightWindows()) {
453 }
454 Q_EMIT selectedIndexChanged();
455}
456
457const QModelIndex &TabBoxHandler::currentIndex() const
458{
459 return d->index;
460}
461
462void TabBoxHandler::grabbedKeyEvent(QKeyEvent *event) const
463{
464 if (!d->m_mainItem || !d->window()) {
465 return;
466 }
467 QCoreApplication::sendEvent(d->window(), event);
468}
469
470bool TabBoxHandler::containsPos(const QPoint &pos) const
471{
472 if (!d->m_mainItem) {
473 return false;
474 }
475 QWindow *w = d->window();
476 if (w) {
477 return w->geometry().contains(pos);
478 }
479 return false;
480}
481
482QModelIndex TabBoxHandler::index(Window *client) const
483{
484 return d->clientModel()->index(client);
485}
486
487QList<Window *> TabBoxHandler::clientList() const
488{
489 return d->clientModel()->clientList();
490}
491
492Window *TabBoxHandler::client(const QModelIndex &index) const
493{
494 if (!index.isValid()) {
495 return nullptr;
496 }
497 Window *c = static_cast<Window *>(
498 d->clientModel()->data(index, ClientModel::ClientRole).value<void *>());
499 return c;
500}
501
502void TabBoxHandler::createModel(bool partialReset)
503{
504 d->clientModel()->createClientList(partialReset);
505 // TODO: C++11 use lambda function
506 bool lastRaised = false;
507 bool lastRaisedSucc = false;
508 const auto clients = stackingOrder();
509 for (Window *window : clients) {
510 if (window == d->lastRaisedClient) {
511 lastRaised = true;
512 }
513 if (window == d->lastRaisedClientSucc) {
514 lastRaisedSucc = true;
515 }
516 }
517 if (d->lastRaisedClient && !lastRaised) {
518 d->lastRaisedClient = nullptr;
519 }
520 if (d->lastRaisedClientSucc && !lastRaisedSucc) {
521 d->lastRaisedClientSucc = nullptr;
522 }
523}
524
525QModelIndex TabBoxHandler::first() const
526{
527 return d->clientModel()->index(0, 0);
528}
529
530bool TabBoxHandler::eventFilter(QObject *watched, QEvent *e)
531{
532 if (e->type() == QEvent::Wheel && watched == d->window()) {
533 QWheelEvent *event = static_cast<QWheelEvent *>(e);
534 // On x11 the delta for vertical scrolling might also be on X for whatever reason
535 const int delta = std::abs(event->angleDelta().x()) > std::abs(event->angleDelta().y()) ? event->angleDelta().x() : event->angleDelta().y();
536 d->wheelAngleDelta += delta;
537 while (d->wheelAngleDelta <= -120) {
538 d->wheelAngleDelta += 120;
539 const QModelIndex index = nextPrev(true);
540 if (index.isValid()) {
542 }
543 }
544 while (d->wheelAngleDelta >= 120) {
545 d->wheelAngleDelta -= 120;
546 const QModelIndex index = nextPrev(false);
547 if (index.isValid()) {
549 }
550 }
551 return true;
552 }
553 // pass on
554 return QObject::eventFilter(watched, e);
555}
556
558
559} // namespace TabBox
560} // namespace KWin
561
562#include "moc_tabboxhandler.cpp"
static Scripting * self()
Definition scripting.h:393
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
QList< Window * > clientList() const
Definition clientmodel.h:76
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
void createClientList(bool partialReset=false)
ClientDesktopMode clientDesktopMode() const
@ AllDesktopsClients
Windows from all desktops are included.
static QString defaultLayoutName()
QString & layoutName() const
void createModel(bool partialReset=false)
virtual void raiseClient(Window *c) const =0
virtual Window * desktopClient() const =0
const QModelIndex & currentIndex() const
bool containsPos(const QPoint &pos) const
virtual void highlightWindows(Window *window=nullptr, QWindow *controller=nullptr)=0
virtual void shadeClient(Window *c, bool b) const =0
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
bool eventFilter(QObject *watcher, QEvent *event) override
Window * client(const QModelIndex &index) const
virtual bool noModifierGrab() const =0
virtual QList< Window * > stackingOrder() const =0
QModelIndex index(Window *client) const
const TabBoxConfig & config() const
void setCurrentIndex(const QModelIndex &index)
virtual void restack(Window *c, Window *under)=0
virtual void elevateClient(Window *c, QWindow *tabbox, bool elevate) const =0
virtual bool isKWinCompositing() const =0
void endHighlightWindows(bool abort=false)
std::unique_ptr< QQmlContext > m_qmlContext
QMap< QString, QObject * > m_clientTabBoxes
std::unique_ptr< QQmlComponent > m_qmlComponent
SwitcherItem * switcherItem() const
QUuid internalId
Definition window.h:259
TabBoxHandler * tabBox
Session::Type type
Definition session.cpp:17