1
/********************************************************************
2
KWin - the KDE window manager
3
This file is part of the KDE project.
5
Copyright (C) 2015 Martin Gräßlin <mgraesslin@kde.org>
7
This program is free software; you can redistribute it and/or modify
8
it under the terms of the GNU General Public License as published by
9
the Free Software Foundation; either version 2 of the License, or
10
(at your option) any later version.
12
This program is distributed in the hope that it will be useful,
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
GNU General Public License for more details.
17
You should have received a copy of the GNU General Public License
18
along with this program. If not, see <http://www.gnu.org/licenses/>.
19
*********************************************************************/
20
#include "abstract_client.h"
21
#include "decorations/decorationpalette.h"
22
#include "focuschain.h"
24
#ifdef KWIN_BUILD_TABBOX
28
#include "workspace.h"
31
#include "wayland_server.h"
32
#include <KWayland/Server/plasmawindowmanagement_interface.h>
38
QHash<QString, std::weak_ptr<Decoration::DecorationPalette>> AbstractClient::s_palettes;
39
std::shared_ptr<Decoration::DecorationPalette> AbstractClient::s_defaultPalette;
41
AbstractClient::AbstractClient()
43
#ifdef KWIN_BUILD_TABBOX
44
, m_tabBoxClient(QSharedPointer<TabBox::TabBoxClientImpl>(new TabBox::TabBoxClientImpl(this)))
46
, m_colorScheme(QStringLiteral("kdeglobals"))
50
AbstractClient::~AbstractClient() = default;
52
void AbstractClient::updateMouseGrab()
56
bool AbstractClient::belongToSameApplication(const AbstractClient *c1, const AbstractClient *c2, bool active_hack)
58
return c1->belongsToSameApplication(c2, active_hack);
61
bool AbstractClient::isTransient() const
66
TabGroup *AbstractClient::tabGroup() const
71
bool AbstractClient::untab(const QRect &toGeometry, bool clientRemoved)
74
Q_UNUSED(clientRemoved)
78
bool AbstractClient::isCurrentTab() const
83
void AbstractClient::growHorizontal()
87
void AbstractClient::growVertical()
91
void AbstractClient::shrinkHorizontal()
95
void AbstractClient::shrinkVertical()
99
void AbstractClient::packTo(int left, int top)
105
xcb_timestamp_t AbstractClient::userTime() const
107
return XCB_TIME_CURRENT_TIME;
110
void AbstractClient::setSkipSwitcher(bool set)
112
set = rules()->checkSkipSwitcher(set);
113
if (set == skipSwitcher())
115
m_skipSwitcher = set;
116
updateWindowRules(Rules::SkipSwitcher);
117
emit skipSwitcherChanged();
120
void AbstractClient::setSkipPager(bool b)
122
b = rules()->checkSkipPager(b);
123
if (b == skipPager())
127
info->setState(b ? NET::SkipPager : NET::States(0), NET::SkipPager);
128
updateWindowRules(Rules::SkipPager);
129
emit skipPagerChanged();
132
void AbstractClient::doSetSkipPager()
136
void AbstractClient::setSkipTaskbar(bool b)
138
int was_wants_tab_focus = wantsTabFocus();
139
if (b == skipTaskbar())
143
updateWindowRules(Rules::SkipTaskbar);
144
if (was_wants_tab_focus != wantsTabFocus()) {
145
FocusChain::self()->update(this, isActive() ? FocusChain::MakeFirst : FocusChain::Update);
147
emit skipTaskbarChanged();
150
void AbstractClient::setOriginalSkipTaskbar(bool b)
152
m_originalSkipTaskbar = rules()->checkSkipTaskbar(b);
153
setSkipTaskbar(m_originalSkipTaskbar);
156
void AbstractClient::doSetSkipTaskbar()
161
void AbstractClient::setIcon(const QIcon &icon)
167
void AbstractClient::setActive(bool act)
169
if (m_active == act) {
173
const int ruledOpacity = m_active
174
? rules()->checkOpacityActive(qRound(opacity() * 100.0))
175
: rules()->checkOpacityInactive(qRound(opacity() * 100.0));
176
setOpacity(ruledOpacity / 100.0);
177
workspace()->setActiveClient(act ? this : NULL);
182
if (!m_active && shadeMode() == ShadeActivated)
183
setShade(ShadeNormal);
186
emit activeChanged();
190
void AbstractClient::doSetActive()
194
void AbstractClient::updateLayer()
198
void AbstractClient::setKeepAbove(bool b)
200
b = rules()->checkKeepAbove(b);
201
if (b && !rules()->checkKeepBelow(false))
203
if (b == keepAbove()) {
204
// force hint change if different
205
if (info && bool(info->state() & NET::KeepAbove) != keepAbove())
206
info->setState(keepAbove() ? NET::KeepAbove : NET::States(0), NET::KeepAbove);
211
info->setState(keepAbove() ? NET::KeepAbove : NET::States(0), NET::KeepAbove);
213
workspace()->updateClientLayer(this);
214
updateWindowRules(Rules::Above);
217
emit keepAboveChanged(m_keepAbove);
220
void AbstractClient::doSetKeepAbove()
224
void AbstractClient::setKeepBelow(bool b)
226
b = rules()->checkKeepBelow(b);
227
if (b && !rules()->checkKeepAbove(false))
229
if (b == keepBelow()) {
230
// force hint change if different
231
if (info && bool(info->state() & NET::KeepBelow) != keepBelow())
232
info->setState(keepBelow() ? NET::KeepBelow : NET::States(0), NET::KeepBelow);
237
info->setState(keepBelow() ? NET::KeepBelow : NET::States(0), NET::KeepBelow);
239
workspace()->updateClientLayer(this);
240
updateWindowRules(Rules::Below);
243
emit keepBelowChanged(m_keepBelow);
246
void AbstractClient::doSetKeepBelow()
250
void AbstractClient::startAutoRaise()
252
delete m_autoRaiseTimer;
253
m_autoRaiseTimer = new QTimer(this);
254
connect(m_autoRaiseTimer, &QTimer::timeout, this, &AbstractClient::autoRaise);
255
m_autoRaiseTimer->setSingleShot(true);
256
m_autoRaiseTimer->start(options->autoRaiseInterval());
259
void AbstractClient::cancelAutoRaise()
261
delete m_autoRaiseTimer;
262
m_autoRaiseTimer = nullptr;
265
void AbstractClient::autoRaise()
267
workspace()->raiseClient(this);
271
bool AbstractClient::wantsTabFocus() const
273
return (isNormalWindow() || isDialog()) && wantsInput();
276
bool AbstractClient::isSpecialWindow() const
279
return isDesktop() || isDock() || isSplash() || isToolbar() || isNotification() || isOnScreenDisplay();
282
void AbstractClient::demandAttention(bool set)
286
if (m_demandsAttention == set)
288
m_demandsAttention = set;
290
info->setState(set ? NET::DemandsAttention : NET::States(0), NET::DemandsAttention);
292
workspace()->clientAttentionChanged(this, set);
293
emit demandsAttentionChanged();
296
void AbstractClient::setDesktop(int desktop)
298
const int numberOfDesktops = VirtualDesktopManager::self()->count();
299
if (desktop != NET::OnAllDesktops) // Do range check
300
desktop = qMax(1, qMin(numberOfDesktops, desktop));
301
desktop = qMin(numberOfDesktops, rules()->checkDesktop(desktop));
302
if (m_desktop == desktop)
305
int was_desk = m_desktop;
306
const bool wasOnCurrentDesktop = isOnCurrentDesktop();
309
doSetDesktop(desktop, was_desk);
311
FocusChain::self()->update(this, FocusChain::MakeFirst);
312
updateWindowRules(Rules::Desktop);
314
emit desktopChanged();
315
if (wasOnCurrentDesktop != isOnCurrentDesktop())
316
emit desktopPresenceChanged(this, was_desk);
319
void AbstractClient::doSetDesktop(int desktop, int was_desk)
325
void AbstractClient::setOnAllDesktops(bool b)
327
if ((b && isOnAllDesktops()) ||
328
(!b && !isOnAllDesktops()))
331
setDesktop(NET::OnAllDesktops);
333
setDesktop(VirtualDesktopManager::self()->current());
336
bool AbstractClient::isShadeable() const
341
void AbstractClient::setShade(bool set)
343
set ? setShade(ShadeNormal) : setShade(ShadeNone);
346
void AbstractClient::setShade(ShadeMode mode)
351
ShadeMode AbstractClient::shadeMode() const
356
AbstractClient::Position AbstractClient::titlebarPosition() const
358
// TODO: still needed, remove?
362
void AbstractClient::setMinimized(bool set)
364
set ? minimize() : unminimize();
367
void AbstractClient::minimize(bool avoid_animation)
369
if (!isMinimizable() || isMinimized())
372
if (isShade() && info) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded
373
info->setState(0, NET::Shaded);
379
updateWindowRules(Rules::Minimize);
380
FocusChain::self()->update(this, FocusChain::MakeFirstMinimized);
381
// TODO: merge signal with s_minimized
382
emit clientMinimized(this, !avoid_animation);
383
emit minimizedChanged();
386
void AbstractClient::unminimize(bool avoid_animation)
391
if (rules()->checkMinimize(false)) {
395
if (isShade() && info) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded
396
info->setState(NET::Shaded, NET::Shaded);
402
updateWindowRules(Rules::Minimize);
403
emit clientUnminimized(this, !avoid_animation);
404
emit minimizedChanged();
407
void AbstractClient::doMinimize()
411
QPalette AbstractClient::palette() const
416
return m_palette->palette();
419
const Decoration::DecorationPalette *AbstractClient::decorationPalette() const
421
return m_palette.get();
424
void AbstractClient::updateColorScheme(QString path)
426
if (path.isEmpty()) {
427
path = QStringLiteral("kdeglobals");
430
if (!m_palette || m_colorScheme != path) {
431
m_colorScheme = path;
434
disconnect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &AbstractClient::handlePaletteChange);
437
auto it = s_palettes.find(m_colorScheme);
439
if (it == s_palettes.end() || it->expired()) {
440
m_palette = std::make_shared<Decoration::DecorationPalette>(m_colorScheme);
441
if (m_palette->isValid()) {
442
s_palettes[m_colorScheme] = m_palette;
444
if (!s_defaultPalette) {
445
s_defaultPalette = std::make_shared<Decoration::DecorationPalette>(QStringLiteral("kdeglobals"));
446
s_palettes[QStringLiteral("kdeglobals")] = s_defaultPalette;
449
m_palette = s_defaultPalette;
452
if (m_colorScheme == QStringLiteral("kdeglobals")) {
453
s_defaultPalette = m_palette;
456
m_palette = it->lock();
459
connect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &AbstractClient::handlePaletteChange);
461
emit paletteChanged(palette());
465
void AbstractClient::handlePaletteChange()
467
emit paletteChanged(palette());
470
void AbstractClient::keepInArea(QRect area, bool partial)
473
// increase the area so that can have only 100 pixels in the area
474
area.setLeft(qMin(area.left() - width() + 100, area.left()));
475
area.setTop(qMin(area.top() - height() + 100, area.top()));
476
area.setRight(qMax(area.right() + width() - 100, area.right()));
477
area.setBottom(qMax(area.bottom() + height() - 100, area.bottom()));
480
// resize to fit into area
481
if (area.width() < width() || area.height() < height())
482
resizeWithChecks(qMin(area.width(), width()), qMin(area.height(), height()));
484
int tx = x(), ty = y();
485
if (geometry().right() > area.right() && width() <= area.width())
486
tx = area.right() - width() + 1;
487
if (geometry().bottom() > area.bottom() && height() <= area.height())
488
ty = area.bottom() - height() + 1;
489
if (!area.contains(geometry().topLeft())) {
495
if (tx != x() || ty != y())
499
QSize AbstractClient::maxSize() const
501
return rules()->checkMaxSize(QSize(INT_MAX, INT_MAX));
504
QSize AbstractClient::minSize() const
506
return rules()->checkMinSize(QSize(0, 0));
509
void AbstractClient::updateMoveResize(const QPointF ¤tGlobalCursor)
511
Q_UNUSED(currentGlobalCursor)
514
bool AbstractClient::hasStrut() const
519
void AbstractClient::setupWindowManagementInterface()
522
if (m_windowManagementInterface) {
526
if (!waylandServer() || !surface()) {
529
if (!waylandServer()->windowManagement()) {
532
using namespace KWayland::Server;
533
auto w = waylandServer()->windowManagement()->createWindow(this);
534
w->setTitle(caption());
535
w->setVirtualDesktop(isOnAllDesktops() ? 0 : desktop() - 1);
536
w->setActive(isActive());
537
w->setFullscreen(isFullScreen());
538
w->setKeepAbove(keepAbove());
539
w->setKeepBelow(keepBelow());
540
w->setMaximized(maximizeMode() == KWin::MaximizeFull);
541
w->setMinimized(isMinimized());
542
w->setOnAllDesktops(isOnAllDesktops());
543
w->setDemandsAttention(isDemandingAttention());
544
w->setCloseable(isCloseable());
545
w->setMaximizeable(isMaximizable());
546
w->setMinimizeable(isMinimizable());
547
w->setFullscreenable(isFullScreenable());
548
w->setThemedIconName(icon().name().isEmpty() ? QStringLiteral("xorg") : icon().name());
549
w->setAppId(QString::fromUtf8(resourceName()));
550
connect(this, &AbstractClient::captionChanged, w, [w, this] { w->setTitle(caption()); });
551
connect(this, &AbstractClient::desktopChanged, w,
553
if (isOnAllDesktops()) {
554
w->setOnAllDesktops(true);
557
w->setVirtualDesktop(desktop() - 1);
558
w->setOnAllDesktops(false);
561
connect(this, &AbstractClient::activeChanged, w, [w, this] { w->setActive(isActive()); });
562
connect(this, &AbstractClient::fullScreenChanged, w, [w, this] { w->setFullscreen(isFullScreen()); });
563
connect(this, &AbstractClient::keepAboveChanged, w, &PlasmaWindowInterface::setKeepAbove);
564
connect(this, &AbstractClient::keepBelowChanged, w, &PlasmaWindowInterface::setKeepBelow);
565
connect(this, &AbstractClient::minimizedChanged, w, [w, this] { w->setMinimized(isMinimized()); });
566
connect(this, static_cast<void (AbstractClient::*)(AbstractClient*,MaximizeMode)>(&AbstractClient::clientMaximizedStateChanged), w,
567
[w] (KWin::AbstractClient *c, MaximizeMode mode) {
569
w->setMaximized(mode == KWin::MaximizeFull);
572
connect(this, &AbstractClient::demandsAttentionChanged, w, [w, this] { w->setDemandsAttention(isDemandingAttention()); });
573
connect(this, &AbstractClient::iconChanged, w,
575
const QIcon i = icon();
576
w->setThemedIconName(i.name().isEmpty() ? QStringLiteral("xorg") : i.name());
579
connect(this, &AbstractClient::windowClassChanged, w,
581
w->setAppId(QString::fromUtf8(resourceName()));
584
connect(w, &PlasmaWindowInterface::closeRequested, this, [this] { closeWindow(); });
585
connect(w, &PlasmaWindowInterface::virtualDesktopRequested, this,
586
[this] (quint32 desktop) {
587
workspace()->sendClientToDesktop(this, desktop + 1, true);
590
connect(w, &PlasmaWindowInterface::fullscreenRequested, this,
592
setFullScreen(set, false);
595
connect(w, &PlasmaWindowInterface::minimizedRequested, this,
604
connect(w, &PlasmaWindowInterface::maximizedRequested, this,
606
maximize(set ? MaximizeFull : MaximizeRestore);
609
connect(w, &PlasmaWindowInterface::keepAboveRequested, this,
614
connect(w, &PlasmaWindowInterface::keepBelowRequested, this,
619
connect(w, &PlasmaWindowInterface::demandsAttentionRequested, this,
621
demandAttention(set);
624
connect(w, &PlasmaWindowInterface::activeRequested, this,
627
workspace()->activateClient(this, true);
631
m_windowManagementInterface = w;
635
void AbstractClient::destroyWindowManagementInterface()
638
delete m_windowManagementInterface;
639
m_windowManagementInterface = nullptr;
643
Options::MouseCommand AbstractClient::getMouseCommand(Qt::MouseButton button, bool *handled) const
646
if (button == Qt::NoButton) {
647
return Options::MouseNothing;
650
if (options->isClickRaise()) {
652
return Options::MouseActivateRaiseAndPassClick;
658
return options->commandWindow1();
659
case Qt::MiddleButton:
660
return options->commandWindow2();
661
case Qt::RightButton:
662
return options->commandWindow3();
664
// all other buttons pass Activate & Pass Client
665
return Options::MouseActivateAndPassClick;
668
return Options::MouseNothing;
671
Options::MouseCommand AbstractClient::getWheelCommand(Qt::Orientation orientation, bool *handled) const
674
if (orientation != Qt::Vertical) {
675
return Options::MouseNothing;
679
return options->commandWindowWheel();
681
return Options::MouseNothing;
684
bool AbstractClient::performMouseCommand(Options::MouseCommand cmd, const QPoint &globalPos)
688
case Options::MouseRaise:
689
workspace()->raiseClient(this);
691
case Options::MouseLower: {
692
workspace()->lowerClient(this);
695
case Options::MouseOperationsMenu:
696
if (isActive() && options->isClickRaise())
698
workspace()->showWindowMenu(QRect(globalPos, globalPos), this);
700
case Options::MouseToggleRaiseAndLower:
701
workspace()->raiseOrLowerClient(this);
703
case Options::MouseActivateAndRaise: {
704
replay = isActive(); // for clickraise mode
705
bool mustReplay = !rules()->checkAcceptFocus(info->input());
707
ToplevelList::const_iterator it = workspace()->stackingOrder().constEnd(),
708
begin = workspace()->stackingOrder().constBegin();
709
while (mustReplay && --it != begin && *it != this) {
710
AbstractClient *c = qobject_cast<AbstractClient*>(*it);
711
if (!c || (c->keepAbove() && !keepAbove()) || (keepBelow() && !c->keepBelow()))
712
continue; // can never raise above "it"
713
mustReplay = !(c->isOnCurrentDesktop() && c->isOnCurrentActivity() && c->geometry().intersects(geometry()));
716
workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise);
717
screens()->setCurrent(globalPos);
718
replay = replay || mustReplay;
721
case Options::MouseActivateAndLower:
722
workspace()->requestFocus(this);
723
workspace()->lowerClient(this);
724
screens()->setCurrent(globalPos);
725
replay = replay || !rules()->checkAcceptFocus(info->input());
727
case Options::MouseActivate:
728
replay = isActive(); // for clickraise mode
729
workspace()->takeActivity(this, Workspace::ActivityFocus);
730
screens()->setCurrent(globalPos);
731
replay = replay || !rules()->checkAcceptFocus(info->input());
733
case Options::MouseActivateRaiseAndPassClick:
734
workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise);
735
screens()->setCurrent(globalPos);
738
case Options::MouseActivateAndPassClick:
739
workspace()->takeActivity(this, Workspace::ActivityFocus);
740
screens()->setCurrent(globalPos);
743
case Options::MouseMaximize:
744
maximize(MaximizeFull);
746
case Options::MouseRestore:
747
maximize(MaximizeRestore);
749
case Options::MouseMinimize:
752
case Options::MouseAbove: {
753
StackingUpdatesBlocker blocker(workspace());
760
case Options::MouseBelow: {
761
StackingUpdatesBlocker blocker(workspace());
768
case Options::MousePreviousDesktop:
769
workspace()->windowToPreviousDesktop(this);
771
case Options::MouseNextDesktop:
772
workspace()->windowToNextDesktop(this);
774
case Options::MouseOpacityMore:
775
if (!isDesktop()) // No point in changing the opacity of the desktop
776
setOpacity(qMin(opacity() + 0.1, 1.0));
778
case Options::MouseOpacityLess:
779
if (!isDesktop()) // No point in changing the opacity of the desktop
780
setOpacity(qMax(opacity() - 0.1, 0.1));
782
case Options::MousePreviousTab:
784
tabGroup()->activatePrev();
786
case Options::MouseNextTab:
788
tabGroup()->activateNext();
790
case Options::MouseClose:
793
case Options::MouseDragTab:
794
case Options::MouseNothing: