2020-08-02 22:22:19 +00:00
|
|
|
/*
|
|
|
|
KWin - the KDE window manager
|
|
|
|
This file is part of the KDE project.
|
2015-03-05 09:21:03 +00:00
|
|
|
|
2020-08-02 22:22:19 +00:00
|
|
|
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
|
|
|
|
SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
2015-03-05 09:21:03 +00:00
|
|
|
|
2020-08-02 22:22:19 +00:00
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
*/
|
2015-03-05 09:21:03 +00:00
|
|
|
#include "abstract_client.h"
|
2017-01-11 09:21:03 +00:00
|
|
|
|
2021-03-05 11:12:03 +00:00
|
|
|
#ifdef KWIN_BUILD_ACTIVITIES
|
|
|
|
#include "activities.h"
|
|
|
|
#endif
|
2017-01-11 09:21:03 +00:00
|
|
|
#include "appmenu.h"
|
2015-12-03 16:27:00 +00:00
|
|
|
#include "decorations/decoratedclient.h"
|
2015-04-29 10:02:54 +00:00
|
|
|
#include "decorations/decorationpalette.h"
|
2016-05-23 08:01:31 +00:00
|
|
|
#include "decorations/decorationbridge.h"
|
2015-10-22 15:08:17 +00:00
|
|
|
#include "effects.h"
|
2015-03-13 11:54:11 +00:00
|
|
|
#include "focuschain.h"
|
2015-10-22 13:58:24 +00:00
|
|
|
#include "outline.h"
|
2015-07-09 14:08:01 +00:00
|
|
|
#include "screens.h"
|
2015-03-12 14:21:07 +00:00
|
|
|
#ifdef KWIN_BUILD_TABBOX
|
|
|
|
#include "tabbox.h"
|
|
|
|
#endif
|
2015-10-22 13:58:24 +00:00
|
|
|
#include "screenedge.h"
|
2016-02-18 10:30:52 +00:00
|
|
|
#include "useractions.h"
|
2015-03-12 15:08:19 +00:00
|
|
|
#include "workspace.h"
|
2015-03-05 09:21:03 +00:00
|
|
|
|
2015-07-09 07:10:33 +00:00
|
|
|
#include "wayland_server.h"
|
2020-04-29 15:18:41 +00:00
|
|
|
#include <KWaylandServer/plasmawindowmanagement_interface.h>
|
2015-07-09 07:10:33 +00:00
|
|
|
|
2020-10-17 12:43:16 +00:00
|
|
|
#include <KDecoration2/DecoratedClient>
|
2015-12-03 12:24:44 +00:00
|
|
|
#include <KDecoration2/Decoration>
|
|
|
|
|
2016-10-27 13:59:08 +00:00
|
|
|
#include <KDesktopFile>
|
|
|
|
|
2020-05-14 15:25:32 +00:00
|
|
|
#include <QDir>
|
2015-12-03 15:40:27 +00:00
|
|
|
#include <QMouseEvent>
|
|
|
|
#include <QStyleHints>
|
|
|
|
|
2015-03-05 09:21:03 +00:00
|
|
|
namespace KWin
|
|
|
|
{
|
|
|
|
|
2020-01-13 18:57:07 +00:00
|
|
|
static inline int sign(int v)
|
|
|
|
{
|
|
|
|
return (v > 0) - (v < 0);
|
|
|
|
}
|
|
|
|
|
2015-04-29 10:02:54 +00:00
|
|
|
QHash<QString, std::weak_ptr<Decoration::DecorationPalette>> AbstractClient::s_palettes;
|
|
|
|
std::shared_ptr<Decoration::DecorationPalette> AbstractClient::s_defaultPalette;
|
|
|
|
|
2015-03-05 09:21:03 +00:00
|
|
|
AbstractClient::AbstractClient()
|
|
|
|
: Toplevel()
|
2015-03-12 14:21:07 +00:00
|
|
|
#ifdef KWIN_BUILD_TABBOX
|
|
|
|
, m_tabBoxClient(QSharedPointer<TabBox::TabBoxClientImpl>(new TabBox::TabBoxClientImpl(this)))
|
|
|
|
#endif
|
2015-04-29 10:02:54 +00:00
|
|
|
, m_colorScheme(QStringLiteral("kdeglobals"))
|
2015-03-05 09:21:03 +00:00
|
|
|
{
|
2015-10-26 09:14:54 +00:00
|
|
|
connect(this, &AbstractClient::clientStartUserMovedResized, this, &AbstractClient::moveResizedChanged);
|
|
|
|
connect(this, &AbstractClient::clientFinishUserMovedResized, this, &AbstractClient::moveResizedChanged);
|
|
|
|
connect(this, &AbstractClient::clientStartUserMovedResized, this, &AbstractClient::removeCheckScreenConnection);
|
|
|
|
connect(this, &AbstractClient::clientFinishUserMovedResized, this, &AbstractClient::setupCheckScreenConnection);
|
2015-12-03 12:31:25 +00:00
|
|
|
|
|
|
|
connect(this, &AbstractClient::paletteChanged, this, &AbstractClient::triggerDecorationRepaint);
|
2016-05-23 08:01:31 +00:00
|
|
|
|
|
|
|
connect(Decoration::DecorationBridge::self(), &QObject::destroyed, this, &AbstractClient::destroyDecoration);
|
2016-12-18 09:39:04 +00:00
|
|
|
|
virtualkeyboard: resize the focused window to make room for the keyboard
Summary:
alternative approach: try to resize the winidow to make room for the keyboard.
the new input wayland protocol doesn't have anymore the overlap rectangle (and it would not be going to work with qwidget apps anyways)
in the future will probably be needed anextension to the input protocol v3 which partially gets back this, tough window resizing is needed regardless
what's missing: the resize should be "temporary" and the window should be restored to its previous geometry when the keyboard closes
Test Plan: tested with test QML code
Reviewers: #plasma, #kwin, bshah, graesslin, romangg, davidedmundson
Reviewed By: #plasma, #kwin, romangg, davidedmundson
Subscribers: nicolasfella, mart, kwin, davidedmundson, graesslin
Tags: #kwin
Maniphest Tasks: T9815
Differential Revision: https://phabricator.kde.org/D18818
2019-03-20 10:04:51 +00:00
|
|
|
// If the user manually moved the window, don't restore it after the keyboard closes
|
|
|
|
connect(this, &AbstractClient::clientFinishUserMovedResized, this, [this] () {
|
|
|
|
m_keyboardGeometryRestore = QRect();
|
|
|
|
});
|
|
|
|
connect(this, qOverload<AbstractClient *, bool, bool>(&AbstractClient::clientMaximizedStateChanged), this, [this] () {
|
|
|
|
m_keyboardGeometryRestore = QRect();
|
|
|
|
});
|
|
|
|
connect(this, &AbstractClient::fullScreenChanged, this, [this] () {
|
|
|
|
m_keyboardGeometryRestore = QRect();
|
|
|
|
});
|
|
|
|
|
2016-12-18 09:39:04 +00:00
|
|
|
// replace on-screen-display on size changes
|
2020-02-05 09:28:50 +00:00
|
|
|
connect(this, &AbstractClient::frameGeometryChanged, this,
|
2016-12-18 09:39:04 +00:00
|
|
|
[this] (Toplevel *c, const QRect &old) {
|
|
|
|
Q_UNUSED(c)
|
2020-11-27 08:41:13 +00:00
|
|
|
if (isOnScreenDisplay() && !frameGeometry().isEmpty() && old.size() != frameGeometry().size() && isPlaceable()) {
|
2016-12-26 10:04:14 +00:00
|
|
|
GeometryUpdatesBlocker blocker(this);
|
2019-09-12 14:52:47 +00:00
|
|
|
const QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop());
|
2016-12-18 09:39:04 +00:00
|
|
|
Placement::self()->place(this, area);
|
2019-09-27 10:01:10 +00:00
|
|
|
setGeometryRestore(frameGeometry());
|
2016-12-18 09:39:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
2017-01-11 09:21:03 +00:00
|
|
|
|
2017-03-17 16:46:13 +00:00
|
|
|
connect(this, &AbstractClient::paddingChanged, this, [this]() {
|
2021-02-05 12:46:22 +00:00
|
|
|
m_visibleRectBeforeGeometryUpdate = visibleGeometry();
|
2017-03-17 16:46:13 +00:00
|
|
|
});
|
|
|
|
|
2017-01-11 09:21:03 +00:00
|
|
|
connect(ApplicationMenu::self(), &ApplicationMenu::applicationMenuEnabledChanged, this, [this] {
|
|
|
|
emit hasApplicationMenuChanged(hasApplicationMenu());
|
|
|
|
});
|
2015-03-05 09:21:03 +00:00
|
|
|
}
|
|
|
|
|
2015-10-13 11:15:39 +00:00
|
|
|
AbstractClient::~AbstractClient()
|
|
|
|
{
|
2019-08-31 14:28:37 +00:00
|
|
|
Q_ASSERT(m_blockGeometryUpdates == 0);
|
2015-12-04 07:12:49 +00:00
|
|
|
Q_ASSERT(m_decoration.decoration == nullptr);
|
2015-10-13 11:15:39 +00:00
|
|
|
}
|
2015-03-05 09:21:03 +00:00
|
|
|
|
|
|
|
void AbstractClient::updateMouseGrab()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2017-11-05 09:10:17 +00:00
|
|
|
bool AbstractClient::belongToSameApplication(const AbstractClient *c1, const AbstractClient *c2, SameApplicationChecks checks)
|
2015-03-05 09:21:03 +00:00
|
|
|
{
|
2017-11-05 09:10:17 +00:00
|
|
|
return c1->belongsToSameApplication(c2, checks);
|
2015-03-05 09:21:03 +00:00
|
|
|
}
|
|
|
|
|
2015-03-05 12:35:54 +00:00
|
|
|
bool AbstractClient::isTransient() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-05-05 10:06:24 +00:00
|
|
|
void AbstractClient::setClientShown(bool shown)
|
|
|
|
{
|
|
|
|
Q_UNUSED(shown)
|
|
|
|
}
|
|
|
|
|
2015-03-12 10:24:27 +00:00
|
|
|
xcb_timestamp_t AbstractClient::userTime() const
|
|
|
|
{
|
|
|
|
return XCB_TIME_CURRENT_TIME;
|
|
|
|
}
|
|
|
|
|
2015-03-12 14:35:36 +00:00
|
|
|
void AbstractClient::setSkipSwitcher(bool set)
|
|
|
|
{
|
|
|
|
set = rules()->checkSkipSwitcher(set);
|
|
|
|
if (set == skipSwitcher())
|
|
|
|
return;
|
|
|
|
m_skipSwitcher = set;
|
2018-05-24 04:33:39 +00:00
|
|
|
doSetSkipSwitcher();
|
2015-03-12 14:35:36 +00:00
|
|
|
updateWindowRules(Rules::SkipSwitcher);
|
|
|
|
emit skipSwitcherChanged();
|
|
|
|
}
|
|
|
|
|
2015-06-06 19:17:23 +00:00
|
|
|
void AbstractClient::setSkipPager(bool b)
|
|
|
|
{
|
|
|
|
b = rules()->checkSkipPager(b);
|
|
|
|
if (b == skipPager())
|
|
|
|
return;
|
|
|
|
m_skipPager = b;
|
|
|
|
doSetSkipPager();
|
|
|
|
updateWindowRules(Rules::SkipPager);
|
|
|
|
emit skipPagerChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::doSetSkipPager()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-06-06 20:08:12 +00:00
|
|
|
void AbstractClient::setSkipTaskbar(bool b)
|
|
|
|
{
|
|
|
|
int was_wants_tab_focus = wantsTabFocus();
|
|
|
|
if (b == skipTaskbar())
|
|
|
|
return;
|
|
|
|
m_skipTaskbar = b;
|
|
|
|
doSetSkipTaskbar();
|
|
|
|
updateWindowRules(Rules::SkipTaskbar);
|
|
|
|
if (was_wants_tab_focus != wantsTabFocus()) {
|
|
|
|
FocusChain::self()->update(this, isActive() ? FocusChain::MakeFirst : FocusChain::Update);
|
|
|
|
}
|
|
|
|
emit skipTaskbarChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::setOriginalSkipTaskbar(bool b)
|
|
|
|
{
|
|
|
|
m_originalSkipTaskbar = rules()->checkSkipTaskbar(b);
|
|
|
|
setSkipTaskbar(m_originalSkipTaskbar);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::doSetSkipTaskbar()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-05-24 04:33:39 +00:00
|
|
|
void AbstractClient::doSetSkipSwitcher()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-03-12 14:44:39 +00:00
|
|
|
void AbstractClient::setIcon(const QIcon &icon)
|
|
|
|
{
|
|
|
|
m_icon = icon;
|
|
|
|
emit iconChanged();
|
|
|
|
}
|
|
|
|
|
2015-03-12 15:08:19 +00:00
|
|
|
void AbstractClient::setActive(bool act)
|
|
|
|
{
|
2020-07-16 07:17:19 +00:00
|
|
|
if (isZombie()) {
|
|
|
|
return;
|
|
|
|
}
|
2015-03-12 15:08:19 +00:00
|
|
|
if (m_active == act) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_active = act;
|
|
|
|
const int ruledOpacity = m_active
|
|
|
|
? rules()->checkOpacityActive(qRound(opacity() * 100.0))
|
|
|
|
: rules()->checkOpacityInactive(qRound(opacity() * 100.0));
|
|
|
|
setOpacity(ruledOpacity / 100.0);
|
Use nullptr everywhere
Summary:
Because KWin is a very old project, we use three kinds of null pointer
literals: 0, NULL, and nullptr. Since C++11, it's recommended to use
nullptr keyword.
This change converts all usages of 0 and NULL literal to nullptr. Even
though it breaks git history, we need to do it in order to have consistent
code as well to ease code reviews (it's very tempting for some people to
add unrelated changes to their patches, e.g. converting NULL to nullptr).
Test Plan: Compiles.
Reviewers: #kwin, davidedmundson, romangg
Reviewed By: #kwin, davidedmundson, romangg
Subscribers: romangg, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D23618
2019-09-19 14:46:54 +00:00
|
|
|
workspace()->setActiveClient(act ? this : nullptr);
|
2015-03-12 15:08:19 +00:00
|
|
|
|
|
|
|
if (!m_active)
|
|
|
|
cancelAutoRaise();
|
|
|
|
|
|
|
|
if (!m_active && shadeMode() == ShadeActivated)
|
|
|
|
setShade(ShadeNormal);
|
|
|
|
|
2015-10-01 15:21:56 +00:00
|
|
|
StackingUpdatesBlocker blocker(workspace());
|
|
|
|
workspace()->updateClientLayer(this); // active windows may get different layer
|
|
|
|
auto mainclients = mainClients();
|
|
|
|
for (auto it = mainclients.constBegin();
|
|
|
|
it != mainclients.constEnd();
|
|
|
|
++it)
|
|
|
|
if ((*it)->isFullScreen()) // fullscreens go high even if their transient is active
|
|
|
|
workspace()->updateClientLayer(*it);
|
|
|
|
|
2015-03-12 15:08:19 +00:00
|
|
|
doSetActive();
|
|
|
|
emit activeChanged();
|
|
|
|
updateMouseGrab();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::doSetActive()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-07-16 07:17:19 +00:00
|
|
|
bool AbstractClient::isZombie() const
|
|
|
|
{
|
|
|
|
return m_zombie;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::markAsZombie()
|
|
|
|
{
|
|
|
|
Q_ASSERT(!m_zombie);
|
|
|
|
m_zombie = true;
|
2021-02-05 12:46:22 +00:00
|
|
|
addWorkspaceRepaint(visibleGeometry());
|
2020-07-16 07:17:19 +00:00
|
|
|
}
|
|
|
|
|
2015-09-17 09:06:59 +00:00
|
|
|
Layer AbstractClient::layer() const
|
|
|
|
{
|
|
|
|
if (m_layer == UnknownLayer)
|
|
|
|
const_cast< AbstractClient* >(this)->m_layer = belongsToLayer();
|
|
|
|
return m_layer;
|
|
|
|
}
|
|
|
|
|
2015-03-13 08:26:09 +00:00
|
|
|
void AbstractClient::updateLayer()
|
2015-09-14 10:04:23 +00:00
|
|
|
{
|
2015-09-17 09:06:59 +00:00
|
|
|
if (layer() == belongsToLayer())
|
|
|
|
return;
|
2015-09-14 10:04:23 +00:00
|
|
|
StackingUpdatesBlocker blocker(workspace());
|
|
|
|
invalidateLayer(); // invalidate, will be updated when doing restacking
|
|
|
|
for (auto it = transients().constBegin(),
|
|
|
|
end = transients().constEnd(); it != end; ++it)
|
|
|
|
(*it)->updateLayer();
|
|
|
|
}
|
|
|
|
|
2020-03-04 07:55:26 +00:00
|
|
|
void AbstractClient::placeIn(const QRect &area)
|
|
|
|
{
|
|
|
|
// TODO: Get rid of this method eventually. We need to call setGeometryRestore() because
|
|
|
|
// checkWorkspacePosition() operates on geometryRestore() and because of quick tiling.
|
|
|
|
Placement::self()->place(this, area);
|
|
|
|
setGeometryRestore(frameGeometry());
|
|
|
|
}
|
|
|
|
|
2015-09-14 10:04:23 +00:00
|
|
|
void AbstractClient::invalidateLayer()
|
2015-03-13 08:26:09 +00:00
|
|
|
{
|
2015-09-17 09:06:59 +00:00
|
|
|
m_layer = UnknownLayer;
|
|
|
|
}
|
|
|
|
|
|
|
|
Layer AbstractClient::belongsToLayer() const
|
|
|
|
{
|
|
|
|
// NOTICE while showingDesktop, desktops move to the AboveLayer
|
|
|
|
// (interchangeable w/ eg. yakuake etc. which will at first remain visible)
|
|
|
|
// and the docks move into the NotificationLayer (which is between Above- and
|
|
|
|
// ActiveLayer, so that active fullscreen windows will still cover everything)
|
|
|
|
// Since the desktop is also activated, nothing should be in the ActiveLayer, though
|
2019-05-10 07:47:05 +00:00
|
|
|
if (isInternal())
|
|
|
|
return UnmanagedLayer;
|
2020-05-08 08:26:27 +00:00
|
|
|
if (isLockScreen())
|
|
|
|
return UnmanagedLayer;
|
2020-08-06 14:20:00 +00:00
|
|
|
if (isInputMethod())
|
|
|
|
return UnmanagedLayer;
|
2015-09-17 09:06:59 +00:00
|
|
|
if (isDesktop())
|
|
|
|
return workspace()->showingDesktop() ? AboveLayer : DesktopLayer;
|
|
|
|
if (isSplash()) // no damn annoying splashscreens
|
|
|
|
return NormalLayer; // getting in the way of everything else
|
|
|
|
if (isDock()) {
|
|
|
|
if (workspace()->showingDesktop())
|
|
|
|
return NotificationLayer;
|
|
|
|
return layerForDock();
|
|
|
|
}
|
|
|
|
if (isOnScreenDisplay())
|
|
|
|
return OnScreenDisplayLayer;
|
|
|
|
if (isNotification())
|
|
|
|
return NotificationLayer;
|
2019-05-02 08:29:38 +00:00
|
|
|
if (isCriticalNotification())
|
|
|
|
return CriticalNotificationLayer;
|
2015-09-17 09:06:59 +00:00
|
|
|
if (workspace()->showingDesktop() && belongsToDesktop()) {
|
|
|
|
return AboveLayer;
|
|
|
|
}
|
|
|
|
if (keepBelow())
|
|
|
|
return BelowLayer;
|
|
|
|
if (isActiveFullScreen())
|
|
|
|
return ActiveLayer;
|
|
|
|
if (keepAbove())
|
2019-04-16 08:06:41 +00:00
|
|
|
return AboveLayer;
|
2015-09-17 09:06:59 +00:00
|
|
|
|
|
|
|
return NormalLayer;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AbstractClient::belongsToDesktop() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Layer AbstractClient::layerForDock() const
|
|
|
|
{
|
|
|
|
// slight hack for the 'allow window to cover panel' Kicker setting
|
|
|
|
// don't move keepbelow docks below normal window, but only to the same
|
|
|
|
// layer, so that both may be raised to cover the other
|
|
|
|
if (keepBelow())
|
|
|
|
return NormalLayer;
|
|
|
|
if (keepAbove()) // slight hack for the autohiding panels
|
|
|
|
return AboveLayer;
|
|
|
|
return DockLayer;
|
2015-03-13 08:26:09 +00:00
|
|
|
}
|
|
|
|
|
2015-03-13 08:36:43 +00:00
|
|
|
void AbstractClient::setKeepAbove(bool b)
|
|
|
|
{
|
|
|
|
b = rules()->checkKeepAbove(b);
|
|
|
|
if (b && !rules()->checkKeepBelow(false))
|
|
|
|
setKeepBelow(false);
|
|
|
|
if (b == keepAbove()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_keepAbove = b;
|
2020-02-03 00:10:12 +00:00
|
|
|
doSetKeepAbove();
|
2015-03-13 08:36:43 +00:00
|
|
|
workspace()->updateClientLayer(this);
|
|
|
|
updateWindowRules(Rules::Above);
|
|
|
|
|
|
|
|
emit keepAboveChanged(m_keepAbove);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::doSetKeepAbove()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::setKeepBelow(bool b)
|
|
|
|
{
|
|
|
|
b = rules()->checkKeepBelow(b);
|
|
|
|
if (b && !rules()->checkKeepAbove(false))
|
|
|
|
setKeepAbove(false);
|
|
|
|
if (b == keepBelow()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_keepBelow = b;
|
2020-02-03 00:10:12 +00:00
|
|
|
doSetKeepBelow();
|
2015-03-13 08:36:43 +00:00
|
|
|
workspace()->updateClientLayer(this);
|
|
|
|
updateWindowRules(Rules::Below);
|
|
|
|
|
|
|
|
emit keepBelowChanged(m_keepBelow);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::doSetKeepBelow()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-03-13 09:26:10 +00:00
|
|
|
void AbstractClient::startAutoRaise()
|
|
|
|
{
|
|
|
|
delete m_autoRaiseTimer;
|
|
|
|
m_autoRaiseTimer = new QTimer(this);
|
|
|
|
connect(m_autoRaiseTimer, &QTimer::timeout, this, &AbstractClient::autoRaise);
|
|
|
|
m_autoRaiseTimer->setSingleShot(true);
|
|
|
|
m_autoRaiseTimer->start(options->autoRaiseInterval());
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::cancelAutoRaise()
|
|
|
|
{
|
|
|
|
delete m_autoRaiseTimer;
|
|
|
|
m_autoRaiseTimer = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::autoRaise()
|
|
|
|
{
|
|
|
|
workspace()->raiseClient(this);
|
|
|
|
cancelAutoRaise();
|
|
|
|
}
|
|
|
|
|
2020-07-15 09:14:50 +00:00
|
|
|
bool AbstractClient::isMostRecentlyRaised() const
|
|
|
|
{
|
|
|
|
// The last toplevel in the unconstrained stacking order is the most recently raised one.
|
|
|
|
return workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), -1, true, false) == this;
|
|
|
|
}
|
|
|
|
|
2015-03-13 09:31:35 +00:00
|
|
|
bool AbstractClient::wantsTabFocus() const
|
|
|
|
{
|
|
|
|
return (isNormalWindow() || isDialog()) && wantsInput();
|
|
|
|
}
|
|
|
|
|
2015-03-13 09:33:31 +00:00
|
|
|
bool AbstractClient::isSpecialWindow() const
|
|
|
|
{
|
|
|
|
// TODO
|
2019-05-02 08:29:38 +00:00
|
|
|
return isDesktop() || isDock() || isSplash() || isToolbar() || isNotification() || isOnScreenDisplay() || isCriticalNotification();
|
2015-03-13 09:33:31 +00:00
|
|
|
}
|
|
|
|
|
2015-03-13 10:19:46 +00:00
|
|
|
void AbstractClient::demandAttention(bool set)
|
|
|
|
{
|
|
|
|
if (isActive())
|
|
|
|
set = false;
|
|
|
|
if (m_demandsAttention == set)
|
|
|
|
return;
|
|
|
|
m_demandsAttention = set;
|
2020-02-02 23:23:47 +00:00
|
|
|
doSetDemandsAttention();
|
2015-03-13 10:19:46 +00:00
|
|
|
workspace()->clientAttentionChanged(this, set);
|
|
|
|
emit demandsAttentionChanged();
|
|
|
|
}
|
|
|
|
|
2020-02-02 23:23:47 +00:00
|
|
|
void AbstractClient::doSetDemandsAttention()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-03-13 11:54:11 +00:00
|
|
|
void AbstractClient::setDesktop(int desktop)
|
|
|
|
{
|
|
|
|
const int numberOfDesktops = VirtualDesktopManager::self()->count();
|
|
|
|
if (desktop != NET::OnAllDesktops) // Do range check
|
|
|
|
desktop = qMax(1, qMin(numberOfDesktops, desktop));
|
|
|
|
desktop = qMin(numberOfDesktops, rules()->checkDesktop(desktop));
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
|
2018-11-13 15:59:54 +00:00
|
|
|
QVector<VirtualDesktop *> desktops;
|
|
|
|
if (desktop != NET::OnAllDesktops) {
|
|
|
|
desktops << VirtualDesktopManager::self()->desktopForX11Id(desktop);
|
|
|
|
}
|
|
|
|
setDesktops(desktops);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::setDesktops(QVector<VirtualDesktop*> desktops)
|
|
|
|
{
|
|
|
|
//on x11 we can have only one desktop at a time
|
|
|
|
if (kwinApp()->operationMode() == Application::OperationModeX11 && desktops.size() > 1) {
|
2019-01-17 23:43:11 +00:00
|
|
|
desktops = QVector<VirtualDesktop*>({desktops.last()});
|
2018-11-13 15:59:54 +00:00
|
|
|
}
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
|
2018-11-13 15:59:54 +00:00
|
|
|
if (desktops == m_desktops) {
|
2015-03-13 11:54:11 +00:00
|
|
|
return;
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
}
|
2015-03-13 11:54:11 +00:00
|
|
|
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
int was_desk = AbstractClient::desktop();
|
|
|
|
const bool wasOnCurrentDesktop = isOnCurrentDesktop() && was_desk >= 0;
|
|
|
|
|
2018-11-13 15:59:54 +00:00
|
|
|
m_desktops = desktops;
|
|
|
|
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
if (windowManagementInterface()) {
|
|
|
|
if (m_desktops.isEmpty()) {
|
|
|
|
windowManagementInterface()->setOnAllDesktops(true);
|
|
|
|
} else {
|
2018-11-13 15:59:54 +00:00
|
|
|
windowManagementInterface()->setOnAllDesktops(false);
|
|
|
|
auto currentDesktops = windowManagementInterface()->plasmaVirtualDesktops();
|
|
|
|
for (auto desktop: m_desktops) {
|
|
|
|
if (!currentDesktops.contains(desktop->id())) {
|
|
|
|
windowManagementInterface()->addPlasmaVirtualDesktop(desktop->id());
|
|
|
|
} else {
|
|
|
|
currentDesktops.removeOne(desktop->id());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (auto desktopId: currentDesktops) {
|
|
|
|
windowManagementInterface()->removePlasmaVirtualDesktop(desktopId);
|
|
|
|
}
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
}
|
|
|
|
}
|
2015-09-14 09:40:48 +00:00
|
|
|
if (info) {
|
2018-11-13 15:59:54 +00:00
|
|
|
info->setDesktop(desktop());
|
2015-09-14 09:40:48 +00:00
|
|
|
}
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
|
2018-11-13 15:59:54 +00:00
|
|
|
if ((was_desk == NET::OnAllDesktops) != (desktop() == NET::OnAllDesktops)) {
|
2015-09-14 09:40:48 +00:00
|
|
|
// onAllDesktops changed
|
|
|
|
workspace()->updateOnAllDesktopsOfTransients(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto transients_stacking_order = workspace()->ensureStackingOrder(transients());
|
|
|
|
for (auto it = transients_stacking_order.constBegin();
|
|
|
|
it != transients_stacking_order.constEnd();
|
|
|
|
++it)
|
2018-11-13 15:59:54 +00:00
|
|
|
(*it)->setDesktops(desktops);
|
2015-09-14 09:40:48 +00:00
|
|
|
|
|
|
|
if (isModal()) // if a modal dialog is moved, move the mainwindow with it as otherwise
|
|
|
|
// the (just moved) modal dialog will confusingly return to the mainwindow with
|
|
|
|
// the next desktop change
|
|
|
|
{
|
|
|
|
foreach (AbstractClient * c2, mainClients())
|
2018-11-13 15:59:54 +00:00
|
|
|
c2->setDesktops(desktops);
|
2015-09-14 09:40:48 +00:00
|
|
|
}
|
|
|
|
|
2020-02-03 00:19:46 +00:00
|
|
|
doSetDesktop();
|
2015-03-13 11:54:11 +00:00
|
|
|
|
|
|
|
FocusChain::self()->update(this, FocusChain::MakeFirst);
|
|
|
|
updateWindowRules(Rules::Desktop);
|
|
|
|
|
|
|
|
emit desktopChanged();
|
|
|
|
if (wasOnCurrentDesktop != isOnCurrentDesktop())
|
|
|
|
emit desktopPresenceChanged(this, was_desk);
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
emit x11DesktopIdsChanged();
|
2015-03-13 11:54:11 +00:00
|
|
|
}
|
|
|
|
|
2020-02-03 00:19:46 +00:00
|
|
|
void AbstractClient::doSetDesktop()
|
2015-03-13 11:54:11 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2021-03-09 14:04:20 +00:00
|
|
|
void AbstractClient::doSetOnActivities(const QStringList &activityList)
|
|
|
|
{
|
|
|
|
Q_UNUSED(activityList);
|
|
|
|
}
|
|
|
|
|
2018-11-13 16:18:46 +00:00
|
|
|
void AbstractClient::enterDesktop(VirtualDesktop *virtualDesktop)
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
{
|
2018-11-13 16:18:46 +00:00
|
|
|
if (m_desktops.contains(virtualDesktop)) {
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
return;
|
|
|
|
}
|
2018-11-13 16:18:46 +00:00
|
|
|
auto desktops = m_desktops;
|
|
|
|
desktops.append(virtualDesktop);
|
|
|
|
setDesktops(desktops);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::leaveDesktop(VirtualDesktop *virtualDesktop)
|
|
|
|
{
|
|
|
|
QVector<VirtualDesktop*> currentDesktops;
|
|
|
|
if (m_desktops.isEmpty()) {
|
|
|
|
currentDesktops = VirtualDesktopManager::self()->desktops();
|
|
|
|
} else {
|
|
|
|
currentDesktops = m_desktops;
|
|
|
|
}
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
|
2018-11-13 16:18:46 +00:00
|
|
|
if (!currentDesktops.contains(virtualDesktop)) {
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
return;
|
|
|
|
}
|
2018-11-13 16:18:46 +00:00
|
|
|
auto desktops = currentDesktops;
|
2018-11-13 15:59:54 +00:00
|
|
|
desktops.removeOne(virtualDesktop);
|
|
|
|
setDesktops(desktops);
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
}
|
|
|
|
|
2015-03-13 11:54:11 +00:00
|
|
|
void AbstractClient::setOnAllDesktops(bool b)
|
|
|
|
{
|
|
|
|
if ((b && isOnAllDesktops()) ||
|
|
|
|
(!b && !isOnAllDesktops()))
|
|
|
|
return;
|
|
|
|
if (b)
|
|
|
|
setDesktop(NET::OnAllDesktops);
|
|
|
|
else
|
|
|
|
setDesktop(VirtualDesktopManager::self()->current());
|
|
|
|
}
|
|
|
|
|
2018-11-13 16:18:46 +00:00
|
|
|
QVector<uint> AbstractClient::x11DesktopIds() const
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
{
|
|
|
|
const auto desks = desktops();
|
2018-11-13 16:18:46 +00:00
|
|
|
QVector<uint> x11Ids;
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
x11Ids.reserve(desks.count());
|
|
|
|
std::transform(desks.constBegin(), desks.constEnd(),
|
|
|
|
std::back_inserter(x11Ids),
|
|
|
|
[] (const VirtualDesktop *vd) {
|
|
|
|
return vd->x11DesktopNumber();
|
|
|
|
}
|
|
|
|
);
|
|
|
|
return x11Ids;
|
|
|
|
}
|
|
|
|
|
2020-05-07 16:26:24 +00:00
|
|
|
ShadeMode AbstractClient::shadeMode() const
|
|
|
|
{
|
|
|
|
return m_shadeMode;
|
|
|
|
}
|
|
|
|
|
2015-03-13 12:39:53 +00:00
|
|
|
bool AbstractClient::isShadeable() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::setShade(bool set)
|
|
|
|
{
|
|
|
|
set ? setShade(ShadeNormal) : setShade(ShadeNone);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::setShade(ShadeMode mode)
|
|
|
|
{
|
2020-05-07 16:26:24 +00:00
|
|
|
if (!isShadeable())
|
|
|
|
return;
|
|
|
|
if (mode == ShadeHover && isMove())
|
|
|
|
return; // causes geometry breaks and is probably nasty
|
|
|
|
if (isSpecialWindow() || noBorder())
|
|
|
|
mode = ShadeNone;
|
|
|
|
|
|
|
|
mode = rules()->checkShade(mode);
|
|
|
|
if (m_shadeMode == mode)
|
|
|
|
return;
|
|
|
|
|
|
|
|
const bool wasShade = isShade();
|
|
|
|
const ShadeMode previousShadeMode = shadeMode();
|
|
|
|
m_shadeMode = mode;
|
|
|
|
|
|
|
|
if (wasShade == isShade()) {
|
|
|
|
// Decoration may want to update after e.g. hover-shade changes
|
|
|
|
emit shadeChanged();
|
|
|
|
return; // No real change in shaded state
|
|
|
|
}
|
|
|
|
|
|
|
|
Q_ASSERT(isDecorated());
|
|
|
|
GeometryUpdatesBlocker blocker(this);
|
|
|
|
|
|
|
|
doSetShade(previousShadeMode);
|
|
|
|
|
|
|
|
discardWindowPixmap();
|
|
|
|
updateWindowRules(Rules::Shade);
|
|
|
|
|
|
|
|
emit shadeChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::doSetShade(ShadeMode previousShadeMode)
|
|
|
|
{
|
|
|
|
Q_UNUSED(previousShadeMode)
|
2015-03-13 12:39:53 +00:00
|
|
|
}
|
|
|
|
|
2020-05-07 16:26:24 +00:00
|
|
|
void AbstractClient::shadeHover()
|
|
|
|
{
|
|
|
|
setShade(ShadeHover);
|
|
|
|
cancelShadeHoverTimer();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::shadeUnhover()
|
2015-03-13 12:39:53 +00:00
|
|
|
{
|
2020-05-07 16:26:24 +00:00
|
|
|
setShade(ShadeNormal);
|
|
|
|
cancelShadeHoverTimer();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::startShadeHoverTimer()
|
|
|
|
{
|
|
|
|
if (!isShade())
|
|
|
|
return;
|
|
|
|
m_shadeHoverTimer = new QTimer(this);
|
|
|
|
connect(m_shadeHoverTimer, &QTimer::timeout, this, &AbstractClient::shadeHover);
|
|
|
|
m_shadeHoverTimer->setSingleShot(true);
|
|
|
|
m_shadeHoverTimer->start(options->shadeHoverInterval());
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::startShadeUnhoverTimer()
|
|
|
|
{
|
|
|
|
if (m_shadeMode == ShadeHover && !isMoveResize() && !isMoveResizePointerButtonDown()) {
|
|
|
|
m_shadeHoverTimer = new QTimer(this);
|
|
|
|
connect(m_shadeHoverTimer, &QTimer::timeout, this, &AbstractClient::shadeUnhover);
|
|
|
|
m_shadeHoverTimer->setSingleShot(true);
|
|
|
|
m_shadeHoverTimer->start(options->shadeHoverInterval());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::cancelShadeHoverTimer()
|
|
|
|
{
|
|
|
|
delete m_shadeHoverTimer;
|
|
|
|
m_shadeHoverTimer = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::toggleShade()
|
|
|
|
{
|
|
|
|
// If the mode is ShadeHover or ShadeActive, cancel shade too.
|
|
|
|
setShade(shadeMode() == ShadeNone ? ShadeNormal : ShadeNone);
|
2015-03-13 12:39:53 +00:00
|
|
|
}
|
|
|
|
|
2015-03-13 12:54:30 +00:00
|
|
|
AbstractClient::Position AbstractClient::titlebarPosition() const
|
|
|
|
{
|
|
|
|
// TODO: still needed, remove?
|
|
|
|
return PositionTop;
|
|
|
|
}
|
|
|
|
|
2016-05-12 08:28:40 +00:00
|
|
|
bool AbstractClient::titlebarPositionUnderMouse() const
|
|
|
|
{
|
|
|
|
if (!isDecorated()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto sectionUnderMouse = decoration()->sectionUnderMouse();
|
|
|
|
if (sectionUnderMouse == Qt::TitleBarArea) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// check other sections based on titlebarPosition
|
|
|
|
switch (titlebarPosition()) {
|
|
|
|
case AbstractClient::PositionTop:
|
|
|
|
return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::TopSection || sectionUnderMouse == Qt::TopRightSection);
|
|
|
|
case AbstractClient::PositionLeft:
|
|
|
|
return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::LeftSection || sectionUnderMouse == Qt::BottomLeftSection);
|
|
|
|
case AbstractClient::PositionRight:
|
|
|
|
return (sectionUnderMouse == Qt::BottomRightSection || sectionUnderMouse == Qt::RightSection || sectionUnderMouse == Qt::TopRightSection);
|
|
|
|
case AbstractClient::PositionBottom:
|
|
|
|
return (sectionUnderMouse == Qt::BottomLeftSection || sectionUnderMouse == Qt::BottomSection || sectionUnderMouse == Qt::BottomRightSection);
|
|
|
|
default:
|
|
|
|
// nothing
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-13 13:45:21 +00:00
|
|
|
void AbstractClient::setMinimized(bool set)
|
|
|
|
{
|
|
|
|
set ? minimize() : unminimize();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::minimize(bool avoid_animation)
|
|
|
|
{
|
|
|
|
if (!isMinimizable() || isMinimized())
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_minimized = true;
|
|
|
|
doMinimize();
|
|
|
|
|
|
|
|
updateWindowRules(Rules::Minimize);
|
2020-10-20 20:15:46 +00:00
|
|
|
|
|
|
|
if (options->moveMinimizedWindowsToEndOfTabBoxFocusChain()) {
|
|
|
|
FocusChain::self()->update(this, FocusChain::MakeFirstMinimized);
|
|
|
|
}
|
|
|
|
|
2015-03-13 13:45:21 +00:00
|
|
|
// TODO: merge signal with s_minimized
|
2021-02-05 12:46:22 +00:00
|
|
|
addWorkspaceRepaint(visibleGeometry());
|
2015-03-13 13:45:21 +00:00
|
|
|
emit clientMinimized(this, !avoid_animation);
|
|
|
|
emit minimizedChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::unminimize(bool avoid_animation)
|
|
|
|
{
|
|
|
|
if (!isMinimized())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (rules()->checkMinimize(false)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_minimized = false;
|
|
|
|
doMinimize();
|
|
|
|
|
|
|
|
updateWindowRules(Rules::Minimize);
|
|
|
|
emit clientUnminimized(this, !avoid_animation);
|
|
|
|
emit minimizedChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::doMinimize()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-04-29 10:02:54 +00:00
|
|
|
QPalette AbstractClient::palette() const
|
|
|
|
{
|
|
|
|
if (!m_palette) {
|
|
|
|
return QPalette();
|
|
|
|
}
|
|
|
|
return m_palette->palette();
|
|
|
|
}
|
|
|
|
|
|
|
|
const Decoration::DecorationPalette *AbstractClient::decorationPalette() const
|
|
|
|
{
|
|
|
|
return m_palette.get();
|
|
|
|
}
|
|
|
|
|
2020-08-19 09:21:00 +00:00
|
|
|
QString AbstractClient::preferredColorScheme() const
|
2015-04-29 10:02:54 +00:00
|
|
|
{
|
2020-08-19 09:21:00 +00:00
|
|
|
return rules()->checkDecoColor(QString());
|
|
|
|
}
|
|
|
|
|
|
|
|
QString AbstractClient::colorScheme() const
|
|
|
|
{
|
|
|
|
return m_colorScheme;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::setColorScheme(const QString &colorScheme)
|
|
|
|
{
|
|
|
|
QString requestedColorScheme = colorScheme;
|
|
|
|
if (requestedColorScheme.isEmpty()) {
|
|
|
|
requestedColorScheme = QStringLiteral("kdeglobals");
|
2015-04-29 10:02:54 +00:00
|
|
|
}
|
|
|
|
|
2020-08-19 09:21:00 +00:00
|
|
|
if (!m_palette || m_colorScheme != requestedColorScheme) {
|
|
|
|
m_colorScheme = requestedColorScheme;
|
2015-04-29 10:02:54 +00:00
|
|
|
|
|
|
|
if (m_palette) {
|
|
|
|
disconnect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &AbstractClient::handlePaletteChange);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto it = s_palettes.find(m_colorScheme);
|
|
|
|
|
|
|
|
if (it == s_palettes.end() || it->expired()) {
|
|
|
|
m_palette = std::make_shared<Decoration::DecorationPalette>(m_colorScheme);
|
|
|
|
if (m_palette->isValid()) {
|
|
|
|
s_palettes[m_colorScheme] = m_palette;
|
|
|
|
} else {
|
|
|
|
if (!s_defaultPalette) {
|
|
|
|
s_defaultPalette = std::make_shared<Decoration::DecorationPalette>(QStringLiteral("kdeglobals"));
|
|
|
|
s_palettes[QStringLiteral("kdeglobals")] = s_defaultPalette;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_palette = s_defaultPalette;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_colorScheme == QStringLiteral("kdeglobals")) {
|
|
|
|
s_defaultPalette = m_palette;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
m_palette = it->lock();
|
|
|
|
}
|
|
|
|
|
|
|
|
connect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &AbstractClient::handlePaletteChange);
|
|
|
|
|
|
|
|
emit paletteChanged(palette());
|
2018-10-08 08:32:57 +00:00
|
|
|
emit colorSchemeChanged();
|
2015-04-29 10:02:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-19 09:21:00 +00:00
|
|
|
void AbstractClient::updateColorScheme()
|
|
|
|
{
|
|
|
|
setColorScheme(preferredColorScheme());
|
|
|
|
}
|
|
|
|
|
2015-04-29 10:02:54 +00:00
|
|
|
void AbstractClient::handlePaletteChange()
|
|
|
|
{
|
|
|
|
emit paletteChanged(palette());
|
|
|
|
}
|
|
|
|
|
2015-05-27 09:51:45 +00:00
|
|
|
void AbstractClient::keepInArea(QRect area, bool partial)
|
|
|
|
{
|
|
|
|
if (partial) {
|
|
|
|
// increase the area so that can have only 100 pixels in the area
|
|
|
|
area.setLeft(qMin(area.left() - width() + 100, area.left()));
|
|
|
|
area.setTop(qMin(area.top() - height() + 100, area.top()));
|
|
|
|
area.setRight(qMax(area.right() + width() - 100, area.right()));
|
|
|
|
area.setBottom(qMax(area.bottom() + height() - 100, area.bottom()));
|
|
|
|
}
|
|
|
|
if (!partial) {
|
|
|
|
// resize to fit into area
|
|
|
|
if (area.width() < width() || area.height() < height())
|
2020-03-25 15:15:23 +00:00
|
|
|
resizeWithChecks(size().boundedTo(area.size()));
|
2015-05-27 09:51:45 +00:00
|
|
|
}
|
|
|
|
int tx = x(), ty = y();
|
2019-09-27 10:01:10 +00:00
|
|
|
if (frameGeometry().right() > area.right() && width() <= area.width())
|
2015-05-27 09:51:45 +00:00
|
|
|
tx = area.right() - width() + 1;
|
2019-09-27 10:01:10 +00:00
|
|
|
if (frameGeometry().bottom() > area.bottom() && height() <= area.height())
|
2015-05-27 09:51:45 +00:00
|
|
|
ty = area.bottom() - height() + 1;
|
2019-09-27 10:01:10 +00:00
|
|
|
if (!area.contains(frameGeometry().topLeft())) {
|
2015-05-27 09:51:45 +00:00
|
|
|
if (tx < area.x())
|
|
|
|
tx = area.x();
|
|
|
|
if (ty < area.y())
|
|
|
|
ty = area.y();
|
|
|
|
}
|
|
|
|
if (tx != x() || ty != y())
|
|
|
|
move(tx, ty);
|
|
|
|
}
|
|
|
|
|
2020-02-12 10:40:32 +00:00
|
|
|
/**
|
|
|
|
* Returns the maximum client size, not the maximum frame size.
|
|
|
|
*/
|
2015-05-27 11:21:56 +00:00
|
|
|
QSize AbstractClient::maxSize() const
|
|
|
|
{
|
|
|
|
return rules()->checkMaxSize(QSize(INT_MAX, INT_MAX));
|
|
|
|
}
|
|
|
|
|
2020-02-12 10:40:32 +00:00
|
|
|
/**
|
|
|
|
* Returns the minimum client size, not the minimum frame size.
|
|
|
|
*/
|
2015-05-27 11:21:56 +00:00
|
|
|
QSize AbstractClient::minSize() const
|
|
|
|
{
|
|
|
|
return rules()->checkMinSize(QSize(0, 0));
|
|
|
|
}
|
|
|
|
|
2020-01-13 18:57:07 +00:00
|
|
|
void AbstractClient::blockGeometryUpdates(bool block)
|
|
|
|
{
|
|
|
|
if (block) {
|
|
|
|
if (m_blockGeometryUpdates == 0)
|
|
|
|
m_pendingGeometryUpdate = PendingGeometryNone;
|
|
|
|
++m_blockGeometryUpdates;
|
|
|
|
} else {
|
|
|
|
if (--m_blockGeometryUpdates == 0) {
|
|
|
|
if (m_pendingGeometryUpdate != PendingGeometryNone) {
|
|
|
|
if (isShade())
|
|
|
|
setFrameGeometry(QRect(pos(), adjustedSize()), NormalGeometrySet);
|
|
|
|
else
|
|
|
|
setFrameGeometry(frameGeometry(), NormalGeometrySet);
|
|
|
|
m_pendingGeometryUpdate = PendingGeometryNone;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::maximize(MaximizeMode m)
|
|
|
|
{
|
|
|
|
setMaximize(m & MaximizeVertical, m & MaximizeHorizontal);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::setMaximize(bool vertically, bool horizontally)
|
|
|
|
{
|
|
|
|
// changeMaximize() flips the state, so change from set->flip
|
2020-10-14 18:53:07 +00:00
|
|
|
const MaximizeMode oldMode = requestedMaximizeMode();
|
2020-01-13 18:57:07 +00:00
|
|
|
changeMaximize(
|
|
|
|
oldMode & MaximizeHorizontal ? !horizontally : horizontally,
|
|
|
|
oldMode & MaximizeVertical ? !vertically : vertically,
|
|
|
|
false);
|
|
|
|
const MaximizeMode newMode = maximizeMode();
|
|
|
|
if (oldMode != newMode) {
|
|
|
|
emit clientMaximizedStateChanged(this, newMode);
|
|
|
|
emit clientMaximizedStateChanged(this, vertically, horizontally);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::move(int x, int y, ForceGeometry_t force)
|
|
|
|
{
|
|
|
|
// resuming geometry updates is handled only in setGeometry()
|
|
|
|
Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked());
|
|
|
|
QPoint p(x, y);
|
|
|
|
if (!areGeometryUpdatesBlocked() && p != rules()->checkPosition(p)) {
|
|
|
|
qCDebug(KWIN_CORE) << "forced position fail:" << p << ":" << rules()->checkPosition(p);
|
|
|
|
}
|
|
|
|
if (force == NormalGeometrySet && m_frameGeometry.topLeft() == p)
|
|
|
|
return;
|
|
|
|
m_frameGeometry.moveTopLeft(p);
|
|
|
|
if (areGeometryUpdatesBlocked()) {
|
|
|
|
if (pendingGeometryUpdate() == PendingGeometryForced)
|
|
|
|
{} // maximum, nothing needed
|
|
|
|
else if (force == ForceGeometrySet)
|
|
|
|
setPendingGeometryUpdate(PendingGeometryForced);
|
|
|
|
else
|
|
|
|
setPendingGeometryUpdate(PendingGeometryNormal);
|
|
|
|
return;
|
|
|
|
}
|
2020-07-20 19:33:19 +00:00
|
|
|
const QRect oldBufferGeometry = bufferGeometryBeforeUpdateBlocking();
|
|
|
|
const QRect oldClientGeometry = clientGeometryBeforeUpdateBlocking();
|
|
|
|
const QRect oldFrameGeometry = frameGeometryBeforeUpdateBlocking();
|
2020-01-13 18:57:07 +00:00
|
|
|
doMove(x, y);
|
2020-07-20 19:33:19 +00:00
|
|
|
updateGeometryBeforeUpdateBlocking();
|
2020-01-13 18:57:07 +00:00
|
|
|
updateWindowRules(Rules::Position);
|
|
|
|
screens()->setCurrent(this);
|
|
|
|
workspace()->updateStackingOrder();
|
|
|
|
// client itself is not damaged
|
2020-07-20 19:33:19 +00:00
|
|
|
emit bufferGeometryChanged(this, oldBufferGeometry);
|
|
|
|
emit clientGeometryChanged(this, oldClientGeometry);
|
|
|
|
emit frameGeometryChanged(this, oldFrameGeometry);
|
2020-01-13 18:57:07 +00:00
|
|
|
addRepaintDuringGeometryUpdates();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AbstractClient::startMoveResize()
|
|
|
|
{
|
|
|
|
Q_ASSERT(!isMoveResize());
|
|
|
|
Q_ASSERT(QWidget::keyboardGrabber() == nullptr);
|
|
|
|
Q_ASSERT(QWidget::mouseGrabber() == nullptr);
|
|
|
|
stopDelayedMoveResize();
|
|
|
|
if (QApplication::activePopupWidget() != nullptr)
|
|
|
|
return false; // popups have grab
|
|
|
|
if (isFullScreen() && (screens()->count() < 2 || !isMovableAcrossScreens()))
|
|
|
|
return false;
|
|
|
|
if (!doStartMoveResize()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
invalidateDecorationDoubleClickTimer();
|
|
|
|
|
|
|
|
setMoveResize(true);
|
|
|
|
workspace()->setMoveResizeClient(this);
|
|
|
|
|
|
|
|
const Position mode = moveResizePointerMode();
|
|
|
|
if (mode != PositionCenter) { // means "isResize()" but moveResizeMode = true is set below
|
|
|
|
if (maximizeMode() == MaximizeFull) { // partial is cond. reset in finishMoveResize
|
|
|
|
setGeometryRestore(frameGeometry()); // "restore" to current geometry
|
|
|
|
setMaximize(false, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && mode != PositionCenter) { // Cannot use isResize() yet
|
|
|
|
// Exit quick tile mode when the user attempts to resize a tiled window
|
|
|
|
updateQuickTileMode(QuickTileFlag::None); // Do so without restoring original geometry
|
|
|
|
setGeometryRestore(frameGeometry());
|
2020-08-23 11:23:23 +00:00
|
|
|
doSetQuickTileMode();
|
2020-01-13 18:57:07 +00:00
|
|
|
emit quickTileModeChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
updateHaveResizeEffect();
|
|
|
|
updateInitialMoveResizeGeometry();
|
|
|
|
checkUnrestrictedMoveResize();
|
|
|
|
emit clientStartUserMovedResized(this);
|
|
|
|
if (ScreenEdges::self()->isDesktopSwitchingMovingClients())
|
|
|
|
ScreenEdges::self()->reserveDesktopSwitching(true, Qt::Vertical|Qt::Horizontal);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::finishMoveResize(bool cancel)
|
|
|
|
{
|
|
|
|
GeometryUpdatesBlocker blocker(this);
|
|
|
|
const bool wasResize = isResize(); // store across leaveMoveResize
|
|
|
|
leaveMoveResize();
|
|
|
|
|
2020-02-17 18:39:17 +00:00
|
|
|
doFinishMoveResize();
|
|
|
|
|
2020-01-13 18:57:07 +00:00
|
|
|
if (cancel)
|
|
|
|
setFrameGeometry(initialMoveResizeGeometry());
|
|
|
|
else {
|
|
|
|
const QRect &moveResizeGeom = moveResizeGeometry();
|
|
|
|
if (wasResize) {
|
|
|
|
const bool restoreH = maximizeMode() == MaximizeHorizontal &&
|
|
|
|
moveResizeGeom.width() != initialMoveResizeGeometry().width();
|
|
|
|
const bool restoreV = maximizeMode() == MaximizeVertical &&
|
|
|
|
moveResizeGeom.height() != initialMoveResizeGeometry().height();
|
|
|
|
if (restoreH || restoreV) {
|
|
|
|
changeMaximize(restoreH, restoreV, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setFrameGeometry(moveResizeGeom);
|
|
|
|
}
|
|
|
|
checkScreen(); // needs to be done because clientFinishUserMovedResized has not yet re-activated online alignment
|
|
|
|
if (screen() != moveResizeStartScreen()) {
|
2021-01-22 12:08:16 +00:00
|
|
|
if (isFullScreen() || isElectricBorderMaximizing()) {
|
2021-01-22 12:07:52 +00:00
|
|
|
updateGeometryRestoresForFullscreen();
|
|
|
|
}
|
2020-01-13 18:57:07 +00:00
|
|
|
workspace()->sendClientToScreen(this, screen()); // checks rule validity
|
2021-01-20 19:36:07 +00:00
|
|
|
if (maximizeMode() != MaximizeRestore) {
|
2020-01-13 18:57:07 +00:00
|
|
|
checkWorkspacePosition();
|
2021-01-20 19:36:07 +00:00
|
|
|
}
|
2020-01-13 18:57:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (isElectricBorderMaximizing()) {
|
|
|
|
setQuickTileMode(electricBorderMode());
|
|
|
|
setElectricBorderMaximizing(false);
|
2021-01-20 19:36:07 +00:00
|
|
|
} else if (!cancel && !isFullScreen()) {
|
2020-01-13 18:57:07 +00:00
|
|
|
QRect geom_restore = geometryRestore();
|
|
|
|
if (!(maximizeMode() & MaximizeHorizontal)) {
|
|
|
|
geom_restore.setX(frameGeometry().x());
|
|
|
|
geom_restore.setWidth(frameGeometry().width());
|
|
|
|
}
|
|
|
|
if (!(maximizeMode() & MaximizeVertical)) {
|
|
|
|
geom_restore.setY(frameGeometry().y());
|
|
|
|
geom_restore.setHeight(frameGeometry().height());
|
|
|
|
}
|
|
|
|
setGeometryRestore(geom_restore);
|
|
|
|
}
|
|
|
|
// FRAME update();
|
|
|
|
|
|
|
|
emit clientFinishUserMovedResized(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
// This function checks if it actually makes sense to perform a restricted move/resize.
|
|
|
|
// If e.g. the titlebar is already outside of the workarea, there's no point in performing
|
|
|
|
// a restricted move resize, because then e.g. resize would also move the window (#74555).
|
|
|
|
// NOTE: Most of it is duplicated from handleMoveResize().
|
|
|
|
void AbstractClient::checkUnrestrictedMoveResize()
|
|
|
|
{
|
|
|
|
if (isUnrestrictedMoveResize())
|
|
|
|
return;
|
|
|
|
const QRect &moveResizeGeom = moveResizeGeometry();
|
|
|
|
QRect desktopArea = workspace()->clientArea(WorkArea, moveResizeGeom.center(), desktop());
|
|
|
|
int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge;
|
|
|
|
// restricted move/resize - keep at least part of the titlebar always visible
|
|
|
|
// how much must remain visible when moved away in that direction
|
|
|
|
left_marge = qMin(100 + borderRight(), moveResizeGeom.width());
|
|
|
|
right_marge = qMin(100 + borderLeft(), moveResizeGeom.width());
|
|
|
|
// width/height change with opaque resizing, use the initial ones
|
|
|
|
titlebar_marge = initialMoveResizeGeometry().height();
|
|
|
|
top_marge = borderBottom();
|
|
|
|
bottom_marge = borderTop();
|
|
|
|
if (isResize()) {
|
|
|
|
if (moveResizeGeom.bottom() < desktopArea.top() + top_marge)
|
|
|
|
setUnrestrictedMoveResize(true);
|
|
|
|
if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge)
|
|
|
|
setUnrestrictedMoveResize(true);
|
|
|
|
if (moveResizeGeom.right() < desktopArea.left() + left_marge)
|
|
|
|
setUnrestrictedMoveResize(true);
|
|
|
|
if (moveResizeGeom.left() > desktopArea.right() - right_marge)
|
|
|
|
setUnrestrictedMoveResize(true);
|
|
|
|
if (!isUnrestrictedMoveResize() && moveResizeGeom.top() < desktopArea.top()) // titlebar mustn't go out
|
|
|
|
setUnrestrictedMoveResize(true);
|
|
|
|
}
|
|
|
|
if (isMove()) {
|
|
|
|
if (moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1)
|
|
|
|
setUnrestrictedMoveResize(true);
|
|
|
|
// no need to check top_marge, titlebar_marge already handles it
|
|
|
|
if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge + 1) // titlebar mustn't go out
|
|
|
|
setUnrestrictedMoveResize(true);
|
|
|
|
if (moveResizeGeom.right() < desktopArea.left() + left_marge)
|
|
|
|
setUnrestrictedMoveResize(true);
|
|
|
|
if (moveResizeGeom.left() > desktopArea.right() - right_marge)
|
|
|
|
setUnrestrictedMoveResize(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-04 18:48:33 +00:00
|
|
|
// When the user pressed mouse on the titlebar, don't activate move immediately,
|
2020-01-13 18:57:07 +00:00
|
|
|
// since it may be just a click. Activate instead after a delay. Move used to be
|
|
|
|
// activated only after moving by several pixels, but that looks bad.
|
|
|
|
void AbstractClient::startDelayedMoveResize()
|
|
|
|
{
|
|
|
|
Q_ASSERT(!m_moveResize.delayedTimer);
|
|
|
|
m_moveResize.delayedTimer = new QTimer(this);
|
|
|
|
m_moveResize.delayedTimer->setSingleShot(true);
|
|
|
|
connect(m_moveResize.delayedTimer, &QTimer::timeout, this,
|
|
|
|
[this]() {
|
|
|
|
Q_ASSERT(isMoveResizePointerButtonDown());
|
|
|
|
if (!startMoveResize()) {
|
|
|
|
setMoveResizePointerButtonDown(false);
|
|
|
|
}
|
|
|
|
updateCursor();
|
|
|
|
stopDelayedMoveResize();
|
|
|
|
}
|
|
|
|
);
|
|
|
|
m_moveResize.delayedTimer->start(QApplication::startDragTime());
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::stopDelayedMoveResize()
|
|
|
|
{
|
|
|
|
delete m_moveResize.delayedTimer;
|
|
|
|
m_moveResize.delayedTimer = nullptr;
|
|
|
|
}
|
|
|
|
|
2015-06-05 03:06:20 +00:00
|
|
|
void AbstractClient::updateMoveResize(const QPointF ¤tGlobalCursor)
|
|
|
|
{
|
2015-10-23 11:36:53 +00:00
|
|
|
handleMoveResize(pos(), currentGlobalCursor.toPoint());
|
2015-06-05 03:06:20 +00:00
|
|
|
}
|
|
|
|
|
2020-01-13 18:57:07 +00:00
|
|
|
void AbstractClient::handleMoveResize(const QPoint &local, const QPoint &global)
|
|
|
|
{
|
|
|
|
const QRect oldGeo = frameGeometry();
|
|
|
|
handleMoveResize(local.x(), local.y(), global.x(), global.y());
|
|
|
|
if (!isFullScreen() && isMove()) {
|
|
|
|
if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && oldGeo != frameGeometry()) {
|
|
|
|
GeometryUpdatesBlocker blocker(this);
|
|
|
|
setQuickTileMode(QuickTileFlag::None);
|
|
|
|
const QRect &geom_restore = geometryRestore();
|
|
|
|
setMoveOffset(QPoint(double(moveOffset().x()) / double(oldGeo.width()) * double(geom_restore.width()),
|
|
|
|
double(moveOffset().y()) / double(oldGeo.height()) * double(geom_restore.height())));
|
|
|
|
if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore)
|
|
|
|
setMoveResizeGeometry(geom_restore);
|
|
|
|
handleMoveResize(local.x(), local.y(), global.x(), global.y()); // fix position
|
|
|
|
} else if (quickTileMode() == QuickTileMode(QuickTileFlag::None) && isResizable()) {
|
|
|
|
checkQuickTilingMaximizationZones(global.x(), global.y());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::handleMoveResize(int x, int y, int x_root, int y_root)
|
|
|
|
{
|
|
|
|
if (isWaitingForMoveResizeSync())
|
|
|
|
return; // we're still waiting for the client or the timeout
|
|
|
|
|
|
|
|
const Position mode = moveResizePointerMode();
|
|
|
|
if ((mode == PositionCenter && !isMovableAcrossScreens())
|
|
|
|
|| (mode != PositionCenter && (isShade() || !isResizable())))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!isMoveResize()) {
|
|
|
|
QPoint p(QPoint(x/* - padding_left*/, y/* - padding_top*/) - moveOffset());
|
|
|
|
if (p.manhattanLength() >= QApplication::startDragDistance()) {
|
|
|
|
if (!startMoveResize()) {
|
|
|
|
setMoveResizePointerButtonDown(false);
|
|
|
|
updateCursor();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
updateCursor();
|
|
|
|
} else
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ShadeHover or ShadeActive, ShadeNormal was already avoided above
|
|
|
|
if (mode != PositionCenter && shadeMode() != ShadeNone)
|
|
|
|
setShade(ShadeNone);
|
|
|
|
|
|
|
|
QPoint globalPos(x_root, y_root);
|
|
|
|
// these two points limit the geometry rectangle, i.e. if bottomleft resizing is done,
|
|
|
|
// the bottomleft corner should be at is at (topleft.x(), bottomright().y())
|
|
|
|
QPoint topleft = globalPos - moveOffset();
|
|
|
|
QPoint bottomright = globalPos + invertedMoveOffset();
|
|
|
|
QRect previousMoveResizeGeom = moveResizeGeometry();
|
|
|
|
|
|
|
|
// TODO move whole group when moving its leader or when the leader is not mapped?
|
|
|
|
|
|
|
|
auto titleBarRect = [this](bool &transposed, int &requiredPixels) -> QRect {
|
|
|
|
const QRect &moveResizeGeom = moveResizeGeometry();
|
|
|
|
QRect r(moveResizeGeom);
|
|
|
|
r.moveTopLeft(QPoint(0,0));
|
|
|
|
switch (titlebarPosition()) {
|
|
|
|
default:
|
|
|
|
case PositionTop:
|
|
|
|
r.setHeight(borderTop());
|
|
|
|
break;
|
|
|
|
case PositionLeft:
|
|
|
|
r.setWidth(borderLeft());
|
|
|
|
transposed = true;
|
|
|
|
break;
|
|
|
|
case PositionBottom:
|
|
|
|
r.setTop(r.bottom() - borderBottom());
|
|
|
|
break;
|
|
|
|
case PositionRight:
|
|
|
|
r.setLeft(r.right() - borderRight());
|
|
|
|
transposed = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// When doing a restricted move we must always keep 100px of the titlebar
|
|
|
|
// visible to allow the user to be able to move it again.
|
|
|
|
requiredPixels = qMin(100 * (transposed ? r.width() : r.height()),
|
|
|
|
moveResizeGeom.width() * moveResizeGeom.height());
|
|
|
|
return r;
|
|
|
|
};
|
|
|
|
|
|
|
|
bool update = false;
|
|
|
|
if (isResize()) {
|
|
|
|
QRect orig = initialMoveResizeGeometry();
|
2020-01-17 21:30:56 +00:00
|
|
|
SizeMode sizeMode = SizeModeAny;
|
|
|
|
auto calculateMoveResizeGeom = [this, &topleft, &bottomright, &orig, &sizeMode, &mode]() {
|
2020-01-13 18:57:07 +00:00
|
|
|
switch(mode) {
|
|
|
|
case PositionTopLeft:
|
|
|
|
setMoveResizeGeometry(QRect(topleft, orig.bottomRight()));
|
|
|
|
break;
|
|
|
|
case PositionBottomRight:
|
|
|
|
setMoveResizeGeometry(QRect(orig.topLeft(), bottomright));
|
|
|
|
break;
|
|
|
|
case PositionBottomLeft:
|
|
|
|
setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.y()), QPoint(orig.right(), bottomright.y())));
|
|
|
|
break;
|
|
|
|
case PositionTopRight:
|
|
|
|
setMoveResizeGeometry(QRect(QPoint(orig.x(), topleft.y()), QPoint(bottomright.x(), orig.bottom())));
|
|
|
|
break;
|
|
|
|
case PositionTop:
|
|
|
|
setMoveResizeGeometry(QRect(QPoint(orig.left(), topleft.y()), orig.bottomRight()));
|
2020-01-17 21:30:56 +00:00
|
|
|
sizeMode = SizeModeFixedH; // try not to affect height
|
2020-01-13 18:57:07 +00:00
|
|
|
break;
|
|
|
|
case PositionBottom:
|
|
|
|
setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(orig.right(), bottomright.y())));
|
2020-01-17 21:30:56 +00:00
|
|
|
sizeMode = SizeModeFixedH;
|
2020-01-13 18:57:07 +00:00
|
|
|
break;
|
|
|
|
case PositionLeft:
|
|
|
|
setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.top()), orig.bottomRight()));
|
2020-01-17 21:30:56 +00:00
|
|
|
sizeMode = SizeModeFixedW;
|
2020-01-13 18:57:07 +00:00
|
|
|
break;
|
|
|
|
case PositionRight:
|
|
|
|
setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(bottomright.x(), orig.bottom())));
|
2020-01-17 21:30:56 +00:00
|
|
|
sizeMode = SizeModeFixedW;
|
2020-01-13 18:57:07 +00:00
|
|
|
break;
|
|
|
|
case PositionCenter:
|
|
|
|
default:
|
|
|
|
abort();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// first resize (without checking constrains), then snap, then check bounds, then check constrains
|
|
|
|
calculateMoveResizeGeom();
|
|
|
|
// adjust new size to snap to other windows/borders
|
|
|
|
setMoveResizeGeometry(workspace()->adjustClientSize(this, moveResizeGeometry(), mode));
|
|
|
|
|
|
|
|
if (!isUnrestrictedMoveResize()) {
|
|
|
|
// Make sure the titlebar isn't behind a restricted area. We don't need to restrict
|
|
|
|
// the other directions. If not visible enough, move the window to the closest valid
|
|
|
|
// point. We bruteforce this by slowly moving the window back to its previous position
|
|
|
|
QRegion availableArea(workspace()->clientArea(FullArea, -1, 0)); // On the screen
|
|
|
|
availableArea -= workspace()->restrictedMoveArea(desktop()); // Strut areas
|
|
|
|
bool transposed = false;
|
|
|
|
int requiredPixels;
|
|
|
|
QRect bTitleRect = titleBarRect(transposed, requiredPixels);
|
|
|
|
int lastVisiblePixels = -1;
|
|
|
|
QRect lastTry = moveResizeGeometry();
|
|
|
|
bool titleFailed = false;
|
|
|
|
for (;;) {
|
|
|
|
const QRect titleRect(bTitleRect.translated(moveResizeGeometry().topLeft()));
|
|
|
|
int visiblePixels = 0;
|
|
|
|
int realVisiblePixels = 0;
|
|
|
|
for (const QRect &rect : availableArea) {
|
|
|
|
const QRect r = rect & titleRect;
|
|
|
|
realVisiblePixels += r.width() * r.height();
|
|
|
|
if ((transposed && r.width() == titleRect.width()) || // Only the full size regions...
|
|
|
|
(!transposed && r.height() == titleRect.height())) // ...prevents long slim areas
|
|
|
|
visiblePixels += r.width() * r.height();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (visiblePixels >= requiredPixels)
|
|
|
|
break; // We have reached a valid position
|
|
|
|
|
|
|
|
if (realVisiblePixels <= lastVisiblePixels) {
|
|
|
|
if (titleFailed && realVisiblePixels < lastVisiblePixels)
|
|
|
|
break; // we won't become better
|
|
|
|
else {
|
|
|
|
if (!titleFailed)
|
|
|
|
setMoveResizeGeometry(lastTry);
|
|
|
|
titleFailed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lastVisiblePixels = realVisiblePixels;
|
|
|
|
QRect moveResizeGeom = moveResizeGeometry();
|
|
|
|
lastTry = moveResizeGeom;
|
|
|
|
|
|
|
|
// Not visible enough, move the window to the closest valid point. We bruteforce
|
|
|
|
// this by slowly moving the window back to its previous position.
|
|
|
|
// The geometry changes at up to two edges, the one with the title (if) shall take
|
|
|
|
// precedence. The opposing edge has no impact on visiblePixels and only one of
|
|
|
|
// the adjacent can alter at a time, ie. it's enough to ignore adjacent edges
|
|
|
|
// if the title edge altered
|
|
|
|
bool leftChanged = previousMoveResizeGeom.left() != moveResizeGeom.left();
|
|
|
|
bool rightChanged = previousMoveResizeGeom.right() != moveResizeGeom.right();
|
|
|
|
bool topChanged = previousMoveResizeGeom.top() != moveResizeGeom.top();
|
|
|
|
bool btmChanged = previousMoveResizeGeom.bottom() != moveResizeGeom.bottom();
|
|
|
|
auto fixChangedState = [titleFailed](bool &major, bool &counter, bool &ad1, bool &ad2) {
|
|
|
|
counter = false;
|
|
|
|
if (titleFailed)
|
|
|
|
major = false;
|
|
|
|
if (major)
|
|
|
|
ad1 = ad2 = false;
|
|
|
|
};
|
|
|
|
switch (titlebarPosition()) {
|
|
|
|
default:
|
|
|
|
case PositionTop:
|
|
|
|
fixChangedState(topChanged, btmChanged, leftChanged, rightChanged);
|
|
|
|
break;
|
|
|
|
case PositionLeft:
|
|
|
|
fixChangedState(leftChanged, rightChanged, topChanged, btmChanged);
|
|
|
|
break;
|
|
|
|
case PositionBottom:
|
|
|
|
fixChangedState(btmChanged, topChanged, leftChanged, rightChanged);
|
|
|
|
break;
|
|
|
|
case PositionRight:
|
|
|
|
fixChangedState(rightChanged, leftChanged, topChanged, btmChanged);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (topChanged)
|
|
|
|
moveResizeGeom.setTop(moveResizeGeom.y() + sign(previousMoveResizeGeom.y() - moveResizeGeom.y()));
|
|
|
|
else if (leftChanged)
|
|
|
|
moveResizeGeom.setLeft(moveResizeGeom.x() + sign(previousMoveResizeGeom.x() - moveResizeGeom.x()));
|
|
|
|
else if (btmChanged)
|
|
|
|
moveResizeGeom.setBottom(moveResizeGeom.bottom() + sign(previousMoveResizeGeom.bottom() - moveResizeGeom.bottom()));
|
|
|
|
else if (rightChanged)
|
|
|
|
moveResizeGeom.setRight(moveResizeGeom.right() + sign(previousMoveResizeGeom.right() - moveResizeGeom.right()));
|
|
|
|
else
|
|
|
|
break; // no position changed - that's certainly not good
|
|
|
|
setMoveResizeGeometry(moveResizeGeom);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Always obey size hints, even when in "unrestricted" mode
|
Refactor geometry constraints code
Summary:
Currently, there are a couple of issues with sizeForClientSize(). First
of all, we have a method called clientSizeToFrameSize() which does similar
thing except applying geometry constraints and checking window rules. The
other issue is that sizeForClientSize() is doing a bit too much, it checks
window rules, it applies a bunch of geometry constrains. Sometimes it
does not perform conversion between client sizes and frame sizes!
This change attempts to address those issues by replacing sizeForClientSize
with two similar methods and changing semantics of some methods of the
X11Client class.
The most significant difference between sizeForClientSize() and the new
methods is that neither constrainClientSize() nor constrainFrameSize()
check window rules. This is up to users of those methods. In many places,
we don't have to check window rules because we check isResizable(),
which returns false if the frame size is enforced by a window rule.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, romangg, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D26828
2020-02-12 10:38:40 +00:00
|
|
|
QSize size = constrainFrameSize(moveResizeGeometry().size(), sizeMode);
|
2020-01-13 18:57:07 +00:00
|
|
|
// the new topleft and bottomright corners (after checking size constrains), if they'll be needed
|
|
|
|
topleft = QPoint(moveResizeGeometry().right() - size.width() + 1, moveResizeGeometry().bottom() - size.height() + 1);
|
|
|
|
bottomright = QPoint(moveResizeGeometry().left() + size.width() - 1, moveResizeGeometry().top() + size.height() - 1);
|
|
|
|
orig = moveResizeGeometry();
|
|
|
|
|
|
|
|
// if aspect ratios are specified, both dimensions may change.
|
|
|
|
// Therefore grow to the right/bottom if needed.
|
|
|
|
// TODO it should probably obey gravity rather than always using right/bottom ?
|
2020-01-17 21:30:56 +00:00
|
|
|
if (sizeMode == SizeModeFixedH)
|
2020-01-13 18:57:07 +00:00
|
|
|
orig.setRight(bottomright.x());
|
2020-01-17 21:30:56 +00:00
|
|
|
else if (sizeMode == SizeModeFixedW)
|
2020-01-13 18:57:07 +00:00
|
|
|
orig.setBottom(bottomright.y());
|
|
|
|
|
|
|
|
calculateMoveResizeGeom();
|
|
|
|
|
|
|
|
if (moveResizeGeometry().size() != previousMoveResizeGeom.size())
|
|
|
|
update = true;
|
|
|
|
} else if (isMove()) {
|
|
|
|
Q_ASSERT(mode == PositionCenter);
|
|
|
|
if (!isMovable()) { // isMovableAcrossScreens() must have been true to get here
|
|
|
|
// Special moving of maximized windows on Xinerama screens
|
|
|
|
int screen = screens()->number(globalPos);
|
|
|
|
if (isFullScreen())
|
|
|
|
setMoveResizeGeometry(workspace()->clientArea(FullScreenArea, screen, 0));
|
|
|
|
else {
|
|
|
|
QRect moveResizeGeom = workspace()->clientArea(MaximizeArea, screen, 0);
|
Refactor geometry constraints code
Summary:
Currently, there are a couple of issues with sizeForClientSize(). First
of all, we have a method called clientSizeToFrameSize() which does similar
thing except applying geometry constraints and checking window rules. The
other issue is that sizeForClientSize() is doing a bit too much, it checks
window rules, it applies a bunch of geometry constrains. Sometimes it
does not perform conversion between client sizes and frame sizes!
This change attempts to address those issues by replacing sizeForClientSize
with two similar methods and changing semantics of some methods of the
X11Client class.
The most significant difference between sizeForClientSize() and the new
methods is that neither constrainClientSize() nor constrainFrameSize()
check window rules. This is up to users of those methods. In many places,
we don't have to check window rules because we check isResizable(),
which returns false if the frame size is enforced by a window rule.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, romangg, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D26828
2020-02-12 10:38:40 +00:00
|
|
|
QSize adjSize = constrainFrameSize(moveResizeGeom.size(), SizeModeMax);
|
2020-01-13 18:57:07 +00:00
|
|
|
if (adjSize != moveResizeGeom.size()) {
|
|
|
|
QRect r(moveResizeGeom);
|
|
|
|
moveResizeGeom.setSize(adjSize);
|
|
|
|
moveResizeGeom.moveCenter(r.center());
|
|
|
|
}
|
|
|
|
setMoveResizeGeometry(moveResizeGeom);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// first move, then snap, then check bounds
|
|
|
|
QRect moveResizeGeom = moveResizeGeometry();
|
|
|
|
moveResizeGeom.moveTopLeft(topleft);
|
|
|
|
moveResizeGeom.moveTopLeft(workspace()->adjustClientPosition(this, moveResizeGeom.topLeft(),
|
|
|
|
isUnrestrictedMoveResize()));
|
|
|
|
setMoveResizeGeometry(moveResizeGeom);
|
|
|
|
|
|
|
|
if (!isUnrestrictedMoveResize()) {
|
|
|
|
const QRegion strut = workspace()->restrictedMoveArea(desktop()); // Strut areas
|
|
|
|
QRegion availableArea(workspace()->clientArea(FullArea, -1, 0)); // On the screen
|
|
|
|
availableArea -= strut; // Strut areas
|
|
|
|
bool transposed = false;
|
|
|
|
int requiredPixels;
|
|
|
|
QRect bTitleRect = titleBarRect(transposed, requiredPixels);
|
|
|
|
for (;;) {
|
|
|
|
QRect moveResizeGeom = moveResizeGeometry();
|
|
|
|
const QRect titleRect(bTitleRect.translated(moveResizeGeom.topLeft()));
|
|
|
|
int visiblePixels = 0;
|
|
|
|
for (const QRect &rect : availableArea) {
|
|
|
|
const QRect r = rect & titleRect;
|
|
|
|
if ((transposed && r.width() == titleRect.width()) || // Only the full size regions...
|
|
|
|
(!transposed && r.height() == titleRect.height())) // ...prevents long slim areas
|
|
|
|
visiblePixels += r.width() * r.height();
|
|
|
|
}
|
|
|
|
if (visiblePixels >= requiredPixels)
|
|
|
|
break; // We have reached a valid position
|
|
|
|
|
|
|
|
// (esp.) if there're more screens with different struts (panels) it the titlebar
|
|
|
|
// will be movable outside the movearea (covering one of the panels) until it
|
|
|
|
// crosses the panel "too much" (not enough visiblePixels) and then stucks because
|
|
|
|
// it's usually only pushed by 1px to either direction
|
|
|
|
// so we first check whether we intersect suc strut and move the window below it
|
|
|
|
// immediately (it's still possible to hit the visiblePixels >= titlebarArea break
|
|
|
|
// by moving the window slightly downwards, but it won't stuck)
|
|
|
|
// see bug #274466
|
|
|
|
// and bug #301805 for why we can't just match the titlearea against the screen
|
|
|
|
if (screens()->count() > 1) { // optimization
|
|
|
|
// TODO: could be useful on partial screen struts (half-width panels etc.)
|
|
|
|
int newTitleTop = -1;
|
|
|
|
for (const QRect &r : strut) {
|
|
|
|
if (r.top() == 0 && r.width() > r.height() && // "top panel"
|
|
|
|
r.intersects(moveResizeGeom) && moveResizeGeom.top() < r.bottom()) {
|
|
|
|
newTitleTop = r.bottom() + 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (newTitleTop > -1) {
|
|
|
|
moveResizeGeom.moveTop(newTitleTop); // invalid position, possibly on screen change
|
|
|
|
setMoveResizeGeometry(moveResizeGeom);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int dx = sign(previousMoveResizeGeom.x() - moveResizeGeom.x()),
|
|
|
|
dy = sign(previousMoveResizeGeom.y() - moveResizeGeom.y());
|
|
|
|
if (visiblePixels && dx) // means there's no full width cap -> favor horizontally
|
|
|
|
dy = 0;
|
|
|
|
else if (dy)
|
|
|
|
dx = 0;
|
|
|
|
|
|
|
|
// Move it back
|
|
|
|
moveResizeGeom.translate(dx, dy);
|
|
|
|
setMoveResizeGeometry(moveResizeGeom);
|
|
|
|
|
|
|
|
if (moveResizeGeom == previousMoveResizeGeom) {
|
|
|
|
break; // Prevent lockup
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (moveResizeGeometry().topLeft() != previousMoveResizeGeom.topLeft())
|
|
|
|
update = true;
|
|
|
|
} else
|
|
|
|
abort();
|
|
|
|
|
|
|
|
if (!update)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (isResize() && !haveResizeEffect()) {
|
|
|
|
doResizeSync();
|
|
|
|
} else
|
|
|
|
performMoveResize();
|
|
|
|
|
|
|
|
if (isMove()) {
|
2020-02-03 10:55:46 +00:00
|
|
|
ScreenEdges::self()->check(globalPos, QDateTime::fromMSecsSinceEpoch(xTime(), Qt::UTC));
|
2020-01-13 18:57:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::performMoveResize()
|
|
|
|
{
|
|
|
|
const QRect &moveResizeGeom = moveResizeGeometry();
|
|
|
|
if (isMove() || (isResize() && !haveResizeEffect())) {
|
|
|
|
setFrameGeometry(moveResizeGeom);
|
|
|
|
}
|
|
|
|
doPerformMoveResize();
|
|
|
|
positionGeometryTip();
|
|
|
|
emit clientStepUserMovedResized(this, moveResizeGeom);
|
|
|
|
}
|
|
|
|
|
2020-08-17 13:14:20 +00:00
|
|
|
StrutRect AbstractClient::strutRect(StrutArea area) const
|
|
|
|
{
|
|
|
|
Q_UNUSED(area)
|
|
|
|
return StrutRect();
|
|
|
|
}
|
|
|
|
|
|
|
|
StrutRects AbstractClient::strutRects() const
|
|
|
|
{
|
|
|
|
StrutRects region;
|
|
|
|
region += strutRect(StrutAreaTop);
|
|
|
|
region += strutRect(StrutAreaRight);
|
|
|
|
region += strutRect(StrutAreaBottom);
|
|
|
|
region += strutRect(StrutAreaLeft);
|
|
|
|
return region;
|
|
|
|
}
|
|
|
|
|
2015-06-19 22:14:15 +00:00
|
|
|
bool AbstractClient::hasStrut() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-07-09 07:10:33 +00:00
|
|
|
void AbstractClient::setupWindowManagementInterface()
|
|
|
|
{
|
|
|
|
if (m_windowManagementInterface) {
|
|
|
|
// already setup
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!waylandServer() || !surface()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!waylandServer()->windowManagement()) {
|
|
|
|
return;
|
|
|
|
}
|
2020-04-29 15:18:41 +00:00
|
|
|
using namespace KWaylandServer;
|
2021-03-08 22:36:08 +00:00
|
|
|
auto w = waylandServer()->windowManagement()->createWindow(this, internalId());
|
2015-07-09 07:10:33 +00:00
|
|
|
w->setTitle(caption());
|
|
|
|
w->setVirtualDesktop(isOnAllDesktops() ? 0 : desktop() - 1);
|
|
|
|
w->setActive(isActive());
|
|
|
|
w->setFullscreen(isFullScreen());
|
|
|
|
w->setKeepAbove(keepAbove());
|
|
|
|
w->setKeepBelow(keepBelow());
|
|
|
|
w->setMaximized(maximizeMode() == KWin::MaximizeFull);
|
|
|
|
w->setMinimized(isMinimized());
|
|
|
|
w->setOnAllDesktops(isOnAllDesktops());
|
|
|
|
w->setDemandsAttention(isDemandingAttention());
|
|
|
|
w->setCloseable(isCloseable());
|
|
|
|
w->setMaximizeable(isMaximizable());
|
|
|
|
w->setMinimizeable(isMinimizable());
|
|
|
|
w->setFullscreenable(isFullScreenable());
|
2020-03-20 06:59:41 +00:00
|
|
|
w->setApplicationMenuPaths(applicationMenuServiceName(), applicationMenuObjectPath());
|
2016-10-13 11:57:34 +00:00
|
|
|
w->setIcon(icon());
|
2016-10-27 13:59:08 +00:00
|
|
|
auto updateAppId = [this, w] {
|
2017-05-11 16:05:36 +00:00
|
|
|
w->setAppId(QString::fromUtf8(m_desktopFileName.isEmpty() ? resourceClass() : m_desktopFileName));
|
2016-10-27 13:59:08 +00:00
|
|
|
};
|
|
|
|
updateAppId();
|
2015-09-29 18:25:04 +00:00
|
|
|
w->setSkipTaskbar(skipTaskbar());
|
2018-05-24 04:33:39 +00:00
|
|
|
w->setSkipSwitcher(skipSwitcher());
|
2017-05-13 15:02:30 +00:00
|
|
|
w->setPid(pid());
|
2016-04-19 09:43:04 +00:00
|
|
|
w->setShadeable(isShadeable());
|
2016-04-15 11:41:10 +00:00
|
|
|
w->setShaded(isShade());
|
2016-04-17 14:43:45 +00:00
|
|
|
w->setResizable(isResizable());
|
|
|
|
w->setMovable(isMovable());
|
2019-09-24 08:48:08 +00:00
|
|
|
w->setVirtualDesktopChangeable(true); // FIXME Matches X11Client::actionSupported(), but both should be implemented.
|
2016-06-06 13:19:28 +00:00
|
|
|
w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr);
|
2019-09-27 10:01:10 +00:00
|
|
|
w->setGeometry(frameGeometry());
|
2015-09-29 18:25:04 +00:00
|
|
|
connect(this, &AbstractClient::skipTaskbarChanged, w,
|
|
|
|
[w, this] {
|
|
|
|
w->setSkipTaskbar(skipTaskbar());
|
|
|
|
}
|
|
|
|
);
|
2018-05-24 04:33:39 +00:00
|
|
|
connect(this, &AbstractClient::skipSwitcherChanged, w,
|
|
|
|
[w, this] {
|
|
|
|
w->setSkipSwitcher(skipSwitcher());
|
|
|
|
}
|
|
|
|
);
|
2015-07-09 07:10:33 +00:00
|
|
|
connect(this, &AbstractClient::captionChanged, w, [w, this] { w->setTitle(caption()); });
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
|
2015-07-09 07:10:33 +00:00
|
|
|
connect(this, &AbstractClient::activeChanged, w, [w, this] { w->setActive(isActive()); });
|
|
|
|
connect(this, &AbstractClient::fullScreenChanged, w, [w, this] { w->setFullscreen(isFullScreen()); });
|
|
|
|
connect(this, &AbstractClient::keepAboveChanged, w, &PlasmaWindowInterface::setKeepAbove);
|
|
|
|
connect(this, &AbstractClient::keepBelowChanged, w, &PlasmaWindowInterface::setKeepBelow);
|
|
|
|
connect(this, &AbstractClient::minimizedChanged, w, [w, this] { w->setMinimized(isMinimized()); });
|
|
|
|
connect(this, static_cast<void (AbstractClient::*)(AbstractClient*,MaximizeMode)>(&AbstractClient::clientMaximizedStateChanged), w,
|
|
|
|
[w] (KWin::AbstractClient *c, MaximizeMode mode) {
|
|
|
|
Q_UNUSED(c);
|
|
|
|
w->setMaximized(mode == KWin::MaximizeFull);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
connect(this, &AbstractClient::demandsAttentionChanged, w, [w, this] { w->setDemandsAttention(isDemandingAttention()); });
|
|
|
|
connect(this, &AbstractClient::iconChanged, w,
|
|
|
|
[w, this] {
|
2016-10-13 11:57:34 +00:00
|
|
|
w->setIcon(icon());
|
2015-07-09 07:10:33 +00:00
|
|
|
}
|
|
|
|
);
|
2016-10-27 13:59:08 +00:00
|
|
|
connect(this, &AbstractClient::windowClassChanged, w, updateAppId);
|
|
|
|
connect(this, &AbstractClient::desktopFileNameChanged, w, updateAppId);
|
2016-04-15 11:41:10 +00:00
|
|
|
connect(this, &AbstractClient::shadeChanged, w, [w, this] { w->setShaded(isShade()); });
|
2016-06-06 13:19:28 +00:00
|
|
|
connect(this, &AbstractClient::transientChanged, w,
|
|
|
|
[w, this] {
|
|
|
|
w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr);
|
|
|
|
}
|
|
|
|
);
|
2020-02-05 09:28:50 +00:00
|
|
|
connect(this, &AbstractClient::frameGeometryChanged, w,
|
2016-07-18 06:22:33 +00:00
|
|
|
[w, this] {
|
2019-09-27 10:01:10 +00:00
|
|
|
w->setGeometry(frameGeometry());
|
2016-07-18 06:22:33 +00:00
|
|
|
}
|
|
|
|
);
|
2020-03-03 22:26:10 +00:00
|
|
|
connect(this, &AbstractClient::applicationMenuChanged, w,
|
|
|
|
[w, this] {
|
|
|
|
w->setApplicationMenuPaths(applicationMenuServiceName(), applicationMenuObjectPath());
|
|
|
|
}
|
|
|
|
);
|
2015-07-09 07:10:33 +00:00
|
|
|
connect(w, &PlasmaWindowInterface::closeRequested, this, [this] { closeWindow(); });
|
2016-04-17 14:43:45 +00:00
|
|
|
connect(w, &PlasmaWindowInterface::moveRequested, this,
|
|
|
|
[this] {
|
2020-04-02 16:18:01 +00:00
|
|
|
Cursors::self()->mouse()->setPos(frameGeometry().center());
|
|
|
|
performMouseCommand(Options::MouseMove, Cursors::self()->mouse()->pos());
|
2016-04-17 14:43:45 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
connect(w, &PlasmaWindowInterface::resizeRequested, this,
|
|
|
|
[this] {
|
2020-04-02 16:18:01 +00:00
|
|
|
Cursors::self()->mouse()->setPos(frameGeometry().bottomRight());
|
|
|
|
performMouseCommand(Options::MouseResize, Cursors::self()->mouse()->pos());
|
2016-04-17 14:43:45 +00:00
|
|
|
}
|
|
|
|
);
|
2015-07-09 07:10:33 +00:00
|
|
|
connect(w, &PlasmaWindowInterface::virtualDesktopRequested, this,
|
|
|
|
[this] (quint32 desktop) {
|
|
|
|
workspace()->sendClientToDesktop(this, desktop + 1, true);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
connect(w, &PlasmaWindowInterface::fullscreenRequested, this,
|
|
|
|
[this] (bool set) {
|
|
|
|
setFullScreen(set, false);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
connect(w, &PlasmaWindowInterface::minimizedRequested, this,
|
|
|
|
[this] (bool set) {
|
|
|
|
if (set) {
|
|
|
|
minimize();
|
|
|
|
} else {
|
|
|
|
unminimize();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
connect(w, &PlasmaWindowInterface::maximizedRequested, this,
|
|
|
|
[this] (bool set) {
|
|
|
|
maximize(set ? MaximizeFull : MaximizeRestore);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
connect(w, &PlasmaWindowInterface::keepAboveRequested, this,
|
|
|
|
[this] (bool set) {
|
|
|
|
setKeepAbove(set);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
connect(w, &PlasmaWindowInterface::keepBelowRequested, this,
|
|
|
|
[this] (bool set) {
|
|
|
|
setKeepBelow(set);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
connect(w, &PlasmaWindowInterface::demandsAttentionRequested, this,
|
|
|
|
[this] (bool set) {
|
|
|
|
demandAttention(set);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
connect(w, &PlasmaWindowInterface::activeRequested, this,
|
|
|
|
[this] (bool set) {
|
|
|
|
if (set) {
|
|
|
|
workspace()->activateClient(this, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
2016-04-15 11:41:10 +00:00
|
|
|
connect(w, &PlasmaWindowInterface::shadedRequested, this,
|
|
|
|
[this] (bool set) {
|
|
|
|
setShade(set);
|
|
|
|
}
|
|
|
|
);
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
|
|
|
|
for (const auto vd : m_desktops) {
|
|
|
|
w->addPlasmaVirtualDesktop(vd->id());
|
|
|
|
}
|
|
|
|
|
|
|
|
//this is only for the legacy
|
|
|
|
connect(this, &AbstractClient::desktopChanged, w,
|
|
|
|
[w, this] {
|
|
|
|
if (isOnAllDesktops()) {
|
|
|
|
w->setOnAllDesktops(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
w->setVirtualDesktop(desktop() - 1);
|
|
|
|
w->setOnAllDesktops(false);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
//Plasma Virtual desktop management
|
|
|
|
//show/hide when the window enters/exits from desktop
|
|
|
|
connect(w, &PlasmaWindowInterface::enterPlasmaVirtualDesktopRequested, this,
|
|
|
|
[this] (const QString &desktopId) {
|
|
|
|
VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId.toUtf8());
|
|
|
|
if (vd) {
|
2018-11-13 16:18:46 +00:00
|
|
|
enterDesktop(vd);
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
connect(w, &PlasmaWindowInterface::enterNewPlasmaVirtualDesktopRequested, this,
|
|
|
|
[this] () {
|
|
|
|
VirtualDesktopManager::self()->setCount(VirtualDesktopManager::self()->count() + 1);
|
2018-11-13 16:18:46 +00:00
|
|
|
enterDesktop(VirtualDesktopManager::self()->desktops().last());
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
connect(w, &PlasmaWindowInterface::leavePlasmaVirtualDesktopRequested, this,
|
|
|
|
[this] (const QString &desktopId) {
|
|
|
|
VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId.toUtf8());
|
|
|
|
if (vd) {
|
2018-11-13 16:18:46 +00:00
|
|
|
leaveDesktop(vd);
|
[wayland] Use the new plasma virtual desktop protocol
Summary:
implement virtual desktop support for Wayland.
use the new virtual desktop protocol from D12820
The VirtualDesktopManager class needed some big change in order
to accomodate it, which is where most changes are.
Other than that, it's mostly connections to wire up
VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl)
Depends on D12820
Other notable detail, is the client visibility updated to reflect the presence
of the client in the plasmavirtualdesktop.
(and the unSetDesktop concept)
Test Plan: used a bit a plasma session together with D12820, D13748 and D13746
Reviewers: #plasma, #kwin, graesslin, davidedmundson
Reviewed By: #plasma, #kwin, davidedmundson
Subscribers: hein, zzag, davidedmundson, kwin
Tags: #kwin
Maniphest Tasks: T4457
Differential Revision: https://phabricator.kde.org/D13887
2018-10-29 22:29:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2021-03-16 15:46:38 +00:00
|
|
|
for (const auto &activity : m_activityList) {
|
|
|
|
w->addPlasmaActivity(activity);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify clients on activities changes
|
|
|
|
connect(this, &AbstractClient::activitiesChanged, w, [w, this] {
|
|
|
|
const auto newActivities = m_activityList.toSet();
|
|
|
|
const auto oldActivities = w->plasmaActivities().toSet();
|
|
|
|
|
|
|
|
const auto activitiesToAdd = newActivities - oldActivities;
|
|
|
|
for (const auto &activity : activitiesToAdd) {
|
|
|
|
w->addPlasmaActivity(activity);
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto activitiesToRemove = oldActivities - newActivities;
|
|
|
|
for (const auto &activity : activitiesToRemove) {
|
|
|
|
w->removePlasmaActivity(activity);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
//Plasma Activities management
|
|
|
|
//show/hide when the window enters/exits activity
|
|
|
|
connect(w, &PlasmaWindowInterface::enterPlasmaActivityRequested, this,
|
|
|
|
[this] (const QString &activityId) {
|
|
|
|
setOnActivity(activityId, true);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
connect(w, &PlasmaWindowInterface::leavePlasmaActivityRequested, this,
|
|
|
|
[this] (const QString &activityId) {
|
|
|
|
setOnActivity(activityId, false);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2015-07-09 07:10:33 +00:00
|
|
|
m_windowManagementInterface = w;
|
|
|
|
}
|
|
|
|
|
2015-07-09 14:11:51 +00:00
|
|
|
Options::MouseCommand AbstractClient::getMouseCommand(Qt::MouseButton button, bool *handled) const
|
|
|
|
{
|
|
|
|
*handled = false;
|
|
|
|
if (button == Qt::NoButton) {
|
|
|
|
return Options::MouseNothing;
|
|
|
|
}
|
|
|
|
if (isActive()) {
|
2020-07-15 09:14:50 +00:00
|
|
|
if (options->isClickRaise() && !isMostRecentlyRaised()) {
|
2015-07-09 14:11:51 +00:00
|
|
|
*handled = true;
|
|
|
|
return Options::MouseActivateRaiseAndPassClick;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*handled = true;
|
|
|
|
switch (button) {
|
|
|
|
case Qt::LeftButton:
|
|
|
|
return options->commandWindow1();
|
|
|
|
case Qt::MiddleButton:
|
|
|
|
return options->commandWindow2();
|
|
|
|
case Qt::RightButton:
|
|
|
|
return options->commandWindow3();
|
|
|
|
default:
|
|
|
|
// all other buttons pass Activate & Pass Client
|
|
|
|
return Options::MouseActivateAndPassClick;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Options::MouseNothing;
|
|
|
|
}
|
|
|
|
|
|
|
|
Options::MouseCommand AbstractClient::getWheelCommand(Qt::Orientation orientation, bool *handled) const
|
|
|
|
{
|
|
|
|
*handled = false;
|
|
|
|
if (orientation != Qt::Vertical) {
|
|
|
|
return Options::MouseNothing;
|
|
|
|
}
|
|
|
|
if (!isActive()) {
|
|
|
|
*handled = true;
|
|
|
|
return options->commandWindowWheel();
|
|
|
|
}
|
|
|
|
return Options::MouseNothing;
|
|
|
|
}
|
|
|
|
|
2015-07-09 14:08:01 +00:00
|
|
|
bool AbstractClient::performMouseCommand(Options::MouseCommand cmd, const QPoint &globalPos)
|
|
|
|
{
|
|
|
|
bool replay = false;
|
|
|
|
switch(cmd) {
|
|
|
|
case Options::MouseRaise:
|
|
|
|
workspace()->raiseClient(this);
|
|
|
|
break;
|
|
|
|
case Options::MouseLower: {
|
|
|
|
workspace()->lowerClient(this);
|
2016-02-18 09:30:58 +00:00
|
|
|
// used to be activateNextClient(this), then topClientOnDesktop
|
|
|
|
// since this is a mouseOp it's however safe to use the client under the mouse instead
|
|
|
|
if (isActive() && options->focusPolicyIsReasonable()) {
|
|
|
|
AbstractClient *next = workspace()->clientUnderMouse(screen());
|
|
|
|
if (next && next != this)
|
|
|
|
workspace()->requestFocus(next, false);
|
|
|
|
}
|
2015-07-09 14:08:01 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Options::MouseOperationsMenu:
|
|
|
|
if (isActive() && options->isClickRaise())
|
|
|
|
autoRaise();
|
|
|
|
workspace()->showWindowMenu(QRect(globalPos, globalPos), this);
|
|
|
|
break;
|
|
|
|
case Options::MouseToggleRaiseAndLower:
|
|
|
|
workspace()->raiseOrLowerClient(this);
|
|
|
|
break;
|
|
|
|
case Options::MouseActivateAndRaise: {
|
|
|
|
replay = isActive(); // for clickraise mode
|
2015-12-07 15:18:30 +00:00
|
|
|
bool mustReplay = !rules()->checkAcceptFocus(acceptsFocus());
|
2015-07-09 14:08:01 +00:00
|
|
|
if (mustReplay) {
|
Drop some custom list typedefs
Summary:
Qt has its own thing where a type might also have corresponding list
alias, e.g. QObject and QObjectList, QWidget and QWidgetList. I don't
know why Qt does that, maybe for some historical reasons, but what
matters is that we copy this pattern here in KWin. While this pattern
might be useful with some long list types, for example
QList<QWeakPointer<TabBoxClient>> TabBoxClientList
in general, it causes more harm than good. For example, we've got two
new client types, do we need corresponding list typedefs for them? If
no, why do we have ClientList and so on?
Another problem with these typedefs is that you need to include utils.h
header in order to use them. A better way to handle such things is to
just forward declare a client class (if that's possible) and use it
directly with QList or QVector. This way translation units don't get
"bloated" with utils.h stuff for no apparent reason.
So, in order to make code more consistent and easier to follow, this
change drops some of our custom typedefs. Namely ConstClientList,
ClientList, DeletedList, UnmanagedList, ToplevelList, and GroupList.
Test Plan: Compiles.
Reviewers: #kwin
Subscribers: kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D24950
2019-10-16 09:11:04 +00:00
|
|
|
auto it = workspace()->stackingOrder().constEnd(),
|
2015-07-09 14:08:01 +00:00
|
|
|
begin = workspace()->stackingOrder().constBegin();
|
|
|
|
while (mustReplay && --it != begin && *it != this) {
|
|
|
|
AbstractClient *c = qobject_cast<AbstractClient*>(*it);
|
|
|
|
if (!c || (c->keepAbove() && !keepAbove()) || (keepBelow() && !c->keepBelow()))
|
|
|
|
continue; // can never raise above "it"
|
2019-09-27 10:01:10 +00:00
|
|
|
mustReplay = !(c->isOnCurrentDesktop() && c->isOnCurrentActivity() && c->frameGeometry().intersects(frameGeometry()));
|
2015-07-09 14:08:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise);
|
|
|
|
screens()->setCurrent(globalPos);
|
|
|
|
replay = replay || mustReplay;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Options::MouseActivateAndLower:
|
|
|
|
workspace()->requestFocus(this);
|
|
|
|
workspace()->lowerClient(this);
|
|
|
|
screens()->setCurrent(globalPos);
|
2015-12-07 15:18:30 +00:00
|
|
|
replay = replay || !rules()->checkAcceptFocus(acceptsFocus());
|
2015-07-09 14:08:01 +00:00
|
|
|
break;
|
|
|
|
case Options::MouseActivate:
|
|
|
|
replay = isActive(); // for clickraise mode
|
|
|
|
workspace()->takeActivity(this, Workspace::ActivityFocus);
|
|
|
|
screens()->setCurrent(globalPos);
|
2015-12-07 15:18:30 +00:00
|
|
|
replay = replay || !rules()->checkAcceptFocus(acceptsFocus());
|
2015-07-09 14:08:01 +00:00
|
|
|
break;
|
|
|
|
case Options::MouseActivateRaiseAndPassClick:
|
|
|
|
workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise);
|
|
|
|
screens()->setCurrent(globalPos);
|
|
|
|
replay = true;
|
|
|
|
break;
|
|
|
|
case Options::MouseActivateAndPassClick:
|
|
|
|
workspace()->takeActivity(this, Workspace::ActivityFocus);
|
|
|
|
screens()->setCurrent(globalPos);
|
|
|
|
replay = true;
|
|
|
|
break;
|
|
|
|
case Options::MouseMaximize:
|
|
|
|
maximize(MaximizeFull);
|
|
|
|
break;
|
|
|
|
case Options::MouseRestore:
|
|
|
|
maximize(MaximizeRestore);
|
|
|
|
break;
|
|
|
|
case Options::MouseMinimize:
|
|
|
|
minimize();
|
|
|
|
break;
|
|
|
|
case Options::MouseAbove: {
|
|
|
|
StackingUpdatesBlocker blocker(workspace());
|
|
|
|
if (keepBelow())
|
|
|
|
setKeepBelow(false);
|
|
|
|
else
|
|
|
|
setKeepAbove(true);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Options::MouseBelow: {
|
|
|
|
StackingUpdatesBlocker blocker(workspace());
|
|
|
|
if (keepAbove())
|
|
|
|
setKeepAbove(false);
|
|
|
|
else
|
|
|
|
setKeepBelow(true);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Options::MousePreviousDesktop:
|
|
|
|
workspace()->windowToPreviousDesktop(this);
|
|
|
|
break;
|
|
|
|
case Options::MouseNextDesktop:
|
|
|
|
workspace()->windowToNextDesktop(this);
|
|
|
|
break;
|
|
|
|
case Options::MouseOpacityMore:
|
|
|
|
if (!isDesktop()) // No point in changing the opacity of the desktop
|
|
|
|
setOpacity(qMin(opacity() + 0.1, 1.0));
|
|
|
|
break;
|
|
|
|
case Options::MouseOpacityLess:
|
|
|
|
if (!isDesktop()) // No point in changing the opacity of the desktop
|
|
|
|
setOpacity(qMax(opacity() - 0.1, 0.1));
|
|
|
|
break;
|
|
|
|
case Options::MouseClose:
|
|
|
|
closeWindow();
|
|
|
|
break;
|
2015-10-23 11:40:13 +00:00
|
|
|
case Options::MouseActivateRaiseAndMove:
|
|
|
|
case Options::MouseActivateRaiseAndUnrestrictedMove:
|
|
|
|
workspace()->raiseClient(this);
|
|
|
|
workspace()->requestFocus(this);
|
|
|
|
screens()->setCurrent(globalPos);
|
|
|
|
// fallthrough
|
|
|
|
case Options::MouseMove:
|
|
|
|
case Options::MouseUnrestrictedMove: {
|
|
|
|
if (!isMovableAcrossScreens())
|
|
|
|
break;
|
|
|
|
if (isMoveResize())
|
|
|
|
finishMoveResize(false);
|
|
|
|
setMoveResizePointerMode(PositionCenter);
|
|
|
|
setMoveResizePointerButtonDown(true);
|
|
|
|
setMoveOffset(QPoint(globalPos.x() - x(), globalPos.y() - y())); // map from global
|
|
|
|
setInvertedMoveOffset(rect().bottomRight() - moveOffset());
|
|
|
|
setUnrestrictedMoveResize((cmd == Options::MouseActivateRaiseAndUnrestrictedMove
|
|
|
|
|| cmd == Options::MouseUnrestrictedMove));
|
|
|
|
if (!startMoveResize())
|
|
|
|
setMoveResizePointerButtonDown(false);
|
|
|
|
updateCursor();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Options::MouseResize:
|
|
|
|
case Options::MouseUnrestrictedResize: {
|
|
|
|
if (!isResizable() || isShade())
|
|
|
|
break;
|
|
|
|
if (isMoveResize())
|
|
|
|
finishMoveResize(false);
|
|
|
|
setMoveResizePointerButtonDown(true);
|
|
|
|
const QPoint moveOffset = QPoint(globalPos.x() - x(), globalPos.y() - y()); // map from global
|
|
|
|
setMoveOffset(moveOffset);
|
|
|
|
int x = moveOffset.x(), y = moveOffset.y();
|
|
|
|
bool left = x < width() / 3;
|
|
|
|
bool right = x >= 2 * width() / 3;
|
|
|
|
bool top = y < height() / 3;
|
|
|
|
bool bot = y >= 2 * height() / 3;
|
|
|
|
Position mode;
|
|
|
|
if (top)
|
|
|
|
mode = left ? PositionTopLeft : (right ? PositionTopRight : PositionTop);
|
|
|
|
else if (bot)
|
|
|
|
mode = left ? PositionBottomLeft : (right ? PositionBottomRight : PositionBottom);
|
|
|
|
else
|
|
|
|
mode = (x < width() / 2) ? PositionLeft : PositionRight;
|
|
|
|
setMoveResizePointerMode(mode);
|
|
|
|
setInvertedMoveOffset(rect().bottomRight() - moveOffset);
|
|
|
|
setUnrestrictedMoveResize((cmd == Options::MouseUnrestrictedResize));
|
|
|
|
if (!startMoveResize())
|
|
|
|
setMoveResizePointerButtonDown(false);
|
|
|
|
updateCursor();
|
|
|
|
break;
|
|
|
|
}
|
2020-05-07 16:26:24 +00:00
|
|
|
case Options::MouseShade:
|
|
|
|
toggleShade();
|
|
|
|
cancelShadeHoverTimer();
|
|
|
|
break;
|
|
|
|
case Options::MouseSetShade:
|
|
|
|
setShade(ShadeNormal);
|
|
|
|
cancelShadeHoverTimer();
|
|
|
|
break;
|
|
|
|
case Options::MouseUnsetShade:
|
|
|
|
setShade(ShadeNone);
|
|
|
|
cancelShadeHoverTimer();
|
|
|
|
break;
|
2015-07-09 14:08:01 +00:00
|
|
|
case Options::MouseNothing:
|
|
|
|
default:
|
|
|
|
replay = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return replay;
|
|
|
|
}
|
|
|
|
|
2015-09-11 09:37:40 +00:00
|
|
|
void AbstractClient::setTransientFor(AbstractClient *transientFor)
|
|
|
|
{
|
2015-09-17 12:10:57 +00:00
|
|
|
if (transientFor == this) {
|
|
|
|
// cannot be transient for one self
|
|
|
|
return;
|
|
|
|
}
|
2015-09-11 09:37:40 +00:00
|
|
|
if (m_transientFor == transientFor) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_transientFor = transientFor;
|
|
|
|
emit transientChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
const AbstractClient *AbstractClient::transientFor() const
|
|
|
|
{
|
|
|
|
return m_transientFor;
|
|
|
|
}
|
|
|
|
|
|
|
|
AbstractClient *AbstractClient::transientFor()
|
|
|
|
{
|
|
|
|
return m_transientFor;
|
|
|
|
}
|
|
|
|
|
2015-09-11 11:31:41 +00:00
|
|
|
bool AbstractClient::hasTransientPlacementHint() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-10-19 22:21:54 +00:00
|
|
|
QRect AbstractClient::transientPlacement(const QRect &bounds) const
|
2015-09-11 11:31:41 +00:00
|
|
|
{
|
2018-10-19 22:21:54 +00:00
|
|
|
Q_UNUSED(bounds);
|
|
|
|
Q_UNREACHABLE();
|
|
|
|
return QRect();
|
2015-09-11 11:31:41 +00:00
|
|
|
}
|
|
|
|
|
2015-09-11 12:14:21 +00:00
|
|
|
bool AbstractClient::hasTransient(const AbstractClient *c, bool indirect) const
|
|
|
|
{
|
|
|
|
Q_UNUSED(indirect);
|
|
|
|
return c->transientFor() == this;
|
|
|
|
}
|
|
|
|
|
2015-09-11 13:55:23 +00:00
|
|
|
QList< AbstractClient* > AbstractClient::mainClients() const
|
|
|
|
{
|
|
|
|
if (const AbstractClient *t = transientFor()) {
|
|
|
|
return QList<AbstractClient*>{const_cast< AbstractClient* >(t)};
|
|
|
|
}
|
|
|
|
return QList<AbstractClient*>();
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<AbstractClient*> AbstractClient::allMainClients() const
|
|
|
|
{
|
|
|
|
auto result = mainClients();
|
|
|
|
foreach (const auto *cl, result) {
|
|
|
|
result += cl->allMainClients();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-09-14 07:04:20 +00:00
|
|
|
void AbstractClient::setModal(bool m)
|
|
|
|
{
|
|
|
|
// Qt-3.2 can have even modal normal windows :(
|
|
|
|
if (m_modal == m)
|
|
|
|
return;
|
|
|
|
m_modal = m;
|
|
|
|
emit modalChanged();
|
|
|
|
// Changing modality for a mapped window is weird (?)
|
|
|
|
// _NET_WM_STATE_MODAL should possibly rather be _NET_WM_WINDOW_TYPE_MODAL_DIALOG
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AbstractClient::isModal() const
|
|
|
|
{
|
|
|
|
return m_modal;
|
|
|
|
}
|
|
|
|
|
2015-09-14 08:55:27 +00:00
|
|
|
void AbstractClient::addTransient(AbstractClient *cl)
|
|
|
|
{
|
2019-08-31 14:28:37 +00:00
|
|
|
Q_ASSERT(!m_transients.contains(cl));
|
|
|
|
Q_ASSERT(cl != this);
|
2015-09-14 08:55:27 +00:00
|
|
|
m_transients.append(cl);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::removeTransient(AbstractClient *cl)
|
|
|
|
{
|
|
|
|
m_transients.removeAll(cl);
|
2015-10-01 12:12:46 +00:00
|
|
|
if (cl->transientFor() == this) {
|
|
|
|
cl->setTransientFor(nullptr);
|
|
|
|
}
|
2015-09-14 08:55:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::removeTransientFromList(AbstractClient *cl)
|
|
|
|
{
|
|
|
|
m_transients.removeAll(cl);
|
|
|
|
}
|
|
|
|
|
2015-09-17 09:06:59 +00:00
|
|
|
bool AbstractClient::isActiveFullScreen() const
|
|
|
|
{
|
|
|
|
if (!isFullScreen())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const auto ac = workspace()->mostRecentlyActivatedClient(); // instead of activeClient() - avoids flicker
|
|
|
|
// according to NETWM spec implementation notes suggests
|
|
|
|
// "focused windows having state _NET_WM_STATE_FULLSCREEN" to be on the highest layer.
|
|
|
|
// we'll also take the screen into account
|
2018-01-06 10:24:31 +00:00
|
|
|
return ac && (ac == this || ac->screen() != screen()|| ac->allMainClients().contains(const_cast<AbstractClient*>(this)));
|
2015-09-17 09:06:59 +00:00
|
|
|
}
|
|
|
|
|
2015-12-03 12:54:16 +00:00
|
|
|
#define BORDER(which) \
|
|
|
|
int AbstractClient::border##which() const \
|
|
|
|
{ \
|
|
|
|
return isDecorated() ? decoration()->border##which() : 0; \
|
|
|
|
}
|
2015-10-12 10:00:33 +00:00
|
|
|
|
2015-12-03 12:54:16 +00:00
|
|
|
BORDER(Bottom)
|
|
|
|
BORDER(Left)
|
|
|
|
BORDER(Right)
|
|
|
|
BORDER(Top)
|
|
|
|
#undef BORDER
|
2015-10-12 10:00:33 +00:00
|
|
|
|
2015-10-15 10:50:28 +00:00
|
|
|
void AbstractClient::addRepaintDuringGeometryUpdates()
|
|
|
|
{
|
2021-02-05 12:46:22 +00:00
|
|
|
const QRect deco_rect = visibleGeometry();
|
2015-10-15 10:50:28 +00:00
|
|
|
addLayerRepaint(m_visibleRectBeforeGeometryUpdate);
|
|
|
|
addLayerRepaint(deco_rect); // trigger repaint of window's new location
|
|
|
|
m_visibleRectBeforeGeometryUpdate = deco_rect;
|
|
|
|
}
|
|
|
|
|
2019-10-10 11:52:26 +00:00
|
|
|
QRect AbstractClient::bufferGeometryBeforeUpdateBlocking() const
|
|
|
|
{
|
|
|
|
return m_bufferGeometryBeforeUpdateBlocking;
|
|
|
|
}
|
|
|
|
|
2019-10-10 11:49:11 +00:00
|
|
|
QRect AbstractClient::frameGeometryBeforeUpdateBlocking() const
|
|
|
|
{
|
|
|
|
return m_frameGeometryBeforeUpdateBlocking;
|
|
|
|
}
|
|
|
|
|
2020-06-01 10:43:49 +00:00
|
|
|
QRect AbstractClient::clientGeometryBeforeUpdateBlocking() const
|
|
|
|
{
|
|
|
|
return m_clientGeometryBeforeUpdateBlocking;
|
|
|
|
}
|
|
|
|
|
2015-10-15 11:11:21 +00:00
|
|
|
void AbstractClient::updateGeometryBeforeUpdateBlocking()
|
|
|
|
{
|
2019-10-10 11:52:26 +00:00
|
|
|
m_bufferGeometryBeforeUpdateBlocking = bufferGeometry();
|
2019-10-10 11:49:11 +00:00
|
|
|
m_frameGeometryBeforeUpdateBlocking = frameGeometry();
|
2020-06-01 10:43:49 +00:00
|
|
|
m_clientGeometryBeforeUpdateBlocking = clientGeometry();
|
2015-10-15 11:11:21 +00:00
|
|
|
}
|
|
|
|
|
2015-10-15 13:28:01 +00:00
|
|
|
void AbstractClient::doMove(int, int)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-10-16 07:57:53 +00:00
|
|
|
void AbstractClient::updateInitialMoveResizeGeometry()
|
|
|
|
{
|
2019-09-27 10:01:10 +00:00
|
|
|
m_moveResize.initialGeometry = frameGeometry();
|
2015-10-16 09:13:41 +00:00
|
|
|
m_moveResize.geometry = m_moveResize.initialGeometry;
|
2015-10-16 13:53:28 +00:00
|
|
|
m_moveResize.startScreen = screen();
|
2015-10-16 07:57:53 +00:00
|
|
|
}
|
|
|
|
|
2015-10-16 13:27:53 +00:00
|
|
|
void AbstractClient::updateCursor()
|
|
|
|
{
|
|
|
|
Position m = moveResizePointerMode();
|
|
|
|
if (!isResizable() || isShade())
|
|
|
|
m = PositionCenter;
|
2018-06-07 00:33:54 +00:00
|
|
|
CursorShape c = Qt::ArrowCursor;
|
2015-10-16 13:27:53 +00:00
|
|
|
switch(m) {
|
|
|
|
case PositionTopLeft:
|
2018-06-07 00:33:54 +00:00
|
|
|
c = KWin::ExtendedCursor::SizeNorthWest;
|
|
|
|
break;
|
2015-10-16 13:27:53 +00:00
|
|
|
case PositionBottomRight:
|
2018-06-07 00:33:54 +00:00
|
|
|
c = KWin::ExtendedCursor::SizeSouthEast;
|
2015-10-16 13:27:53 +00:00
|
|
|
break;
|
|
|
|
case PositionBottomLeft:
|
2018-06-07 00:33:54 +00:00
|
|
|
c = KWin::ExtendedCursor::SizeSouthWest;
|
|
|
|
break;
|
2015-10-16 13:27:53 +00:00
|
|
|
case PositionTopRight:
|
2018-06-07 00:33:54 +00:00
|
|
|
c = KWin::ExtendedCursor::SizeNorthEast;
|
2015-10-16 13:27:53 +00:00
|
|
|
break;
|
|
|
|
case PositionTop:
|
2018-06-07 00:33:54 +00:00
|
|
|
c = KWin::ExtendedCursor::SizeNorth;
|
|
|
|
break;
|
2015-10-16 13:27:53 +00:00
|
|
|
case PositionBottom:
|
2018-06-07 00:33:54 +00:00
|
|
|
c = KWin::ExtendedCursor::SizeSouth;
|
2015-10-16 13:27:53 +00:00
|
|
|
break;
|
|
|
|
case PositionLeft:
|
2018-06-07 00:33:54 +00:00
|
|
|
c = KWin::ExtendedCursor::SizeWest;
|
|
|
|
break;
|
2015-10-16 13:27:53 +00:00
|
|
|
case PositionRight:
|
2018-06-07 00:33:54 +00:00
|
|
|
c = KWin::ExtendedCursor::SizeEast;
|
2015-10-16 13:27:53 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (isMoveResize())
|
|
|
|
c = Qt::SizeAllCursor;
|
|
|
|
else
|
|
|
|
c = Qt::ArrowCursor;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (c == m_moveResize.cursor)
|
|
|
|
return;
|
|
|
|
m_moveResize.cursor = c;
|
|
|
|
emit moveResizeCursorChanged(c);
|
|
|
|
}
|
|
|
|
|
2015-10-22 13:58:24 +00:00
|
|
|
void AbstractClient::leaveMoveResize()
|
|
|
|
{
|
2019-04-18 12:28:11 +00:00
|
|
|
workspace()->setMoveResizeClient(nullptr);
|
2015-10-22 13:58:24 +00:00
|
|
|
setMoveResize(false);
|
|
|
|
if (ScreenEdges::self()->isDesktopSwitchingMovingClients())
|
|
|
|
ScreenEdges::self()->reserveDesktopSwitching(false, Qt::Vertical|Qt::Horizontal);
|
|
|
|
if (isElectricBorderMaximizing()) {
|
|
|
|
outline()->hide();
|
|
|
|
elevate(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-22 15:08:17 +00:00
|
|
|
bool AbstractClient::s_haveResizeEffect = false;
|
|
|
|
|
|
|
|
void AbstractClient::updateHaveResizeEffect()
|
|
|
|
{
|
|
|
|
s_haveResizeEffect = effects && static_cast<EffectsHandlerImpl*>(effects)->provides(Effect::Resize);
|
|
|
|
}
|
|
|
|
|
2015-10-22 16:16:21 +00:00
|
|
|
bool AbstractClient::doStartMoveResize()
|
2015-10-22 15:27:05 +00:00
|
|
|
{
|
2015-10-22 16:16:21 +00:00
|
|
|
return true;
|
2015-10-22 15:27:05 +00:00
|
|
|
}
|
|
|
|
|
2020-02-17 18:39:17 +00:00
|
|
|
void AbstractClient::doFinishMoveResize()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-10-23 10:27:06 +00:00
|
|
|
void AbstractClient::positionGeometryTip()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-10-23 11:05:55 +00:00
|
|
|
void AbstractClient::doPerformMoveResize()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-10-23 11:33:18 +00:00
|
|
|
bool AbstractClient::isWaitingForMoveResizeSync() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::doResizeSync()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-10-23 11:32:18 +00:00
|
|
|
void AbstractClient::checkQuickTilingMaximizationZones(int xroot, int yroot)
|
|
|
|
{
|
2017-07-18 19:12:37 +00:00
|
|
|
QuickTileMode mode = QuickTileFlag::None;
|
2015-10-23 11:32:18 +00:00
|
|
|
bool innerBorder = false;
|
|
|
|
for (int i=0; i < screens()->count(); ++i) {
|
|
|
|
|
|
|
|
if (!screens()->geometry(i).contains(QPoint(xroot, yroot)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
auto isInScreen = [i](const QPoint &pt) {
|
|
|
|
for (int j = 0; j < screens()->count(); ++j) {
|
|
|
|
if (j == i)
|
|
|
|
continue;
|
|
|
|
if (screens()->geometry(j).contains(pt)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
QRect area = workspace()->clientArea(MaximizeArea, QPoint(xroot, yroot), desktop());
|
|
|
|
if (options->electricBorderTiling()) {
|
|
|
|
if (xroot <= area.x() + 20) {
|
2017-07-18 19:12:37 +00:00
|
|
|
mode |= QuickTileFlag::Left;
|
2015-10-23 11:32:18 +00:00
|
|
|
innerBorder = isInScreen(QPoint(area.x() - 1, yroot));
|
|
|
|
} else if (xroot >= area.x() + area.width() - 20) {
|
2017-07-18 19:12:37 +00:00
|
|
|
mode |= QuickTileFlag::Right;
|
2015-10-23 11:32:18 +00:00
|
|
|
innerBorder = isInScreen(QPoint(area.right() + 1, yroot));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-18 19:12:37 +00:00
|
|
|
if (mode != QuickTileMode(QuickTileFlag::None)) {
|
2015-10-23 11:32:18 +00:00
|
|
|
if (yroot <= area.y() + area.height() * options->electricBorderCornerRatio())
|
2017-07-18 19:12:37 +00:00
|
|
|
mode |= QuickTileFlag::Top;
|
2015-10-23 11:32:18 +00:00
|
|
|
else if (yroot >= area.y() + area.height() - area.height() * options->electricBorderCornerRatio())
|
2017-07-18 19:12:37 +00:00
|
|
|
mode |= QuickTileFlag::Bottom;
|
2015-10-23 11:32:18 +00:00
|
|
|
} else if (options->electricBorderMaximize() && yroot <= area.y() + 5 && isMaximizable()) {
|
2017-07-18 19:12:37 +00:00
|
|
|
mode = QuickTileFlag::Maximize;
|
2015-10-23 11:32:18 +00:00
|
|
|
innerBorder = isInScreen(QPoint(xroot, area.y() - 1));
|
|
|
|
}
|
|
|
|
break; // no point in checking other screens to contain this... "point"...
|
|
|
|
}
|
|
|
|
if (mode != electricBorderMode()) {
|
|
|
|
setElectricBorderMode(mode);
|
|
|
|
if (innerBorder) {
|
|
|
|
if (!m_electricMaximizingDelay) {
|
|
|
|
m_electricMaximizingDelay = new QTimer(this);
|
|
|
|
m_electricMaximizingDelay->setInterval(250);
|
|
|
|
m_electricMaximizingDelay->setSingleShot(true);
|
|
|
|
connect(m_electricMaximizingDelay, &QTimer::timeout, [this]() {
|
|
|
|
if (isMove())
|
2017-07-18 19:12:37 +00:00
|
|
|
setElectricBorderMaximizing(electricBorderMode() != QuickTileMode(QuickTileFlag::None));
|
2015-10-23 11:32:18 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
m_electricMaximizingDelay->start();
|
|
|
|
} else {
|
2017-07-18 19:12:37 +00:00
|
|
|
setElectricBorderMaximizing(mode != QuickTileMode(QuickTileFlag::None));
|
2015-10-23 11:32:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-23 13:32:10 +00:00
|
|
|
void AbstractClient::keyPressEvent(uint key_code)
|
|
|
|
{
|
|
|
|
if (!isMove() && !isResize())
|
|
|
|
return;
|
|
|
|
bool is_control = key_code & Qt::CTRL;
|
|
|
|
bool is_alt = key_code & Qt::ALT;
|
|
|
|
key_code = key_code & ~Qt::KeyboardModifierMask;
|
|
|
|
int delta = is_control ? 1 : is_alt ? 32 : 8;
|
2020-04-02 16:18:01 +00:00
|
|
|
QPoint pos = Cursors::self()->mouse()->pos();
|
2015-10-23 13:32:10 +00:00
|
|
|
switch(key_code) {
|
|
|
|
case Qt::Key_Left:
|
|
|
|
pos.rx() -= delta;
|
|
|
|
break;
|
|
|
|
case Qt::Key_Right:
|
|
|
|
pos.rx() += delta;
|
|
|
|
break;
|
|
|
|
case Qt::Key_Up:
|
|
|
|
pos.ry() -= delta;
|
|
|
|
break;
|
|
|
|
case Qt::Key_Down:
|
|
|
|
pos.ry() += delta;
|
|
|
|
break;
|
|
|
|
case Qt::Key_Space:
|
|
|
|
case Qt::Key_Return:
|
|
|
|
case Qt::Key_Enter:
|
|
|
|
setMoveResizePointerButtonDown(false);
|
[wayland] Don't crash when resizing windows
Summary:
If you resize a decorated client by using the resize user action(press
Alt + F3 > More Actions > Resize), then KWin will crash because it gets
stuck in an infinite loop (AbstractClient::performMoveResize <->
ShellClient::setGeometry).
Here's how KWin gets stuck in that loop:
* when you finish resizing the client, AbstractClient::keyPressEvent
will call AbstractClient::finishMoveResize;
* the first thing that finishMoveResize does is block geometry updates,
then it does some clean up (e.g. reset the value of isMoveResize(), etc),
updates the geometry of the client and when it's done, it will emit
clientFinishUserMoveResized signal;
* when PointerInputRedirection notices that signal, it will call
processDecorationMove on the client, which in its turn will indirectly
call AbstractClient::startMoveResize;
* when it's time to go back to AbstractClient::keyPressEvent, geometry
updates are unblocked and if there are any pending geometry updates,
then ShellClient::setGeometry will be called;
* ShellClient::setGeometry will eventually call ShellClient::doSetGeometry;
* ShellClient::doSetGeometry will call AbstractClient::performMoveResize
because AbstractClient::processDecorationMove indirectly called
AbstractClient::startMoveResize;
* AbstractClient::performMoveResize calls ShellClient::setGeometry;
* (at this point, KWin got stuck in the infinite loop)
This change swaps setMoveResizePointerButtonDown and finishMoveResize,
so processDecorationMove won't indirectly call startMoveResize.
BUG: 397577
FIXED-IN: 5.14.4
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D16846
2018-11-12 15:45:14 +00:00
|
|
|
finishMoveResize(false);
|
2015-10-23 13:32:10 +00:00
|
|
|
updateCursor();
|
|
|
|
break;
|
|
|
|
case Qt::Key_Escape:
|
|
|
|
setMoveResizePointerButtonDown(false);
|
[wayland] Don't crash when resizing windows
Summary:
If you resize a decorated client by using the resize user action(press
Alt + F3 > More Actions > Resize), then KWin will crash because it gets
stuck in an infinite loop (AbstractClient::performMoveResize <->
ShellClient::setGeometry).
Here's how KWin gets stuck in that loop:
* when you finish resizing the client, AbstractClient::keyPressEvent
will call AbstractClient::finishMoveResize;
* the first thing that finishMoveResize does is block geometry updates,
then it does some clean up (e.g. reset the value of isMoveResize(), etc),
updates the geometry of the client and when it's done, it will emit
clientFinishUserMoveResized signal;
* when PointerInputRedirection notices that signal, it will call
processDecorationMove on the client, which in its turn will indirectly
call AbstractClient::startMoveResize;
* when it's time to go back to AbstractClient::keyPressEvent, geometry
updates are unblocked and if there are any pending geometry updates,
then ShellClient::setGeometry will be called;
* ShellClient::setGeometry will eventually call ShellClient::doSetGeometry;
* ShellClient::doSetGeometry will call AbstractClient::performMoveResize
because AbstractClient::processDecorationMove indirectly called
AbstractClient::startMoveResize;
* AbstractClient::performMoveResize calls ShellClient::setGeometry;
* (at this point, KWin got stuck in the infinite loop)
This change swaps setMoveResizePointerButtonDown and finishMoveResize,
so processDecorationMove won't indirectly call startMoveResize.
BUG: 397577
FIXED-IN: 5.14.4
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D16846
2018-11-12 15:45:14 +00:00
|
|
|
finishMoveResize(true);
|
2015-10-23 13:32:10 +00:00
|
|
|
updateCursor();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
2020-04-02 16:18:01 +00:00
|
|
|
Cursors::self()->mouse()->setPos(pos);
|
2015-10-23 13:32:10 +00:00
|
|
|
}
|
|
|
|
|
2015-10-26 14:49:03 +00:00
|
|
|
QSize AbstractClient::resizeIncrements() const
|
|
|
|
{
|
|
|
|
return QSize(1, 1);
|
|
|
|
}
|
|
|
|
|
2015-12-03 14:27:21 +00:00
|
|
|
void AbstractClient::dontMoveResize()
|
|
|
|
{
|
|
|
|
setMoveResizePointerButtonDown(false);
|
|
|
|
stopDelayedMoveResize();
|
|
|
|
if (isMoveResize())
|
|
|
|
finishMoveResize(false);
|
|
|
|
}
|
|
|
|
|
2015-10-28 09:43:49 +00:00
|
|
|
AbstractClient::Position AbstractClient::mousePosition() const
|
|
|
|
{
|
2015-12-03 13:07:32 +00:00
|
|
|
if (isDecorated()) {
|
|
|
|
switch (decoration()->sectionUnderMouse()) {
|
|
|
|
case Qt::BottomLeftSection:
|
|
|
|
return PositionBottomLeft;
|
|
|
|
case Qt::BottomRightSection:
|
|
|
|
return PositionBottomRight;
|
|
|
|
case Qt::BottomSection:
|
|
|
|
return PositionBottom;
|
|
|
|
case Qt::LeftSection:
|
|
|
|
return PositionLeft;
|
|
|
|
case Qt::RightSection:
|
|
|
|
return PositionRight;
|
|
|
|
case Qt::TopSection:
|
|
|
|
return PositionTop;
|
|
|
|
case Qt::TopLeftSection:
|
|
|
|
return PositionTopLeft;
|
|
|
|
case Qt::TopRightSection:
|
|
|
|
return PositionTopRight;
|
|
|
|
default:
|
|
|
|
return PositionCenter;
|
|
|
|
}
|
|
|
|
}
|
2015-10-28 09:43:49 +00:00
|
|
|
return PositionCenter;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::endMoveResize()
|
|
|
|
{
|
|
|
|
setMoveResizePointerButtonDown(false);
|
|
|
|
stopDelayedMoveResize();
|
|
|
|
if (isMoveResize()) {
|
|
|
|
finishMoveResize(false);
|
|
|
|
setMoveResizePointerMode(mousePosition());
|
|
|
|
}
|
|
|
|
updateCursor();
|
|
|
|
}
|
|
|
|
|
2020-02-25 12:57:44 +00:00
|
|
|
void AbstractClient::createDecoration(const QRect &oldGeometry)
|
|
|
|
{
|
|
|
|
KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this);
|
|
|
|
if (decoration) {
|
|
|
|
QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection);
|
|
|
|
connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::updateShadow);
|
2020-10-17 12:43:16 +00:00
|
|
|
connect(decoration, &KDecoration2::Decoration::bordersChanged,
|
|
|
|
this, &AbstractClient::updateDecorationInputShape);
|
|
|
|
connect(decoration, &KDecoration2::Decoration::resizeOnlyBordersChanged,
|
|
|
|
this, &AbstractClient::updateDecorationInputShape);
|
2020-02-25 12:57:44 +00:00
|
|
|
connect(decoration, &KDecoration2::Decoration::bordersChanged, this, [this]() {
|
|
|
|
GeometryUpdatesBlocker blocker(this);
|
|
|
|
const QRect oldGeometry = frameGeometry();
|
|
|
|
if (!isShade()) {
|
|
|
|
checkWorkspacePosition(oldGeometry);
|
|
|
|
}
|
|
|
|
emit geometryShapeChanged(this, oldGeometry);
|
|
|
|
});
|
2020-10-17 12:43:16 +00:00
|
|
|
connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::sizeChanged,
|
|
|
|
this, &AbstractClient::updateDecorationInputShape);
|
2020-02-25 12:57:44 +00:00
|
|
|
}
|
|
|
|
setDecoration(decoration);
|
|
|
|
setFrameGeometry(QRect(oldGeometry.topLeft(), clientSizeToFrameSize(clientSize())));
|
2020-10-17 12:43:16 +00:00
|
|
|
updateDecorationInputShape();
|
2020-02-25 12:57:44 +00:00
|
|
|
|
|
|
|
emit geometryShapeChanged(this, oldGeometry);
|
|
|
|
}
|
|
|
|
|
2015-12-03 12:24:44 +00:00
|
|
|
void AbstractClient::destroyDecoration()
|
|
|
|
{
|
2015-12-04 07:12:49 +00:00
|
|
|
delete m_decoration.decoration;
|
|
|
|
m_decoration.decoration = nullptr;
|
2020-10-17 12:43:16 +00:00
|
|
|
m_decoration.inputRegion = QRegion();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::updateDecorationInputShape()
|
|
|
|
{
|
|
|
|
if (!isDecorated()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const QMargins borders = decoration()->borders();
|
|
|
|
const QMargins resizeBorders = decoration()->resizeOnlyBorders();
|
|
|
|
|
|
|
|
const QRect innerRect = QRect(QPoint(borderLeft(), borderTop()), decoratedClient()->size());
|
|
|
|
const QRect outerRect = innerRect + borders + resizeBorders;
|
|
|
|
|
|
|
|
m_decoration.inputRegion = QRegion(outerRect) - innerRect;
|
2015-12-03 12:24:44 +00:00
|
|
|
}
|
|
|
|
|
2015-12-03 12:28:22 +00:00
|
|
|
bool AbstractClient::decorationHasAlpha() const
|
|
|
|
{
|
|
|
|
if (!isDecorated() || decoration()->isOpaque()) {
|
|
|
|
// either no decoration or decoration has alpha disabled
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-12-03 12:31:25 +00:00
|
|
|
void AbstractClient::triggerDecorationRepaint()
|
|
|
|
{
|
|
|
|
if (isDecorated()) {
|
|
|
|
decoration()->update();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-03 12:48:54 +00:00
|
|
|
void AbstractClient::layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const
|
|
|
|
{
|
|
|
|
if (!isDecorated()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
QRect r = decoration()->rect();
|
|
|
|
|
|
|
|
top = QRect(r.x(), r.y(), r.width(), borderTop());
|
|
|
|
bottom = QRect(r.x(), r.y() + r.height() - borderBottom(),
|
|
|
|
r.width(), borderBottom());
|
|
|
|
left = QRect(r.x(), r.y() + top.height(),
|
|
|
|
borderLeft(), r.height() - top.height() - bottom.height());
|
|
|
|
right = QRect(r.x() + r.width() - borderRight(), r.y() + top.height(),
|
|
|
|
borderRight(), r.height() - top.height() - bottom.height());
|
|
|
|
}
|
|
|
|
|
2016-03-11 11:48:01 +00:00
|
|
|
void AbstractClient::processDecorationMove(const QPoint &localPos, const QPoint &globalPos)
|
2015-12-03 13:10:12 +00:00
|
|
|
{
|
|
|
|
if (isMoveResizePointerButtonDown()) {
|
2016-03-11 11:48:01 +00:00
|
|
|
handleMoveResize(localPos.x(), localPos.y(), globalPos.x(), globalPos.y());
|
2015-12-03 13:10:12 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
// TODO: handle modifiers
|
|
|
|
Position newmode = mousePosition();
|
|
|
|
if (newmode != moveResizePointerMode()) {
|
|
|
|
setMoveResizePointerMode(newmode);
|
|
|
|
updateCursor();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-03 15:40:27 +00:00
|
|
|
bool AbstractClient::processDecorationButtonPress(QMouseEvent *event, bool ignoreMenu)
|
|
|
|
{
|
|
|
|
Options::MouseCommand com = Options::MouseNothing;
|
|
|
|
bool active = isActive();
|
|
|
|
if (!wantsInput()) // we cannot be active, use it anyway
|
|
|
|
active = true;
|
|
|
|
|
|
|
|
// check whether it is a double click
|
2016-05-12 08:28:40 +00:00
|
|
|
if (event->button() == Qt::LeftButton && titlebarPositionUnderMouse()) {
|
2016-01-05 10:43:12 +00:00
|
|
|
if (m_decoration.doubleClickTimer.isValid()) {
|
2016-06-29 08:52:43 +00:00
|
|
|
const qint64 interval = m_decoration.doubleClickTimer.elapsed();
|
2015-12-04 07:12:49 +00:00
|
|
|
m_decoration.doubleClickTimer.invalidate();
|
2016-01-05 10:43:12 +00:00
|
|
|
if (interval > QGuiApplication::styleHints()->mouseDoubleClickInterval()) {
|
2017-06-22 04:52:57 +00:00
|
|
|
m_decoration.doubleClickTimer.start(); // expired -> new first click and pot. init
|
2016-01-05 10:43:12 +00:00
|
|
|
} else {
|
|
|
|
Workspace::self()->performWindowOperation(this, options->operationTitlebarDblClick());
|
|
|
|
dontMoveResize();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
m_decoration.doubleClickTimer.start(); // new first click and pot. init, could be invalidated by release - see below
|
2015-12-03 15:40:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event->button() == Qt::LeftButton)
|
|
|
|
com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1();
|
2020-09-01 05:14:58 +00:00
|
|
|
else if (event->button() == Qt::MiddleButton)
|
2015-12-03 15:40:27 +00:00
|
|
|
com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2();
|
|
|
|
else if (event->button() == Qt::RightButton)
|
|
|
|
com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3();
|
|
|
|
if (event->button() == Qt::LeftButton
|
|
|
|
&& com != Options::MouseOperationsMenu // actions where it's not possible to get the matching
|
2019-09-14 08:58:12 +00:00
|
|
|
&& com != Options::MouseMinimize) // mouse release event
|
|
|
|
{
|
2015-12-03 15:40:27 +00:00
|
|
|
setMoveResizePointerMode(mousePosition());
|
|
|
|
setMoveResizePointerButtonDown(true);
|
|
|
|
setMoveOffset(event->pos());
|
|
|
|
setInvertedMoveOffset(rect().bottomRight() - moveOffset());
|
|
|
|
setUnrestrictedMoveResize(false);
|
|
|
|
startDelayedMoveResize();
|
|
|
|
updateCursor();
|
|
|
|
}
|
|
|
|
// In the new API the decoration may process the menu action to display an inactive tab's menu.
|
|
|
|
// If the event is unhandled then the core will create one for the active window in the group.
|
|
|
|
if (!ignoreMenu || com != Options::MouseOperationsMenu)
|
|
|
|
performMouseCommand(com, event->globalPos());
|
|
|
|
return !( // Return events that should be passed to the decoration in the new API
|
|
|
|
com == Options::MouseRaise ||
|
|
|
|
com == Options::MouseOperationsMenu ||
|
|
|
|
com == Options::MouseActivateAndRaise ||
|
|
|
|
com == Options::MouseActivate ||
|
|
|
|
com == Options::MouseActivateRaiseAndPassClick ||
|
|
|
|
com == Options::MouseActivateAndPassClick ||
|
|
|
|
com == Options::MouseNothing);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::processDecorationButtonRelease(QMouseEvent *event)
|
|
|
|
{
|
|
|
|
if (isDecorated()) {
|
2016-05-12 08:28:40 +00:00
|
|
|
if (event->isAccepted() || !titlebarPositionUnderMouse()) {
|
2016-02-17 13:41:26 +00:00
|
|
|
invalidateDecorationDoubleClickTimer(); // click was for the deco and shall not init a doubleclick
|
2015-12-03 15:40:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event->buttons() == Qt::NoButton) {
|
|
|
|
setMoveResizePointerButtonDown(false);
|
|
|
|
stopDelayedMoveResize();
|
|
|
|
if (isMoveResize()) {
|
|
|
|
finishMoveResize(false);
|
|
|
|
setMoveResizePointerMode(mousePosition());
|
|
|
|
}
|
|
|
|
updateCursor();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void AbstractClient::startDecorationDoubleClickTimer()
|
|
|
|
{
|
2015-12-04 07:12:49 +00:00
|
|
|
m_decoration.doubleClickTimer.start();
|
2015-12-03 15:40:27 +00:00
|
|
|
}
|
|
|
|
|
2016-01-05 10:43:12 +00:00
|
|
|
void AbstractClient::invalidateDecorationDoubleClickTimer()
|
|
|
|
{
|
|
|
|
m_decoration.doubleClickTimer.invalidate();
|
|
|
|
}
|
|
|
|
|
2015-12-03 16:11:14 +00:00
|
|
|
bool AbstractClient::providesContextHelp() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::showContextHelp()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-12-03 16:27:00 +00:00
|
|
|
QPointer<Decoration::DecoratedClientImpl> AbstractClient::decoratedClient() const
|
|
|
|
{
|
2015-12-04 07:12:49 +00:00
|
|
|
return m_decoration.client;
|
2015-12-03 16:27:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::setDecoratedClient(QPointer< Decoration::DecoratedClientImpl > client)
|
|
|
|
{
|
2015-12-04 07:12:49 +00:00
|
|
|
m_decoration.client = client;
|
2015-12-03 16:27:00 +00:00
|
|
|
}
|
|
|
|
|
2016-02-18 10:30:52 +00:00
|
|
|
void AbstractClient::enterEvent(const QPoint &globalPos)
|
|
|
|
{
|
2020-05-07 16:26:24 +00:00
|
|
|
if (options->isShadeHover()) {
|
|
|
|
cancelShadeHoverTimer();
|
|
|
|
startShadeHoverTimer();
|
|
|
|
}
|
|
|
|
|
2016-02-18 10:30:52 +00:00
|
|
|
if (options->focusPolicy() == Options::ClickToFocus || workspace()->userActionsMenu()->isShown())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (options->isAutoRaise() && !isDesktop() &&
|
|
|
|
!isDock() && workspace()->focusChangeEnabled() &&
|
|
|
|
globalPos != workspace()->focusMousePosition() &&
|
|
|
|
workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(),
|
|
|
|
options->isSeparateScreenFocus() ? screen() : -1) != this) {
|
|
|
|
startAutoRaise();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isDesktop() || isDock())
|
|
|
|
return;
|
|
|
|
// for FocusFollowsMouse, change focus only if the mouse has actually been moved, not if the focus
|
|
|
|
// change came because of window changes (e.g. closing a window) - #92290
|
|
|
|
if (options->focusPolicy() != Options::FocusFollowsMouse
|
|
|
|
|| globalPos != workspace()->focusMousePosition()) {
|
|
|
|
workspace()->requestDelayFocus(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-18 12:00:50 +00:00
|
|
|
void AbstractClient::leaveEvent()
|
|
|
|
{
|
|
|
|
cancelAutoRaise();
|
|
|
|
workspace()->cancelDelayFocus();
|
2020-05-07 16:26:24 +00:00
|
|
|
cancelShadeHoverTimer();
|
|
|
|
startShadeUnhoverTimer();
|
2016-02-18 12:00:50 +00:00
|
|
|
// TODO: send hover leave to deco
|
|
|
|
// TODO: handle Options::FocusStrictlyUnderMouse
|
|
|
|
}
|
|
|
|
|
2016-08-22 14:55:48 +00:00
|
|
|
QRect AbstractClient::iconGeometry() const
|
|
|
|
{
|
|
|
|
if (!windowManagementInterface() || !waylandServer()) {
|
|
|
|
// window management interface is only available if the surface is mapped
|
|
|
|
return QRect();
|
|
|
|
}
|
|
|
|
|
|
|
|
int minDistance = INT_MAX;
|
|
|
|
AbstractClient *candidatePanel = nullptr;
|
|
|
|
QRect candidateGeom;
|
|
|
|
|
|
|
|
for (auto i = windowManagementInterface()->minimizedGeometries().constBegin(), end = windowManagementInterface()->minimizedGeometries().constEnd(); i != end; ++i) {
|
2020-03-04 07:55:26 +00:00
|
|
|
AbstractClient *client = waylandServer()->findClient(i.key());
|
2016-08-22 14:55:48 +00:00
|
|
|
if (!client) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const int distance = QPoint(client->pos() - pos()).manhattanLength();
|
|
|
|
if (distance < minDistance) {
|
|
|
|
minDistance = distance;
|
|
|
|
candidatePanel = client;
|
|
|
|
candidateGeom = i.value();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!candidatePanel) {
|
|
|
|
return QRect();
|
|
|
|
}
|
|
|
|
return candidateGeom.translated(candidatePanel->pos());
|
|
|
|
}
|
|
|
|
|
2016-09-15 09:44:32 +00:00
|
|
|
QRect AbstractClient::inputGeometry() const
|
|
|
|
{
|
|
|
|
if (isDecorated()) {
|
|
|
|
return Toplevel::inputGeometry() + decoration()->resizeOnlyBorders();
|
|
|
|
}
|
|
|
|
return Toplevel::inputGeometry();
|
|
|
|
}
|
virtualkeyboard: resize the focused window to make room for the keyboard
Summary:
alternative approach: try to resize the winidow to make room for the keyboard.
the new input wayland protocol doesn't have anymore the overlap rectangle (and it would not be going to work with qwidget apps anyways)
in the future will probably be needed anextension to the input protocol v3 which partially gets back this, tough window resizing is needed regardless
what's missing: the resize should be "temporary" and the window should be restored to its previous geometry when the keyboard closes
Test Plan: tested with test QML code
Reviewers: #plasma, #kwin, bshah, graesslin, romangg, davidedmundson
Reviewed By: #plasma, #kwin, romangg, davidedmundson
Subscribers: nicolasfella, mart, kwin, davidedmundson, graesslin
Tags: #kwin
Maniphest Tasks: T9815
Differential Revision: https://phabricator.kde.org/D18818
2019-03-20 10:04:51 +00:00
|
|
|
|
Adapt to input region changes in kwayland-server
SurfaceInterface::inputIsInfinite() has been dropped. If the surface has
no any input region specified, SurfaceInterface::input() will return a
region that corresponds to the rect of the surface (0, 0, width, height).
While the new design is more robust, for example it's no longer possible
to forget to check SurfaceInterface::inputIsInfinite(), it has shown some
issues in the input stack of kwin.
Currently, acceptsInput() will return false if you attempt to click the
server-side decoration for a surface whose input region is not empty.
Therefore, it's possible for an application to set an input region with
a width and a height of 1. If user doesn't know about KSysGuard or the
possibility of closing apps via the task manager, they won't be able to
close such an application.
Another issue is that if an application has specified an empty input
region on purpose, user will be still able click it. With the new
behavior of SurfaceInterface::input(), this is no longer an issue and it
is handled properly by kwin.
2020-10-17 12:47:25 +00:00
|
|
|
bool AbstractClient::hitTest(const QPoint &point) const
|
|
|
|
{
|
|
|
|
if (isDecorated()) {
|
|
|
|
if (m_decoration.inputRegion.contains(mapToFrame(point))) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Toplevel::hitTest(point);
|
|
|
|
}
|
|
|
|
|
virtualkeyboard: resize the focused window to make room for the keyboard
Summary:
alternative approach: try to resize the winidow to make room for the keyboard.
the new input wayland protocol doesn't have anymore the overlap rectangle (and it would not be going to work with qwidget apps anyways)
in the future will probably be needed anextension to the input protocol v3 which partially gets back this, tough window resizing is needed regardless
what's missing: the resize should be "temporary" and the window should be restored to its previous geometry when the keyboard closes
Test Plan: tested with test QML code
Reviewers: #plasma, #kwin, bshah, graesslin, romangg, davidedmundson
Reviewed By: #plasma, #kwin, romangg, davidedmundson
Subscribers: nicolasfella, mart, kwin, davidedmundson, graesslin
Tags: #kwin
Maniphest Tasks: T9815
Differential Revision: https://phabricator.kde.org/D18818
2019-03-20 10:04:51 +00:00
|
|
|
QRect AbstractClient::virtualKeyboardGeometry() const
|
|
|
|
{
|
|
|
|
return m_virtualKeyboardGeometry;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::setVirtualKeyboardGeometry(const QRect &geo)
|
|
|
|
{
|
|
|
|
// No keyboard anymore
|
|
|
|
if (geo.isEmpty() && !m_keyboardGeometryRestore.isEmpty()) {
|
2019-09-27 10:01:10 +00:00
|
|
|
setFrameGeometry(m_keyboardGeometryRestore);
|
virtualkeyboard: resize the focused window to make room for the keyboard
Summary:
alternative approach: try to resize the winidow to make room for the keyboard.
the new input wayland protocol doesn't have anymore the overlap rectangle (and it would not be going to work with qwidget apps anyways)
in the future will probably be needed anextension to the input protocol v3 which partially gets back this, tough window resizing is needed regardless
what's missing: the resize should be "temporary" and the window should be restored to its previous geometry when the keyboard closes
Test Plan: tested with test QML code
Reviewers: #plasma, #kwin, bshah, graesslin, romangg, davidedmundson
Reviewed By: #plasma, #kwin, romangg, davidedmundson
Subscribers: nicolasfella, mart, kwin, davidedmundson, graesslin
Tags: #kwin
Maniphest Tasks: T9815
Differential Revision: https://phabricator.kde.org/D18818
2019-03-20 10:04:51 +00:00
|
|
|
m_keyboardGeometryRestore = QRect();
|
|
|
|
} else if (geo.isEmpty()) {
|
|
|
|
return;
|
|
|
|
// The keyboard has just been opened (rather than resized) save client geometry for a restore
|
|
|
|
} else if (m_keyboardGeometryRestore.isEmpty()) {
|
2019-09-27 10:01:10 +00:00
|
|
|
m_keyboardGeometryRestore = frameGeometry();
|
virtualkeyboard: resize the focused window to make room for the keyboard
Summary:
alternative approach: try to resize the winidow to make room for the keyboard.
the new input wayland protocol doesn't have anymore the overlap rectangle (and it would not be going to work with qwidget apps anyways)
in the future will probably be needed anextension to the input protocol v3 which partially gets back this, tough window resizing is needed regardless
what's missing: the resize should be "temporary" and the window should be restored to its previous geometry when the keyboard closes
Test Plan: tested with test QML code
Reviewers: #plasma, #kwin, bshah, graesslin, romangg, davidedmundson
Reviewed By: #plasma, #kwin, romangg, davidedmundson
Subscribers: nicolasfella, mart, kwin, davidedmundson, graesslin
Tags: #kwin
Maniphest Tasks: T9815
Differential Revision: https://phabricator.kde.org/D18818
2019-03-20 10:04:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
m_virtualKeyboardGeometry = geo;
|
|
|
|
|
2019-03-20 16:42:37 +00:00
|
|
|
// Don't resize Desktop and fullscreen windows
|
|
|
|
if (isFullScreen() || isDesktop()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
virtualkeyboard: resize the focused window to make room for the keyboard
Summary:
alternative approach: try to resize the winidow to make room for the keyboard.
the new input wayland protocol doesn't have anymore the overlap rectangle (and it would not be going to work with qwidget apps anyways)
in the future will probably be needed anextension to the input protocol v3 which partially gets back this, tough window resizing is needed regardless
what's missing: the resize should be "temporary" and the window should be restored to its previous geometry when the keyboard closes
Test Plan: tested with test QML code
Reviewers: #plasma, #kwin, bshah, graesslin, romangg, davidedmundson
Reviewed By: #plasma, #kwin, romangg, davidedmundson
Subscribers: nicolasfella, mart, kwin, davidedmundson, graesslin
Tags: #kwin
Maniphest Tasks: T9815
Differential Revision: https://phabricator.kde.org/D18818
2019-03-20 10:04:51 +00:00
|
|
|
if (!geo.intersects(m_keyboardGeometryRestore)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const QRect availableArea = workspace()->clientArea(MaximizeArea, this);
|
|
|
|
QRect newWindowGeometry = m_keyboardGeometryRestore;
|
|
|
|
newWindowGeometry.moveBottom(geo.top());
|
|
|
|
newWindowGeometry.setTop(qMax(newWindowGeometry.top(), availableArea.top()));
|
|
|
|
|
2019-09-27 10:01:10 +00:00
|
|
|
setFrameGeometry(newWindowGeometry);
|
virtualkeyboard: resize the focused window to make room for the keyboard
Summary:
alternative approach: try to resize the winidow to make room for the keyboard.
the new input wayland protocol doesn't have anymore the overlap rectangle (and it would not be going to work with qwidget apps anyways)
in the future will probably be needed anextension to the input protocol v3 which partially gets back this, tough window resizing is needed regardless
what's missing: the resize should be "temporary" and the window should be restored to its previous geometry when the keyboard closes
Test Plan: tested with test QML code
Reviewers: #plasma, #kwin, bshah, graesslin, romangg, davidedmundson
Reviewed By: #plasma, #kwin, romangg, davidedmundson
Subscribers: nicolasfella, mart, kwin, davidedmundson, graesslin
Tags: #kwin
Maniphest Tasks: T9815
Differential Revision: https://phabricator.kde.org/D18818
2019-03-20 10:04:51 +00:00
|
|
|
}
|
2016-09-15 09:44:32 +00:00
|
|
|
|
2020-09-01 08:58:46 +00:00
|
|
|
QRect AbstractClient::keyboardGeometryRestore() const
|
|
|
|
{
|
|
|
|
return m_keyboardGeometryRestore;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::setKeyboardGeometryRestore(const QRect &geom)
|
|
|
|
{
|
|
|
|
m_keyboardGeometryRestore = geom;
|
|
|
|
}
|
|
|
|
|
2016-10-12 13:09:52 +00:00
|
|
|
bool AbstractClient::dockWantsInput() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-03-11 16:13:32 +00:00
|
|
|
void AbstractClient::setDesktopFileName(QByteArray name)
|
2016-10-27 13:59:08 +00:00
|
|
|
{
|
2018-03-11 16:13:32 +00:00
|
|
|
name = rules()->checkDesktopFile(name).toUtf8();
|
2016-10-27 13:59:08 +00:00
|
|
|
if (name == m_desktopFileName) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_desktopFileName = name;
|
2018-03-11 16:13:32 +00:00
|
|
|
updateWindowRules(Rules::DesktopFile);
|
2016-10-27 13:59:08 +00:00
|
|
|
emit desktopFileNameChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString AbstractClient::iconFromDesktopFile() const
|
|
|
|
{
|
2020-11-06 01:45:27 +00:00
|
|
|
if (m_desktopFileName.isEmpty()) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2020-05-13 17:54:03 +00:00
|
|
|
const QString desktopFileName = QString::fromUtf8(m_desktopFileName);
|
2020-05-14 15:25:32 +00:00
|
|
|
QString desktopFilePath;
|
|
|
|
|
|
|
|
if (QDir::isAbsolutePath(desktopFileName)) {
|
|
|
|
desktopFilePath = desktopFileName;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (desktopFilePath.isEmpty()) {
|
|
|
|
desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation,
|
|
|
|
desktopFileName);
|
|
|
|
}
|
2020-05-13 17:54:03 +00:00
|
|
|
if (desktopFilePath.isEmpty()) {
|
|
|
|
desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation,
|
|
|
|
desktopFileName + QLatin1String(".desktop"));
|
2016-10-27 13:59:08 +00:00
|
|
|
}
|
2020-05-13 17:54:03 +00:00
|
|
|
|
|
|
|
KDesktopFile df(desktopFilePath);
|
2016-10-27 13:59:08 +00:00
|
|
|
return df.readIcon();
|
|
|
|
}
|
|
|
|
|
2017-01-11 09:21:03 +00:00
|
|
|
bool AbstractClient::hasApplicationMenu() const
|
|
|
|
{
|
|
|
|
return ApplicationMenu::self()->applicationMenuEnabled() && !m_applicationMenuServiceName.isEmpty() && !m_applicationMenuObjectPath.isEmpty();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::updateApplicationMenuServiceName(const QString &serviceName)
|
|
|
|
{
|
|
|
|
const bool old_hasApplicationMenu = hasApplicationMenu();
|
|
|
|
|
|
|
|
m_applicationMenuServiceName = serviceName;
|
|
|
|
|
|
|
|
const bool new_hasApplicationMenu = hasApplicationMenu();
|
|
|
|
|
2020-03-03 22:26:10 +00:00
|
|
|
emit applicationMenuChanged();
|
2017-01-11 09:21:03 +00:00
|
|
|
if (old_hasApplicationMenu != new_hasApplicationMenu) {
|
|
|
|
emit hasApplicationMenuChanged(new_hasApplicationMenu);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::updateApplicationMenuObjectPath(const QString &objectPath)
|
|
|
|
{
|
|
|
|
const bool old_hasApplicationMenu = hasApplicationMenu();
|
|
|
|
|
|
|
|
m_applicationMenuObjectPath = objectPath;
|
|
|
|
|
|
|
|
const bool new_hasApplicationMenu = hasApplicationMenu();
|
|
|
|
|
2020-03-03 22:26:10 +00:00
|
|
|
emit applicationMenuChanged();
|
2017-01-11 09:21:03 +00:00
|
|
|
if (old_hasApplicationMenu != new_hasApplicationMenu) {
|
|
|
|
emit hasApplicationMenuChanged(new_hasApplicationMenu);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::setApplicationMenuActive(bool applicationMenuActive)
|
|
|
|
{
|
|
|
|
if (m_applicationMenuActive != applicationMenuActive) {
|
|
|
|
m_applicationMenuActive = applicationMenuActive;
|
|
|
|
emit applicationMenuActiveChanged(applicationMenuActive);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::showApplicationMenu(int actionId)
|
|
|
|
{
|
|
|
|
if (isDecorated()) {
|
|
|
|
decoration()->showApplicationMenu(actionId);
|
|
|
|
} else {
|
|
|
|
// we don't know where the application menu button will be, show it in the top left corner instead
|
|
|
|
Workspace::self()->showApplicationMenu(QRect(), this, actionId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-05 09:16:23 +00:00
|
|
|
bool AbstractClient::unresponsive() const
|
|
|
|
{
|
|
|
|
return m_unresponsive;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::setUnresponsive(bool unresponsive)
|
|
|
|
{
|
|
|
|
if (m_unresponsive != unresponsive) {
|
|
|
|
m_unresponsive = unresponsive;
|
|
|
|
emit unresponsiveChanged(m_unresponsive);
|
|
|
|
emit captionChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-03 05:11:40 +00:00
|
|
|
QString AbstractClient::shortcutCaptionSuffix() const
|
|
|
|
{
|
|
|
|
if (shortcut().isEmpty()) {
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
return QLatin1String(" {") + shortcut().toString() + QLatin1Char('}');
|
|
|
|
}
|
|
|
|
|
2017-08-20 06:56:13 +00:00
|
|
|
AbstractClient *AbstractClient::findClientWithSameCaption() const
|
|
|
|
{
|
|
|
|
auto fetchNameInternalPredicate = [this](const AbstractClient *cl) {
|
|
|
|
return (!cl->isSpecialWindow() || cl->isToolbar()) && cl != this && cl->captionNormal() == captionNormal() && cl->captionSuffix() == captionSuffix();
|
|
|
|
};
|
|
|
|
return workspace()->findAbstractClient(fetchNameInternalPredicate);
|
|
|
|
}
|
|
|
|
|
2017-08-20 07:35:15 +00:00
|
|
|
QString AbstractClient::caption() const
|
|
|
|
{
|
|
|
|
QString cap = captionNormal() + captionSuffix();
|
|
|
|
if (unresponsive()) {
|
|
|
|
cap += QLatin1String(" ");
|
|
|
|
cap += i18nc("Application is not responding, appended to window title", "(Not Responding)");
|
2017-08-20 07:24:10 +00:00
|
|
|
}
|
|
|
|
return cap;
|
|
|
|
}
|
|
|
|
|
2017-10-01 14:38:57 +00:00
|
|
|
void AbstractClient::removeRule(Rules* rule)
|
|
|
|
{
|
|
|
|
m_rules.remove(rule);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::discardTemporaryRules()
|
|
|
|
{
|
|
|
|
m_rules.discardTemporary();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::evaluateWindowRules()
|
|
|
|
{
|
|
|
|
setupWindowRules(true);
|
|
|
|
applyWindowRules();
|
|
|
|
}
|
|
|
|
|
2021-03-09 14:04:20 +00:00
|
|
|
/**
|
|
|
|
* Returns the list of activities the client window is on.
|
|
|
|
* if it's on all activities, the list will be empty.
|
|
|
|
* Don't use this, use isOnActivity() and friends (from class Toplevel)
|
|
|
|
*/
|
|
|
|
QStringList AbstractClient::activities() const
|
2017-10-01 14:38:57 +00:00
|
|
|
{
|
2021-03-09 14:04:20 +00:00
|
|
|
return m_activityList;
|
2017-10-01 14:38:57 +00:00
|
|
|
}
|
|
|
|
|
2021-03-05 11:12:03 +00:00
|
|
|
/**
|
|
|
|
* Sets whether the client is on @p activity.
|
|
|
|
* If you remove it from its last activity, then it's on all activities.
|
|
|
|
*
|
|
|
|
* Note: If it was on all activities and you try to remove it from one, nothing will happen;
|
|
|
|
* I don't think that's an important enough use case to handle here.
|
|
|
|
*/
|
|
|
|
void AbstractClient::setOnActivity(const QString &activity, bool enable)
|
|
|
|
{
|
|
|
|
#ifdef KWIN_BUILD_ACTIVITIES
|
|
|
|
if (!Activities::self()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
QStringList newActivitiesList = activities();
|
|
|
|
if (newActivitiesList.contains(activity) == enable) {
|
|
|
|
//nothing to do
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (enable) {
|
|
|
|
QStringList allActivities = Activities::self()->all();
|
|
|
|
if (!allActivities.contains(activity)) {
|
|
|
|
//bogus ID
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
newActivitiesList.append(activity);
|
|
|
|
} else {
|
|
|
|
newActivitiesList.removeOne(activity);
|
|
|
|
}
|
|
|
|
setOnActivities(newActivitiesList);
|
|
|
|
#else
|
|
|
|
Q_UNUSED(activity)
|
|
|
|
Q_UNUSED(enable)
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2021-03-09 14:04:20 +00:00
|
|
|
/**
|
|
|
|
* set exactly which activities this client is on
|
|
|
|
*/
|
|
|
|
void AbstractClient::setOnActivities(const QStringList &newActivitiesList)
|
|
|
|
{
|
|
|
|
#ifdef KWIN_BUILD_ACTIVITIES
|
|
|
|
if (!Activities::self()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto allActivities = Activities::self()->all();
|
|
|
|
const auto activityList = [&] {
|
|
|
|
auto result = rules()->checkActivity(newActivitiesList);
|
|
|
|
|
|
|
|
const auto it = std::remove_if(result.begin(), result.end(), [=](const QString &activity) {
|
|
|
|
return !allActivities.contains(activity);
|
|
|
|
});
|
|
|
|
result.erase(it, result.end());
|
|
|
|
return result;
|
|
|
|
}();
|
|
|
|
|
|
|
|
const auto allActivityExplicitlyRequested = activityList.isEmpty() || activityList.contains(Activities::nullUuid());
|
|
|
|
const auto allActivitiesCovered = activityList.size() > 1 && activityList.size() == allActivities.size();
|
|
|
|
|
|
|
|
if (allActivityExplicitlyRequested || allActivitiesCovered) {
|
|
|
|
if (!m_activityList.isEmpty()) {
|
|
|
|
m_activityList.clear();
|
|
|
|
doSetOnActivities(m_activityList);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (m_activityList != activityList) {
|
|
|
|
m_activityList = activityList;
|
|
|
|
doSetOnActivities(m_activityList);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
updateActivities(false);
|
|
|
|
#else
|
|
|
|
Q_UNUSED(newActivitiesList)
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* if @p all is true, sets on all activities.
|
|
|
|
* if it's false, sets it to only be on the current activity
|
|
|
|
*/
|
|
|
|
void AbstractClient::setOnAllActivities(bool all)
|
|
|
|
{
|
|
|
|
#ifdef KWIN_BUILD_ACTIVITIES
|
|
|
|
if (all == isOnAllActivities()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (all) {
|
|
|
|
setOnActivities(QStringList());
|
|
|
|
} else {
|
|
|
|
setOnActivity(Activities::self()->current(), true);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
Q_UNUSED(on)
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* update after activities changed
|
|
|
|
*/
|
|
|
|
void AbstractClient::updateActivities(bool includeTransients)
|
|
|
|
{
|
|
|
|
if (m_activityUpdatesBlocked) {
|
|
|
|
m_blockedActivityUpdatesRequireTransients |= includeTransients;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
emit activitiesChanged(this);
|
|
|
|
m_blockedActivityUpdatesRequireTransients = false; // reset
|
|
|
|
FocusChain::self()->update(this, FocusChain::MakeFirst);
|
|
|
|
updateWindowRules(Rules::Activity);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::blockActivityUpdates(bool b)
|
|
|
|
{
|
|
|
|
if (b) {
|
|
|
|
++m_activityUpdatesBlocked;
|
|
|
|
} else {
|
|
|
|
Q_ASSERT(m_activityUpdatesBlocked);
|
|
|
|
--m_activityUpdatesBlocked;
|
|
|
|
if (!m_activityUpdatesBlocked) {
|
|
|
|
updateActivities(m_blockedActivityUpdatesRequireTransients);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-01 19:48:57 +00:00
|
|
|
void AbstractClient::checkNoBorder()
|
|
|
|
{
|
|
|
|
setNoBorder(false);
|
|
|
|
}
|
|
|
|
|
2018-12-31 17:57:13 +00:00
|
|
|
bool AbstractClient::groupTransient() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const Group *AbstractClient::group() const
|
|
|
|
{
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
Group *AbstractClient::group()
|
|
|
|
{
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2019-05-10 07:47:05 +00:00
|
|
|
bool AbstractClient::isInternal() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
[wayland] Apply window rules only to xdg-shell clients
Summary:
There are rules that have to be applied only once, e.g. every Remember
and Apply Initially rule, as well rules that need to configure the client,
e.g. size, etc. In the best scenario the compositor would evaluate such
rules when the client is about to be mapped.
This change limits window rules only to xdg-shell clients because right
now only this protocol lets compositors to intervene in the client
initialization process. Also, it makes things a bit easier for us on the
compositor side.
xdg-shell protocol satisfies most of ours requirements to implement window
rules, but not all of them. If the client is about to be mapped for the
second time and its size is forced by a rule, then compositor may need
to configure it. Currently, xdg-shell protocol doesn't have any mechanism
that a client could use to notify the compositor about its intent to map.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: fmonteiro, davidedmundson, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D19411
2019-07-09 11:58:57 +00:00
|
|
|
bool AbstractClient::supportsWindowRules() const
|
|
|
|
{
|
2020-08-20 11:31:48 +00:00
|
|
|
return false;
|
[wayland] Apply window rules only to xdg-shell clients
Summary:
There are rules that have to be applied only once, e.g. every Remember
and Apply Initially rule, as well rules that need to configure the client,
e.g. size, etc. In the best scenario the compositor would evaluate such
rules when the client is about to be mapped.
This change limits window rules only to xdg-shell clients because right
now only this protocol lets compositors to intervene in the client
initialization process. Also, it makes things a bit easier for us on the
compositor side.
xdg-shell protocol satisfies most of ours requirements to implement window
rules, but not all of them. If the client is about to be mapped for the
second time and its size is forced by a rule, then compositor may need
to configure it. Currently, xdg-shell protocol doesn't have any mechanism
that a client could use to notify the compositor about its intent to map.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: fmonteiro, davidedmundson, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D19411
2019-07-09 11:58:57 +00:00
|
|
|
}
|
|
|
|
|
2019-08-26 07:44:04 +00:00
|
|
|
QMargins AbstractClient::frameMargins() const
|
|
|
|
{
|
|
|
|
return QMargins(borderLeft(), borderTop(), borderRight(), borderBottom());
|
|
|
|
}
|
|
|
|
|
[x11] Add support for _GTK_FRAME_EXTENTS
Summary:
KDE is known for having a strong view on the client-side decorations vs
server-side decorations issue. The main argument raised against CSD is
that desktop will look less consistent when clients start drawing window
decorations by themselves, which is somewhat true. It all ties to how
well each toolkit is integrated with the desktop environment.
KDE doesn't control the desktop market on Linux. Another big "player"
is GNOME. Both KDE and GNOME have very polarized views on in which
direction desktop should move forward. The KDE community is pushing more
toward server-side decorations while the GNOME community is pushing
more toward client-side decorations. Both communities have developed
great applications and it's not rare to see a GNOME application being
used in KDE Plasma. The only problem is that these different views are
not left behind the curtain and our users pay the price. Resizing GTK
clients in Plasma became practically impossible due to resize borders
having small hit area.
When a client draws its window decoration, it's more likely that it also
draws the drop-shadow around the decoration. The compositor must know
the extents of the shadow so things like snapping and so on work as
expected. And here lies the problem... While the xdg-shell protocol has
a way to specify such things, the NetWM spec doesn't have anything like
that. There's _GTK_FRAME_EXTENTS in the wild, however the problem with
it is that it's a proprietary atom, which is specific only to GTK apps.
Due to that, _GTK_FRAME_EXTENTS wasn't implemented because implementing
anything like that would require major changes in how we think about
geometry.
Recent xdg-shell window geometry patches adjusted geometry abstractions
in kwin to such a degree that it's very easy to add support for client
side decorated clients on X11. We just have to make sure that the
X11Client class provides correct buffer geometry and frame geometry when
the gtk frame extents are set.
Even though the X11 code is feature frozen, I still think it's worth
to have _GTK_FRAME_EXTENTS support in kwin because it will fix the resize
issues. Also, because KWin/Wayland is unfortunately far from becoming
default, it will help us with testing some implementation bits of the
window geometry from xdg-shell.
BUG: 390550
FIXED-IN: 5.18.0
Test Plan:
Things like quick tiling, maximizing, tiling scripts and so on work as
expected with GTK clients.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: cblack, trmdi, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D24660
2019-10-08 08:46:59 +00:00
|
|
|
QPoint AbstractClient::framePosToClientPos(const QPoint &point) const
|
|
|
|
{
|
|
|
|
return point + QPoint(borderLeft(), borderTop());
|
|
|
|
}
|
|
|
|
|
|
|
|
QPoint AbstractClient::clientPosToFramePos(const QPoint &point) const
|
|
|
|
{
|
|
|
|
return point - QPoint(borderLeft(), borderTop());
|
|
|
|
}
|
|
|
|
|
|
|
|
QSize AbstractClient::frameSizeToClientSize(const QSize &size) const
|
|
|
|
{
|
|
|
|
const int width = size.width() - borderLeft() - borderRight();
|
|
|
|
const int height = size.height() - borderTop() - borderBottom();
|
|
|
|
return QSize(width, height);
|
|
|
|
}
|
|
|
|
|
|
|
|
QSize AbstractClient::clientSizeToFrameSize(const QSize &size) const
|
|
|
|
{
|
|
|
|
const int width = size.width() + borderLeft() + borderRight();
|
|
|
|
const int height = size.height() + borderTop() + borderBottom();
|
|
|
|
return QSize(width, height);
|
|
|
|
}
|
|
|
|
|
|
|
|
QRect AbstractClient::frameRectToClientRect(const QRect &rect) const
|
|
|
|
{
|
|
|
|
const QPoint position = framePosToClientPos(rect.topLeft());
|
|
|
|
const QSize size = frameSizeToClientSize(rect.size());
|
|
|
|
return QRect(position, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
QRect AbstractClient::clientRectToFrameRect(const QRect &rect) const
|
|
|
|
{
|
|
|
|
const QPoint position = clientPosToFramePos(rect.topLeft());
|
|
|
|
const QSize size = clientSizeToFrameSize(rect.size());
|
|
|
|
return QRect(position, size);
|
|
|
|
}
|
|
|
|
|
2020-01-13 18:57:07 +00:00
|
|
|
void AbstractClient::setElectricBorderMode(QuickTileMode mode)
|
|
|
|
{
|
|
|
|
if (mode != QuickTileMode(QuickTileFlag::Maximize)) {
|
|
|
|
// sanitize the mode, ie. simplify "invalid" combinations
|
|
|
|
if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal))
|
|
|
|
mode &= ~QuickTileMode(QuickTileFlag::Horizontal);
|
|
|
|
if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical))
|
|
|
|
mode &= ~QuickTileMode(QuickTileFlag::Vertical);
|
|
|
|
}
|
|
|
|
m_electricMode = mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::setElectricBorderMaximizing(bool maximizing)
|
|
|
|
{
|
|
|
|
m_electricMaximizing = maximizing;
|
|
|
|
if (maximizing)
|
2020-04-02 16:18:01 +00:00
|
|
|
outline()->show(electricBorderMaximizeGeometry(Cursors::self()->mouse()->pos(), desktop()), moveResizeGeometry());
|
2020-01-13 18:57:07 +00:00
|
|
|
else
|
|
|
|
outline()->hide();
|
|
|
|
elevate(maximizing);
|
|
|
|
}
|
|
|
|
|
|
|
|
QRect AbstractClient::electricBorderMaximizeGeometry(QPoint pos, int desktop)
|
|
|
|
{
|
|
|
|
if (electricBorderMode() == QuickTileMode(QuickTileFlag::Maximize)) {
|
|
|
|
if (maximizeMode() == MaximizeFull)
|
|
|
|
return geometryRestore();
|
|
|
|
else
|
|
|
|
return workspace()->clientArea(MaximizeArea, pos, desktop);
|
|
|
|
}
|
|
|
|
|
|
|
|
QRect ret = workspace()->clientArea(MaximizeArea, pos, desktop);
|
|
|
|
if (electricBorderMode() & QuickTileFlag::Left)
|
|
|
|
ret.setRight(ret.left()+ret.width()/2 - 1);
|
|
|
|
else if (electricBorderMode() & QuickTileFlag::Right)
|
|
|
|
ret.setLeft(ret.right()-(ret.width()-ret.width()/2) + 1);
|
|
|
|
if (electricBorderMode() & QuickTileFlag::Top)
|
|
|
|
ret.setBottom(ret.top()+ret.height()/2 - 1);
|
|
|
|
else if (electricBorderMode() & QuickTileFlag::Bottom)
|
|
|
|
ret.setTop(ret.bottom()-(ret.height()-ret.height()/2) + 1);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::setQuickTileMode(QuickTileMode mode, bool keyboard)
|
|
|
|
{
|
|
|
|
// Only allow quick tile on a regular window.
|
|
|
|
if (!isResizable()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-04-02 16:18:01 +00:00
|
|
|
workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event
|
2020-01-13 18:57:07 +00:00
|
|
|
|
|
|
|
GeometryUpdatesBlocker blocker(this);
|
|
|
|
|
|
|
|
if (mode == QuickTileMode(QuickTileFlag::Maximize)) {
|
|
|
|
m_quickTileMode = int(QuickTileFlag::None);
|
|
|
|
if (maximizeMode() == MaximizeFull) {
|
|
|
|
setMaximize(false, false);
|
|
|
|
} else {
|
|
|
|
QRect prev_geom_restore = geometryRestore(); // setMaximize() would set moveResizeGeom as geom_restore
|
|
|
|
m_quickTileMode = int(QuickTileFlag::Maximize);
|
|
|
|
setMaximize(true, true);
|
|
|
|
QRect clientArea = workspace()->clientArea(MaximizeArea, this);
|
|
|
|
if (frameGeometry().top() != clientArea.top()) {
|
|
|
|
QRect r(frameGeometry());
|
|
|
|
r.moveTop(clientArea.top());
|
|
|
|
setFrameGeometry(r);
|
|
|
|
}
|
|
|
|
setGeometryRestore(prev_geom_restore);
|
|
|
|
}
|
2020-08-23 11:23:23 +00:00
|
|
|
doSetQuickTileMode();
|
2020-01-13 18:57:07 +00:00
|
|
|
emit quickTileModeChanged();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// sanitize the mode, ie. simplify "invalid" combinations
|
|
|
|
if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal))
|
|
|
|
mode &= ~QuickTileMode(QuickTileFlag::Horizontal);
|
|
|
|
if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical))
|
|
|
|
mode &= ~QuickTileMode(QuickTileFlag::Vertical);
|
|
|
|
|
|
|
|
setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.)
|
|
|
|
|
|
|
|
// restore from maximized so that it is possible to tile maximized windows with one hit or by dragging
|
|
|
|
if (maximizeMode() != MaximizeRestore) {
|
|
|
|
|
|
|
|
if (mode != QuickTileMode(QuickTileFlag::None)) {
|
|
|
|
// decorations may turn off some borders when tiled
|
|
|
|
const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet;
|
|
|
|
m_quickTileMode = int(QuickTileFlag::None); // Temporary, so the maximize code doesn't get all confused
|
|
|
|
|
|
|
|
setMaximize(false, false);
|
|
|
|
|
2020-04-02 16:18:01 +00:00
|
|
|
setFrameGeometry(electricBorderMaximizeGeometry(keyboard ? frameGeometry().center() : Cursors::self()->mouse()->pos(), desktop()), geom_mode);
|
2020-01-13 18:57:07 +00:00
|
|
|
// Store the mode change
|
|
|
|
m_quickTileMode = mode;
|
|
|
|
} else {
|
|
|
|
m_quickTileMode = mode;
|
|
|
|
setMaximize(false, false);
|
|
|
|
}
|
|
|
|
|
2020-08-23 11:23:23 +00:00
|
|
|
doSetQuickTileMode();
|
2020-01-13 18:57:07 +00:00
|
|
|
emit quickTileModeChanged();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode != QuickTileMode(QuickTileFlag::None)) {
|
2020-04-02 16:18:01 +00:00
|
|
|
QPoint whichScreen = keyboard ? frameGeometry().center() : Cursors::self()->mouse()->pos();
|
2020-01-13 18:57:07 +00:00
|
|
|
|
|
|
|
// If trying to tile to the side that the window is already tiled to move the window to the next
|
|
|
|
// screen if it exists, otherwise toggle the mode (set QuickTileFlag::None)
|
|
|
|
if (quickTileMode() == mode) {
|
|
|
|
const int numScreens = screens()->count();
|
|
|
|
const int curScreen = screen();
|
|
|
|
int nextScreen = curScreen;
|
|
|
|
QVarLengthArray<QRect> screens(numScreens);
|
|
|
|
for (int i = 0; i < numScreens; ++i) // Cache
|
|
|
|
screens[i] = Screens::self()->geometry(i);
|
|
|
|
for (int i = 0; i < numScreens; ++i) {
|
|
|
|
|
|
|
|
if (i == curScreen)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (screens[i].bottom() <= screens[curScreen].top() || screens[i].top() >= screens[curScreen].bottom())
|
|
|
|
continue; // not in horizontal line
|
|
|
|
|
|
|
|
const int x = screens[i].center().x();
|
|
|
|
if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Left)) {
|
|
|
|
if (x >= screens[curScreen].center().x() || (curScreen != nextScreen && x <= screens[nextScreen].center().x()))
|
|
|
|
continue; // not left of current or more left then found next
|
|
|
|
} else if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Right)) {
|
|
|
|
if (x <= screens[curScreen].center().x() || (curScreen != nextScreen && x >= screens[nextScreen].center().x()))
|
|
|
|
continue; // not right of current or more right then found next
|
|
|
|
}
|
|
|
|
|
|
|
|
nextScreen = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nextScreen == curScreen) {
|
|
|
|
mode = QuickTileFlag::None; // No other screens, toggle tiling
|
|
|
|
} else {
|
|
|
|
// Move to other screen
|
|
|
|
setFrameGeometry(geometryRestore().translated(screens[nextScreen].topLeft() - screens[curScreen].topLeft()));
|
|
|
|
whichScreen = screens[nextScreen].center();
|
|
|
|
|
|
|
|
// Swap sides
|
|
|
|
if (mode & QuickTileFlag::Horizontal) {
|
|
|
|
mode = (~mode & QuickTileFlag::Horizontal) | (mode & QuickTileFlag::Vertical);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.)
|
|
|
|
} else if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) {
|
|
|
|
// Not coming out of an existing tile, not shifting monitors, we're setting a brand new tile.
|
|
|
|
// Store geometry first, so we can go out of this tile later.
|
|
|
|
setGeometryRestore(frameGeometry());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode != QuickTileMode(QuickTileFlag::None)) {
|
|
|
|
m_quickTileMode = mode;
|
|
|
|
// decorations may turn off some borders when tiled
|
|
|
|
const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet;
|
|
|
|
// Temporary, so the maximize code doesn't get all confused
|
|
|
|
m_quickTileMode = int(QuickTileFlag::None);
|
|
|
|
setFrameGeometry(electricBorderMaximizeGeometry(whichScreen, desktop()), geom_mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store the mode change
|
|
|
|
m_quickTileMode = mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode == QuickTileMode(QuickTileFlag::None)) {
|
|
|
|
m_quickTileMode = int(QuickTileFlag::None);
|
|
|
|
// Untiling, so just restore geometry, and we're done.
|
|
|
|
if (!geometryRestore().isValid()) // invalid if we started maximized and wait for placement
|
|
|
|
setGeometryRestore(frameGeometry());
|
|
|
|
// decorations may turn off some borders when tiled
|
|
|
|
const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet;
|
|
|
|
setFrameGeometry(geometryRestore(), geom_mode);
|
|
|
|
checkWorkspacePosition(); // Just in case it's a different screen
|
|
|
|
}
|
2020-08-23 11:23:23 +00:00
|
|
|
doSetQuickTileMode();
|
2020-01-13 18:57:07 +00:00
|
|
|
emit quickTileModeChanged();
|
|
|
|
}
|
|
|
|
|
2020-08-23 11:23:23 +00:00
|
|
|
void AbstractClient::doSetQuickTileMode()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-01-13 18:57:07 +00:00
|
|
|
void AbstractClient::sendToScreen(int newScreen)
|
|
|
|
{
|
|
|
|
newScreen = rules()->checkScreen(newScreen);
|
|
|
|
if (isActive()) {
|
|
|
|
screens()->setCurrent(newScreen);
|
|
|
|
// might impact the layer of a fullscreen window
|
|
|
|
foreach (AbstractClient *cc, workspace()->allClientList()) {
|
|
|
|
if (cc->isFullScreen() && cc->screen() == newScreen) {
|
|
|
|
cc->updateLayer();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-20 19:36:07 +00:00
|
|
|
if (screen() == newScreen && !isFullScreen()) // Don't use isOnScreen(), that's true even when only partially
|
2020-01-13 18:57:07 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
GeometryUpdatesBlocker blocker(this);
|
|
|
|
|
|
|
|
// operating on the maximized / quicktiled window would leave the old geom_restore behind,
|
|
|
|
// so we clear the state first
|
|
|
|
MaximizeMode maxMode = maximizeMode();
|
|
|
|
QuickTileMode qtMode = quickTileMode();
|
|
|
|
if (maxMode != MaximizeRestore)
|
|
|
|
maximize(MaximizeRestore);
|
|
|
|
if (qtMode != QuickTileMode(QuickTileFlag::None))
|
|
|
|
setQuickTileMode(QuickTileFlag::None, true);
|
|
|
|
|
|
|
|
QRect oldScreenArea = workspace()->clientArea(MaximizeArea, this);
|
|
|
|
QRect screenArea = workspace()->clientArea(MaximizeArea, newScreen, desktop());
|
|
|
|
|
|
|
|
// the window can have its center so that the position correction moves the new center onto
|
|
|
|
// the old screen, what will tile it where it is. Ie. the screen is not changed
|
|
|
|
// this happens esp. with electric border quicktiling
|
|
|
|
if (qtMode != QuickTileMode(QuickTileFlag::None))
|
|
|
|
keepInArea(oldScreenArea);
|
|
|
|
|
|
|
|
QRect oldGeom = frameGeometry();
|
|
|
|
QRect newGeom = oldGeom;
|
|
|
|
// move the window to have the same relative position to the center of the screen
|
|
|
|
// (i.e. one near the middle of the right edge will also end up near the middle of the right edge)
|
|
|
|
QPoint center = newGeom.center() - oldScreenArea.center();
|
|
|
|
center.setX(center.x() * screenArea.width() / oldScreenArea.width());
|
|
|
|
center.setY(center.y() * screenArea.height() / oldScreenArea.height());
|
|
|
|
center += screenArea.center();
|
|
|
|
newGeom.moveCenter(center);
|
|
|
|
setFrameGeometry(newGeom);
|
|
|
|
|
|
|
|
// If the window was inside the old screen area, explicitly make sure its inside also the new screen area.
|
|
|
|
// Calling checkWorkspacePosition() should ensure that, but when moving to a small screen the window could
|
|
|
|
// be big enough to overlap outside of the new screen area, making struts from other screens come into effect,
|
|
|
|
// which could alter the resulting geometry.
|
|
|
|
if (oldScreenArea.contains(oldGeom)) {
|
|
|
|
keepInArea(screenArea);
|
|
|
|
}
|
|
|
|
|
2021-01-20 19:36:07 +00:00
|
|
|
if (isFullScreen()) {
|
2021-01-22 12:07:52 +00:00
|
|
|
updateGeometryRestoresForFullscreen();
|
2021-01-20 19:36:07 +00:00
|
|
|
checkWorkspacePosition(oldGeom);
|
|
|
|
} else {
|
|
|
|
// align geom_restore - checkWorkspacePosition operates on it
|
|
|
|
setGeometryRestore(frameGeometry());
|
|
|
|
|
|
|
|
checkWorkspacePosition(oldGeom);
|
2020-01-13 18:57:07 +00:00
|
|
|
|
2021-01-20 19:36:07 +00:00
|
|
|
// re-align geom_restore to constrained geometry
|
|
|
|
setGeometryRestore(frameGeometry());
|
|
|
|
}
|
2020-01-13 18:57:07 +00:00
|
|
|
// finally reset special states
|
|
|
|
// NOTICE that MaximizeRestore/QuickTileFlag::None checks are required.
|
|
|
|
// eg. setting QuickTileFlag::None would break maximization
|
|
|
|
if (maxMode != MaximizeRestore)
|
|
|
|
maximize(maxMode);
|
|
|
|
if (qtMode != QuickTileMode(QuickTileFlag::None) && qtMode != quickTileMode())
|
|
|
|
setQuickTileMode(qtMode, true);
|
|
|
|
|
|
|
|
auto tso = workspace()->ensureStackingOrder(transients());
|
|
|
|
for (auto it = tso.constBegin(), end = tso.constEnd(); it != end; ++it)
|
|
|
|
(*it)->sendToScreen(newScreen);
|
|
|
|
}
|
|
|
|
|
2021-01-22 12:07:52 +00:00
|
|
|
void AbstractClient::updateGeometryRestoresForFullscreen()
|
|
|
|
{
|
|
|
|
QRect screenArea = workspace()->clientArea(MaximizeArea, screen(), desktop());
|
|
|
|
QRect newFullScreenGeometryRestore = screenArea;
|
|
|
|
if (!(maximizeMode() & MaximizeVertical)) {
|
|
|
|
newFullScreenGeometryRestore.setHeight(geometryRestore().height());
|
|
|
|
}
|
|
|
|
if (!(maximizeMode() & MaximizeHorizontal)) {
|
|
|
|
newFullScreenGeometryRestore.setWidth(geometryRestore().width());
|
|
|
|
}
|
|
|
|
newFullScreenGeometryRestore.setSize(newFullScreenGeometryRestore.size().boundedTo(screenArea.size()));
|
|
|
|
QSize move = (screenArea.size() - newFullScreenGeometryRestore.size()) / 2;
|
|
|
|
newFullScreenGeometryRestore.translate(move.width(), move.height());
|
|
|
|
|
|
|
|
QRect newGeometryRestore = QRect(screenArea.topLeft(), geometryRestore().size().boundedTo(screenArea.size()));
|
|
|
|
move = (screenArea.size() - newGeometryRestore.size()) / 2;
|
|
|
|
newGeometryRestore.translate(move.width(), move.height());
|
|
|
|
|
|
|
|
setFullscreenGeometryRestore(newFullScreenGeometryRestore);
|
|
|
|
setGeometryRestore(newGeometryRestore);
|
|
|
|
}
|
|
|
|
|
2020-01-13 18:57:07 +00:00
|
|
|
void AbstractClient::checkWorkspacePosition(QRect oldGeometry, int oldDesktop, QRect oldClientGeometry)
|
|
|
|
{
|
2020-08-19 07:31:38 +00:00
|
|
|
if (isDock() || isDesktop() || !isPlaceable()) {
|
|
|
|
return;
|
|
|
|
}
|
2020-01-13 18:57:07 +00:00
|
|
|
enum { Left = 0, Top, Right, Bottom };
|
|
|
|
const int border[4] = { borderLeft(), borderTop(), borderRight(), borderBottom() };
|
|
|
|
if( !oldGeometry.isValid())
|
|
|
|
oldGeometry = frameGeometry();
|
|
|
|
if( oldDesktop == -2 )
|
|
|
|
oldDesktop = desktop();
|
|
|
|
if (!oldClientGeometry.isValid())
|
|
|
|
oldClientGeometry = oldGeometry.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]);
|
|
|
|
if (isFullScreen()) {
|
2021-01-20 19:36:07 +00:00
|
|
|
QRect area = workspace()->clientArea(FullScreenArea, fullscreenGeometryRestore().center(), desktop());
|
2020-01-13 18:57:07 +00:00
|
|
|
if (frameGeometry() != area)
|
|
|
|
setFrameGeometry(area);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (maximizeMode() != MaximizeRestore) {
|
2020-07-21 01:44:05 +00:00
|
|
|
GeometryUpdatesBlocker block(this);
|
2020-01-13 18:57:07 +00:00
|
|
|
changeMaximize(false, false, true); // adjust size
|
|
|
|
const QRect screenArea = workspace()->clientArea(ScreenArea, this);
|
|
|
|
QRect geom = frameGeometry();
|
|
|
|
checkOffscreenPosition(&geom, screenArea);
|
|
|
|
setFrameGeometry(geom);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) {
|
|
|
|
setFrameGeometry(electricBorderMaximizeGeometry(frameGeometry().center(), desktop()));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// this can be true only if this window was mapped before KWin
|
|
|
|
// was started - in such case, don't adjust position to workarea,
|
|
|
|
// because the window already had its position, and if a window
|
|
|
|
// with a strut altering the workarea would be managed in initialization
|
|
|
|
// after this one, this window would be moved
|
|
|
|
if (!workspace() || workspace()->initializing())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// If the window was touching an edge before but not now move it so it is again.
|
|
|
|
// Old and new maximums have different starting values so windows on the screen
|
|
|
|
// edge will move when a new strut is placed on the edge.
|
|
|
|
QRect oldScreenArea;
|
|
|
|
if( workspace()->inUpdateClientArea()) {
|
|
|
|
// we need to find the screen area as it was before the change
|
|
|
|
oldScreenArea = QRect( 0, 0, workspace()->oldDisplayWidth(), workspace()->oldDisplayHeight());
|
|
|
|
int distance = INT_MAX;
|
|
|
|
foreach(const QRect &r, workspace()->previousScreenSizes()) {
|
|
|
|
int d = r.contains( oldGeometry.center()) ? 0 : ( r.center() - oldGeometry.center()).manhattanLength();
|
|
|
|
if( d < distance ) {
|
|
|
|
distance = d;
|
|
|
|
oldScreenArea = r;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
oldScreenArea = workspace()->clientArea(ScreenArea, oldGeometry.center(), oldDesktop);
|
|
|
|
}
|
|
|
|
const QRect oldGeomTall = QRect(oldGeometry.x(), oldScreenArea.y(), oldGeometry.width(), oldScreenArea.height()); // Full screen height
|
|
|
|
const QRect oldGeomWide = QRect(oldScreenArea.x(), oldGeometry.y(), oldScreenArea.width(), oldGeometry.height()); // Full screen width
|
|
|
|
int oldTopMax = oldScreenArea.y();
|
|
|
|
int oldRightMax = oldScreenArea.x() + oldScreenArea.width();
|
|
|
|
int oldBottomMax = oldScreenArea.y() + oldScreenArea.height();
|
|
|
|
int oldLeftMax = oldScreenArea.x();
|
|
|
|
const QRect screenArea = workspace()->clientArea(ScreenArea, geometryRestore().center(), desktop());
|
|
|
|
int topMax = screenArea.y();
|
|
|
|
int rightMax = screenArea.x() + screenArea.width();
|
|
|
|
int bottomMax = screenArea.y() + screenArea.height();
|
|
|
|
int leftMax = screenArea.x();
|
|
|
|
QRect newGeom = geometryRestore(); // geometry();
|
|
|
|
QRect newClientGeom = newGeom.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]);
|
|
|
|
const QRect newGeomTall = QRect(newGeom.x(), screenArea.y(), newGeom.width(), screenArea.height()); // Full screen height
|
|
|
|
const QRect newGeomWide = QRect(screenArea.x(), newGeom.y(), screenArea.width(), newGeom.height()); // Full screen width
|
|
|
|
// Get the max strut point for each side where the window is (E.g. Highest point for
|
|
|
|
// the bottom struts bounded by the window's left and right sides).
|
|
|
|
|
|
|
|
// These 4 compute old bounds ...
|
|
|
|
auto moveAreaFunc = workspace()->inUpdateClientArea() ?
|
|
|
|
&Workspace::previousRestrictedMoveArea : //... the restricted areas changed
|
|
|
|
&Workspace::restrictedMoveArea; //... when e.g. active desktop or screen changes
|
|
|
|
|
|
|
|
for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaTop)) {
|
|
|
|
QRect rect = r & oldGeomTall;
|
|
|
|
if (!rect.isEmpty())
|
|
|
|
oldTopMax = qMax(oldTopMax, rect.y() + rect.height());
|
|
|
|
}
|
|
|
|
for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaRight)) {
|
|
|
|
QRect rect = r & oldGeomWide;
|
|
|
|
if (!rect.isEmpty())
|
|
|
|
oldRightMax = qMin(oldRightMax, rect.x());
|
|
|
|
}
|
|
|
|
for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaBottom)) {
|
|
|
|
QRect rect = r & oldGeomTall;
|
|
|
|
if (!rect.isEmpty())
|
|
|
|
oldBottomMax = qMin(oldBottomMax, rect.y());
|
|
|
|
}
|
|
|
|
for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaLeft)) {
|
|
|
|
QRect rect = r & oldGeomWide;
|
|
|
|
if (!rect.isEmpty())
|
|
|
|
oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width());
|
|
|
|
}
|
|
|
|
|
|
|
|
// These 4 compute new bounds
|
|
|
|
for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaTop)) {
|
|
|
|
QRect rect = r & newGeomTall;
|
|
|
|
if (!rect.isEmpty())
|
|
|
|
topMax = qMax(topMax, rect.y() + rect.height());
|
|
|
|
}
|
|
|
|
for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaRight)) {
|
|
|
|
QRect rect = r & newGeomWide;
|
|
|
|
if (!rect.isEmpty())
|
|
|
|
rightMax = qMin(rightMax, rect.x());
|
|
|
|
}
|
|
|
|
for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaBottom)) {
|
|
|
|
QRect rect = r & newGeomTall;
|
|
|
|
if (!rect.isEmpty())
|
|
|
|
bottomMax = qMin(bottomMax, rect.y());
|
|
|
|
}
|
|
|
|
for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaLeft)) {
|
|
|
|
QRect rect = r & newGeomWide;
|
|
|
|
if (!rect.isEmpty())
|
|
|
|
leftMax = qMax(leftMax, rect.x() + rect.width());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check if the sides were inside or touching but are no longer
|
|
|
|
bool keep[4] = {false, false, false, false};
|
|
|
|
bool save[4] = {false, false, false, false};
|
|
|
|
int padding[4] = {0, 0, 0, 0};
|
|
|
|
if (oldGeometry.x() >= oldLeftMax)
|
|
|
|
save[Left] = newGeom.x() < leftMax;
|
|
|
|
if (oldGeometry.x() == oldLeftMax)
|
|
|
|
keep[Left] = newGeom.x() != leftMax;
|
|
|
|
else if (oldClientGeometry.x() == oldLeftMax && newClientGeom.x() != leftMax) {
|
|
|
|
padding[0] = border[Left];
|
|
|
|
keep[Left] = true;
|
|
|
|
}
|
|
|
|
if (oldGeometry.y() >= oldTopMax)
|
|
|
|
save[Top] = newGeom.y() < topMax;
|
|
|
|
if (oldGeometry.y() == oldTopMax)
|
|
|
|
keep[Top] = newGeom.y() != topMax;
|
|
|
|
else if (oldClientGeometry.y() == oldTopMax && newClientGeom.y() != topMax) {
|
|
|
|
padding[1] = border[Left];
|
|
|
|
keep[Top] = true;
|
|
|
|
}
|
|
|
|
if (oldGeometry.right() <= oldRightMax - 1)
|
|
|
|
save[Right] = newGeom.right() > rightMax - 1;
|
|
|
|
if (oldGeometry.right() == oldRightMax - 1)
|
|
|
|
keep[Right] = newGeom.right() != rightMax - 1;
|
|
|
|
else if (oldClientGeometry.right() == oldRightMax - 1 && newClientGeom.right() != rightMax - 1) {
|
|
|
|
padding[2] = border[Right];
|
|
|
|
keep[Right] = true;
|
|
|
|
}
|
|
|
|
if (oldGeometry.bottom() <= oldBottomMax - 1)
|
|
|
|
save[Bottom] = newGeom.bottom() > bottomMax - 1;
|
|
|
|
if (oldGeometry.bottom() == oldBottomMax - 1)
|
|
|
|
keep[Bottom] = newGeom.bottom() != bottomMax - 1;
|
|
|
|
else if (oldClientGeometry.bottom() == oldBottomMax - 1 && newClientGeom.bottom() != bottomMax - 1) {
|
|
|
|
padding[3] = border[Bottom];
|
|
|
|
keep[Bottom] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if randomly touches opposing edges, do not favor either
|
|
|
|
if (keep[Left] && keep[Right]) {
|
|
|
|
keep[Left] = keep[Right] = false;
|
|
|
|
padding[0] = padding[2] = 0;
|
|
|
|
}
|
|
|
|
if (keep[Top] && keep[Bottom]) {
|
|
|
|
keep[Top] = keep[Bottom] = false;
|
|
|
|
padding[1] = padding[3] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (save[Left] || keep[Left])
|
|
|
|
newGeom.moveLeft(qMax(leftMax, screenArea.x()) - padding[0]);
|
|
|
|
if (padding[0] && screens()->intersecting(newGeom) > 1)
|
|
|
|
newGeom.moveLeft(newGeom.left() + padding[0]);
|
|
|
|
if (save[Top] || keep[Top])
|
|
|
|
newGeom.moveTop(qMax(topMax, screenArea.y()) - padding[1]);
|
|
|
|
if (padding[1] && screens()->intersecting(newGeom) > 1)
|
|
|
|
newGeom.moveTop(newGeom.top() + padding[1]);
|
|
|
|
if (save[Right] || keep[Right])
|
|
|
|
newGeom.moveRight(qMin(rightMax - 1, screenArea.right()) + padding[2]);
|
|
|
|
if (padding[2] && screens()->intersecting(newGeom) > 1)
|
|
|
|
newGeom.moveRight(newGeom.right() - padding[2]);
|
|
|
|
if (oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax)
|
|
|
|
newGeom.setLeft(qMax(leftMax, screenArea.x()));
|
|
|
|
else if (oldClientGeometry.x() >= oldLeftMax && newGeom.x() + border[Left] < leftMax) {
|
|
|
|
newGeom.setLeft(qMax(leftMax, screenArea.x()) - border[Left]);
|
|
|
|
if (screens()->intersecting(newGeom) > 1)
|
|
|
|
newGeom.setLeft(newGeom.left() + border[Left]);
|
|
|
|
}
|
|
|
|
if (save[Bottom] || keep[Bottom])
|
|
|
|
newGeom.moveBottom(qMin(bottomMax - 1, screenArea.bottom()) + padding[3]);
|
|
|
|
if (padding[3] && screens()->intersecting(newGeom) > 1)
|
|
|
|
newGeom.moveBottom(newGeom.bottom() - padding[3]);
|
|
|
|
if (oldGeometry.y() >= oldTopMax && newGeom.y() < topMax)
|
|
|
|
newGeom.setTop(qMax(topMax, screenArea.y()));
|
|
|
|
else if (oldClientGeometry.y() >= oldTopMax && newGeom.y() + border[Top] < topMax) {
|
|
|
|
newGeom.setTop(qMax(topMax, screenArea.y()) - border[Top]);
|
|
|
|
if (screens()->intersecting(newGeom) > 1)
|
|
|
|
newGeom.setTop(newGeom.top() + border[Top]);
|
|
|
|
}
|
|
|
|
|
|
|
|
checkOffscreenPosition(&newGeom, screenArea);
|
|
|
|
// Obey size hints. TODO: We really should make sure it stays in the right place
|
|
|
|
if (!isShade())
|
Refactor geometry constraints code
Summary:
Currently, there are a couple of issues with sizeForClientSize(). First
of all, we have a method called clientSizeToFrameSize() which does similar
thing except applying geometry constraints and checking window rules. The
other issue is that sizeForClientSize() is doing a bit too much, it checks
window rules, it applies a bunch of geometry constrains. Sometimes it
does not perform conversion between client sizes and frame sizes!
This change attempts to address those issues by replacing sizeForClientSize
with two similar methods and changing semantics of some methods of the
X11Client class.
The most significant difference between sizeForClientSize() and the new
methods is that neither constrainClientSize() nor constrainFrameSize()
check window rules. This is up to users of those methods. In many places,
we don't have to check window rules because we check isResizable(),
which returns false if the frame size is enforced by a window rule.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, romangg, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D26828
2020-02-12 10:38:40 +00:00
|
|
|
newGeom.setSize(constrainFrameSize(newGeom.size()));
|
2020-01-13 18:57:07 +00:00
|
|
|
|
|
|
|
if (newGeom != frameGeometry())
|
|
|
|
setFrameGeometry(newGeom);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::checkOffscreenPosition(QRect* geom, const QRect& screenArea)
|
|
|
|
{
|
|
|
|
if (geom->left() > screenArea.right()) {
|
|
|
|
geom->moveLeft(screenArea.right() - screenArea.width()/4);
|
|
|
|
} else if (geom->right() < screenArea.left()) {
|
|
|
|
geom->moveRight(screenArea.left() + screenArea.width()/4);
|
|
|
|
}
|
|
|
|
if (geom->top() > screenArea.bottom()) {
|
|
|
|
geom->moveTop(screenArea.bottom() - screenArea.height()/4);
|
|
|
|
} else if (geom->bottom() < screenArea.top()) {
|
|
|
|
geom->moveBottom(screenArea.top() + screenArea.width()/4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Refactor geometry constraints code
Summary:
Currently, there are a couple of issues with sizeForClientSize(). First
of all, we have a method called clientSizeToFrameSize() which does similar
thing except applying geometry constraints and checking window rules. The
other issue is that sizeForClientSize() is doing a bit too much, it checks
window rules, it applies a bunch of geometry constrains. Sometimes it
does not perform conversion between client sizes and frame sizes!
This change attempts to address those issues by replacing sizeForClientSize
with two similar methods and changing semantics of some methods of the
X11Client class.
The most significant difference between sizeForClientSize() and the new
methods is that neither constrainClientSize() nor constrainFrameSize()
check window rules. This is up to users of those methods. In many places,
we don't have to check window rules because we check isResizable(),
which returns false if the frame size is enforced by a window rule.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, romangg, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D26828
2020-02-12 10:38:40 +00:00
|
|
|
/**
|
|
|
|
* Returns the appropriate frame size for the current client size.
|
|
|
|
*
|
|
|
|
* This is equivalent to clientSizeToFrameSize(constrainClientSize(clientSize())).
|
|
|
|
*/
|
|
|
|
QSize AbstractClient::adjustedSize() const
|
|
|
|
{
|
|
|
|
return clientSizeToFrameSize(constrainClientSize(clientSize()));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constrains the client size @p size according to a set of the window's size hints.
|
2020-02-12 10:43:30 +00:00
|
|
|
*
|
|
|
|
* Default implementation applies only minimum and maximum size constraints.
|
Refactor geometry constraints code
Summary:
Currently, there are a couple of issues with sizeForClientSize(). First
of all, we have a method called clientSizeToFrameSize() which does similar
thing except applying geometry constraints and checking window rules. The
other issue is that sizeForClientSize() is doing a bit too much, it checks
window rules, it applies a bunch of geometry constrains. Sometimes it
does not perform conversion between client sizes and frame sizes!
This change attempts to address those issues by replacing sizeForClientSize
with two similar methods and changing semantics of some methods of the
X11Client class.
The most significant difference between sizeForClientSize() and the new
methods is that neither constrainClientSize() nor constrainFrameSize()
check window rules. This is up to users of those methods. In many places,
we don't have to check window rules because we check isResizable(),
which returns false if the frame size is enforced by a window rule.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, romangg, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D26828
2020-02-12 10:38:40 +00:00
|
|
|
*/
|
|
|
|
QSize AbstractClient::constrainClientSize(const QSize &size, SizeMode mode) const
|
2020-01-13 18:57:07 +00:00
|
|
|
{
|
Refactor geometry constraints code
Summary:
Currently, there are a couple of issues with sizeForClientSize(). First
of all, we have a method called clientSizeToFrameSize() which does similar
thing except applying geometry constraints and checking window rules. The
other issue is that sizeForClientSize() is doing a bit too much, it checks
window rules, it applies a bunch of geometry constrains. Sometimes it
does not perform conversion between client sizes and frame sizes!
This change attempts to address those issues by replacing sizeForClientSize
with two similar methods and changing semantics of some methods of the
X11Client class.
The most significant difference between sizeForClientSize() and the new
methods is that neither constrainClientSize() nor constrainFrameSize()
check window rules. This is up to users of those methods. In many places,
we don't have to check window rules because we check isResizable(),
which returns false if the frame size is enforced by a window rule.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, romangg, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D26828
2020-02-12 10:38:40 +00:00
|
|
|
Q_UNUSED(mode)
|
2020-01-13 18:57:07 +00:00
|
|
|
|
Refactor geometry constraints code
Summary:
Currently, there are a couple of issues with sizeForClientSize(). First
of all, we have a method called clientSizeToFrameSize() which does similar
thing except applying geometry constraints and checking window rules. The
other issue is that sizeForClientSize() is doing a bit too much, it checks
window rules, it applies a bunch of geometry constrains. Sometimes it
does not perform conversion between client sizes and frame sizes!
This change attempts to address those issues by replacing sizeForClientSize
with two similar methods and changing semantics of some methods of the
X11Client class.
The most significant difference between sizeForClientSize() and the new
methods is that neither constrainClientSize() nor constrainFrameSize()
check window rules. This is up to users of those methods. In many places,
we don't have to check window rules because we check isResizable(),
which returns false if the frame size is enforced by a window rule.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, romangg, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D26828
2020-02-12 10:38:40 +00:00
|
|
|
int width = size.width();
|
|
|
|
int height = size.height();
|
|
|
|
|
|
|
|
// When user is resizing the window, the move resize geometry may have negative width or
|
|
|
|
// height. In which case, we need to set negative dimensions to reasonable values.
|
|
|
|
if (width < 1) {
|
|
|
|
width = 1;
|
|
|
|
}
|
|
|
|
if (height < 1) {
|
|
|
|
height = 1;
|
|
|
|
}
|
|
|
|
|
2020-02-12 10:43:30 +00:00
|
|
|
const QSize minimumSize = minSize();
|
|
|
|
const QSize maximumSize = maxSize();
|
|
|
|
|
|
|
|
width = qBound(minimumSize.width(), width, maximumSize.width());
|
|
|
|
height = qBound(minimumSize.height(), height, maximumSize.height());
|
|
|
|
|
Refactor geometry constraints code
Summary:
Currently, there are a couple of issues with sizeForClientSize(). First
of all, we have a method called clientSizeToFrameSize() which does similar
thing except applying geometry constraints and checking window rules. The
other issue is that sizeForClientSize() is doing a bit too much, it checks
window rules, it applies a bunch of geometry constrains. Sometimes it
does not perform conversion between client sizes and frame sizes!
This change attempts to address those issues by replacing sizeForClientSize
with two similar methods and changing semantics of some methods of the
X11Client class.
The most significant difference between sizeForClientSize() and the new
methods is that neither constrainClientSize() nor constrainFrameSize()
check window rules. This is up to users of those methods. In many places,
we don't have to check window rules because we check isResizable(),
which returns false if the frame size is enforced by a window rule.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, romangg, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D26828
2020-02-12 10:38:40 +00:00
|
|
|
return QSize(width, height);
|
2020-01-13 18:57:07 +00:00
|
|
|
}
|
|
|
|
|
Refactor geometry constraints code
Summary:
Currently, there are a couple of issues with sizeForClientSize(). First
of all, we have a method called clientSizeToFrameSize() which does similar
thing except applying geometry constraints and checking window rules. The
other issue is that sizeForClientSize() is doing a bit too much, it checks
window rules, it applies a bunch of geometry constrains. Sometimes it
does not perform conversion between client sizes and frame sizes!
This change attempts to address those issues by replacing sizeForClientSize
with two similar methods and changing semantics of some methods of the
X11Client class.
The most significant difference between sizeForClientSize() and the new
methods is that neither constrainClientSize() nor constrainFrameSize()
check window rules. This is up to users of those methods. In many places,
we don't have to check window rules because we check isResizable(),
which returns false if the frame size is enforced by a window rule.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, romangg, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D26828
2020-02-12 10:38:40 +00:00
|
|
|
/**
|
|
|
|
* Constrains the frame size @p size according to a set of the window's size hints.
|
|
|
|
*/
|
|
|
|
QSize AbstractClient::constrainFrameSize(const QSize &size, SizeMode mode) const
|
2020-01-13 18:57:07 +00:00
|
|
|
{
|
Refactor geometry constraints code
Summary:
Currently, there are a couple of issues with sizeForClientSize(). First
of all, we have a method called clientSizeToFrameSize() which does similar
thing except applying geometry constraints and checking window rules. The
other issue is that sizeForClientSize() is doing a bit too much, it checks
window rules, it applies a bunch of geometry constrains. Sometimes it
does not perform conversion between client sizes and frame sizes!
This change attempts to address those issues by replacing sizeForClientSize
with two similar methods and changing semantics of some methods of the
X11Client class.
The most significant difference between sizeForClientSize() and the new
methods is that neither constrainClientSize() nor constrainFrameSize()
check window rules. This is up to users of those methods. In many places,
we don't have to check window rules because we check isResizable(),
which returns false if the frame size is enforced by a window rule.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, romangg, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D26828
2020-02-12 10:38:40 +00:00
|
|
|
const QSize unconstrainedClientSize = frameSizeToClientSize(size);
|
|
|
|
const QSize constrainedClientSize = constrainClientSize(unconstrainedClientSize, mode);
|
|
|
|
return clientSizeToFrameSize(constrainedClientSize);
|
2020-01-13 18:57:07 +00:00
|
|
|
}
|
|
|
|
|
2020-02-04 18:12:07 +00:00
|
|
|
/**
|
|
|
|
* Returns @c true if the AbstractClient can be shown in full screen mode; otherwise @c false.
|
|
|
|
*
|
|
|
|
* Default implementation returns @c false.
|
|
|
|
*/
|
|
|
|
bool AbstractClient::isFullScreenable() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns @c true if the AbstractClient is currently being shown in full screen mode; otherwise @c false.
|
|
|
|
*
|
|
|
|
* A client in full screen mode occupies the entire screen with no window frame around it.
|
|
|
|
*
|
|
|
|
* Default implementation returns @c false.
|
|
|
|
*/
|
|
|
|
bool AbstractClient::isFullScreen() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns whether requests initiated by the user to enter or leave full screen mode are honored.
|
|
|
|
*
|
|
|
|
* Default implementation returns @c false.
|
|
|
|
*/
|
|
|
|
bool AbstractClient::userCanSetFullScreen() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Asks the AbstractClient to enter or leave full screen mode.
|
|
|
|
*
|
|
|
|
* Default implementation does nothing.
|
|
|
|
*
|
|
|
|
* @param set @c true if the AbstractClient has to be shown in full screen mode, otherwise @c false
|
|
|
|
* @param user @c true if the request is initiated by the user, otherwise @c false
|
|
|
|
*/
|
|
|
|
void AbstractClient::setFullScreen(bool set, bool user)
|
|
|
|
{
|
|
|
|
Q_UNUSED(set)
|
|
|
|
Q_UNUSED(user)
|
2020-08-17 07:28:35 +00:00
|
|
|
qCWarning(KWIN_CORE, "%s doesn't support setting fullscreen state", metaObject()->className());
|
2020-02-04 18:12:07 +00:00
|
|
|
}
|
|
|
|
|
2020-02-04 18:35:50 +00:00
|
|
|
/**
|
|
|
|
* Returns @c true if the AbstractClient can be minimized; otherwise @c false.
|
|
|
|
*
|
|
|
|
* Default implementation returns @c false.
|
|
|
|
*/
|
|
|
|
bool AbstractClient::isMinimizable() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-02-04 19:14:13 +00:00
|
|
|
/**
|
|
|
|
* Returns @c true if the AbstractClient can be maximized; otherwise @c false.
|
|
|
|
*
|
|
|
|
* Default implementation returns @c false.
|
|
|
|
*/
|
|
|
|
bool AbstractClient::isMaximizable() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the currently applied maximize mode.
|
|
|
|
*
|
|
|
|
* Default implementation returns MaximizeRestore.
|
|
|
|
*/
|
|
|
|
MaximizeMode AbstractClient::maximizeMode() const
|
|
|
|
{
|
|
|
|
return MaximizeRestore;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the last requested maximize mode.
|
|
|
|
*
|
|
|
|
* On X11, this method always matches maximizeMode(). On Wayland, it is asynchronous.
|
|
|
|
*
|
|
|
|
* Default implementation matches maximizeMode().
|
|
|
|
*/
|
|
|
|
MaximizeMode AbstractClient::requestedMaximizeMode() const
|
|
|
|
{
|
|
|
|
return maximizeMode();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the geometry of the AbstractClient before it was maximized or quick tiled.
|
|
|
|
*/
|
|
|
|
QRect AbstractClient::geometryRestore() const
|
|
|
|
{
|
|
|
|
return m_maximizeGeometryRestore;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the geometry of the AbstractClient before it was maximized or quick tiled to @p rect.
|
|
|
|
*/
|
|
|
|
void AbstractClient::setGeometryRestore(const QRect &rect)
|
|
|
|
{
|
|
|
|
m_maximizeGeometryRestore = rect;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Toggles the maximized state along specified dimensions @p horizontal and @p vertical.
|
|
|
|
*
|
|
|
|
* If @p adjust is @c true, only frame geometry will be updated to match requestedMaximizeMode().
|
|
|
|
*
|
|
|
|
* Default implementation does nothing.
|
|
|
|
*/
|
|
|
|
void AbstractClient::changeMaximize(bool horizontal, bool vertical, bool adjust)
|
|
|
|
{
|
|
|
|
Q_UNUSED(horizontal)
|
|
|
|
Q_UNUSED(vertical)
|
|
|
|
Q_UNUSED(adjust)
|
2020-08-17 07:28:35 +00:00
|
|
|
qCWarning(KWIN_CORE, "%s doesn't support setting maximized state", metaObject()->className());
|
2020-02-04 19:14:13 +00:00
|
|
|
}
|
|
|
|
|
2020-08-17 07:51:55 +00:00
|
|
|
void AbstractClient::updateDecoration(bool check_workspace_pos, bool force)
|
|
|
|
{
|
|
|
|
Q_UNUSED(check_workspace_pos)
|
|
|
|
Q_UNUSED(force)
|
|
|
|
qCWarning(KWIN_CORE, "%s doesn't support server side decorations", metaObject()->className());
|
|
|
|
}
|
|
|
|
|
2020-08-17 08:08:16 +00:00
|
|
|
bool AbstractClient::noBorder() const
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AbstractClient::userCanSetNoBorder() const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AbstractClient::setNoBorder(bool set)
|
|
|
|
{
|
|
|
|
Q_UNUSED(set)
|
|
|
|
qCWarning(KWIN_CORE, "%s doesn't support setting decorations", metaObject()->className());
|
|
|
|
}
|
|
|
|
|
2020-08-17 10:22:10 +00:00
|
|
|
void AbstractClient::showOnScreenEdge()
|
|
|
|
{
|
|
|
|
qCWarning(KWIN_CORE, "%s doesn't support screen edge activation", metaObject()->className());
|
|
|
|
}
|
|
|
|
|
2020-08-19 07:31:38 +00:00
|
|
|
bool AbstractClient::isPlaceable() const
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-01-20 19:36:07 +00:00
|
|
|
QRect AbstractClient::fullscreenGeometryRestore() const
|
|
|
|
{
|
|
|
|
return m_fullscreenGeometryRestore;
|
|
|
|
}
|
2021-03-24 17:44:52 +00:00
|
|
|
|
2021-01-20 19:36:07 +00:00
|
|
|
void AbstractClient::setFullscreenGeometryRestore(const QRect &geom)
|
|
|
|
{
|
|
|
|
m_fullscreenGeometryRestore = geom;
|
|
|
|
}
|
|
|
|
|
2021-03-24 17:44:52 +00:00
|
|
|
void AbstractClient::cleanTabBox()
|
|
|
|
{
|
|
|
|
#ifdef KWIN_BUILD_TABBOX
|
|
|
|
TabBox::TabBox *tabBox = TabBox::TabBox::self();
|
|
|
|
if (tabBox && tabBox->isDisplayed() && tabBox->currentClient() == this) {
|
|
|
|
tabBox->nextPrev(true);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2015-03-05 09:21:03 +00:00
|
|
|
}
|