f57470d1fd
Summary: Capitalize "mode" to improve readability. Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D26826
3145 lines
110 KiB
C++
3145 lines
110 KiB
C++
/********************************************************************
|
|
KWin - the KDE window manager
|
|
This file is part of the KDE project.
|
|
|
|
Copyright (C) 2015 Martin Gräßlin <mgraesslin@kde.org>
|
|
Copyright (C) 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*********************************************************************/
|
|
#include "abstract_client.h"
|
|
|
|
#include "appmenu.h"
|
|
#include "decorations/decoratedclient.h"
|
|
#include "decorations/decorationpalette.h"
|
|
#include "decorations/decorationbridge.h"
|
|
#include "cursor.h"
|
|
#include "effects.h"
|
|
#include "focuschain.h"
|
|
#include "outline.h"
|
|
#include "screens.h"
|
|
#ifdef KWIN_BUILD_TABBOX
|
|
#include "tabbox.h"
|
|
#endif
|
|
#include "screenedge.h"
|
|
#include "useractions.h"
|
|
#include "workspace.h"
|
|
|
|
#include "wayland_server.h"
|
|
#include <KWayland/Server/plasmawindowmanagement_interface.h>
|
|
|
|
#include <KDecoration2/Decoration>
|
|
|
|
#include <KDesktopFile>
|
|
|
|
#include <QMouseEvent>
|
|
#include <QStyleHints>
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
static inline int sign(int v)
|
|
{
|
|
return (v > 0) - (v < 0);
|
|
}
|
|
|
|
QHash<QString, std::weak_ptr<Decoration::DecorationPalette>> AbstractClient::s_palettes;
|
|
std::shared_ptr<Decoration::DecorationPalette> AbstractClient::s_defaultPalette;
|
|
|
|
AbstractClient::AbstractClient()
|
|
: Toplevel()
|
|
#ifdef KWIN_BUILD_TABBOX
|
|
, m_tabBoxClient(QSharedPointer<TabBox::TabBoxClientImpl>(new TabBox::TabBoxClientImpl(this)))
|
|
#endif
|
|
, m_colorScheme(QStringLiteral("kdeglobals"))
|
|
{
|
|
connect(this, &AbstractClient::geometryShapeChanged, this, &AbstractClient::geometryChanged);
|
|
auto signalMaximizeChanged = static_cast<void (AbstractClient::*)(KWin::AbstractClient*, MaximizeMode)>(&AbstractClient::clientMaximizedStateChanged);
|
|
connect(this, signalMaximizeChanged, this, &AbstractClient::geometryChanged);
|
|
connect(this, &AbstractClient::clientStepUserMovedResized, this, &AbstractClient::geometryChanged);
|
|
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);
|
|
|
|
connect(this, &AbstractClient::paletteChanged, this, &AbstractClient::triggerDecorationRepaint);
|
|
|
|
connect(Decoration::DecorationBridge::self(), &QObject::destroyed, this, &AbstractClient::destroyDecoration);
|
|
|
|
// 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();
|
|
});
|
|
|
|
// replace on-screen-display on size changes
|
|
connect(this, &AbstractClient::geometryShapeChanged, this,
|
|
[this] (Toplevel *c, const QRect &old) {
|
|
Q_UNUSED(c)
|
|
if (isOnScreenDisplay() && !frameGeometry().isEmpty() && old.size() != frameGeometry().size() && !isInitialPositionSet()) {
|
|
GeometryUpdatesBlocker blocker(this);
|
|
const QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop());
|
|
Placement::self()->place(this, area);
|
|
setGeometryRestore(frameGeometry());
|
|
}
|
|
}
|
|
);
|
|
|
|
connect(this, &AbstractClient::paddingChanged, this, [this]() {
|
|
m_visibleRectBeforeGeometryUpdate = visibleRect();
|
|
});
|
|
|
|
connect(ApplicationMenu::self(), &ApplicationMenu::applicationMenuEnabledChanged, this, [this] {
|
|
emit hasApplicationMenuChanged(hasApplicationMenu());
|
|
});
|
|
}
|
|
|
|
AbstractClient::~AbstractClient()
|
|
{
|
|
Q_ASSERT(m_blockGeometryUpdates == 0);
|
|
Q_ASSERT(m_decoration.decoration == nullptr);
|
|
}
|
|
|
|
void AbstractClient::updateMouseGrab()
|
|
{
|
|
}
|
|
|
|
bool AbstractClient::belongToSameApplication(const AbstractClient *c1, const AbstractClient *c2, SameApplicationChecks checks)
|
|
{
|
|
return c1->belongsToSameApplication(c2, checks);
|
|
}
|
|
|
|
bool AbstractClient::isTransient() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void AbstractClient::setClientShown(bool shown)
|
|
{
|
|
Q_UNUSED(shown)
|
|
}
|
|
|
|
MaximizeMode AbstractClient::requestedMaximizeMode() const
|
|
{
|
|
return maximizeMode();
|
|
}
|
|
|
|
xcb_timestamp_t AbstractClient::userTime() const
|
|
{
|
|
return XCB_TIME_CURRENT_TIME;
|
|
}
|
|
|
|
void AbstractClient::setSkipSwitcher(bool set)
|
|
{
|
|
set = rules()->checkSkipSwitcher(set);
|
|
if (set == skipSwitcher())
|
|
return;
|
|
m_skipSwitcher = set;
|
|
doSetSkipSwitcher();
|
|
updateWindowRules(Rules::SkipSwitcher);
|
|
emit skipSwitcherChanged();
|
|
}
|
|
|
|
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()
|
|
{
|
|
}
|
|
|
|
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()
|
|
{
|
|
|
|
}
|
|
|
|
void AbstractClient::doSetSkipSwitcher()
|
|
{
|
|
|
|
}
|
|
|
|
void AbstractClient::setIcon(const QIcon &icon)
|
|
{
|
|
m_icon = icon;
|
|
emit iconChanged();
|
|
}
|
|
|
|
void AbstractClient::setActive(bool act)
|
|
{
|
|
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);
|
|
workspace()->setActiveClient(act ? this : nullptr);
|
|
|
|
if (!m_active)
|
|
cancelAutoRaise();
|
|
|
|
if (!m_active && shadeMode() == ShadeActivated)
|
|
setShade(ShadeNormal);
|
|
|
|
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);
|
|
|
|
doSetActive();
|
|
emit activeChanged();
|
|
updateMouseGrab();
|
|
}
|
|
|
|
void AbstractClient::doSetActive()
|
|
{
|
|
}
|
|
|
|
Layer AbstractClient::layer() const
|
|
{
|
|
if (m_layer == UnknownLayer)
|
|
const_cast< AbstractClient* >(this)->m_layer = belongsToLayer();
|
|
return m_layer;
|
|
}
|
|
|
|
void AbstractClient::updateLayer()
|
|
{
|
|
if (layer() == belongsToLayer())
|
|
return;
|
|
StackingUpdatesBlocker blocker(workspace());
|
|
invalidateLayer(); // invalidate, will be updated when doing restacking
|
|
for (auto it = transients().constBegin(),
|
|
end = transients().constEnd(); it != end; ++it)
|
|
(*it)->updateLayer();
|
|
}
|
|
|
|
void AbstractClient::invalidateLayer()
|
|
{
|
|
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
|
|
if (isInternal())
|
|
return UnmanagedLayer;
|
|
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;
|
|
if (isCriticalNotification())
|
|
return CriticalNotificationLayer;
|
|
if (workspace()->showingDesktop() && belongsToDesktop()) {
|
|
return AboveLayer;
|
|
}
|
|
if (keepBelow())
|
|
return BelowLayer;
|
|
if (isActiveFullScreen())
|
|
return ActiveLayer;
|
|
if (keepAbove())
|
|
return AboveLayer;
|
|
|
|
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;
|
|
}
|
|
|
|
void AbstractClient::setKeepAbove(bool b)
|
|
{
|
|
b = rules()->checkKeepAbove(b);
|
|
if (b && !rules()->checkKeepBelow(false))
|
|
setKeepBelow(false);
|
|
if (b == keepAbove()) {
|
|
// force hint change if different
|
|
if (info && bool(info->state() & NET::KeepAbove) != keepAbove())
|
|
info->setState(keepAbove() ? NET::KeepAbove : NET::States(), NET::KeepAbove);
|
|
return;
|
|
}
|
|
m_keepAbove = b;
|
|
if (info) {
|
|
info->setState(keepAbove() ? NET::KeepAbove : NET::States(), NET::KeepAbove);
|
|
}
|
|
workspace()->updateClientLayer(this);
|
|
updateWindowRules(Rules::Above);
|
|
|
|
doSetKeepAbove();
|
|
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()) {
|
|
// force hint change if different
|
|
if (info && bool(info->state() & NET::KeepBelow) != keepBelow())
|
|
info->setState(keepBelow() ? NET::KeepBelow : NET::States(), NET::KeepBelow);
|
|
return;
|
|
}
|
|
m_keepBelow = b;
|
|
if (info) {
|
|
info->setState(keepBelow() ? NET::KeepBelow : NET::States(), NET::KeepBelow);
|
|
}
|
|
workspace()->updateClientLayer(this);
|
|
updateWindowRules(Rules::Below);
|
|
|
|
doSetKeepBelow();
|
|
emit keepBelowChanged(m_keepBelow);
|
|
}
|
|
|
|
void AbstractClient::doSetKeepBelow()
|
|
{
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
bool AbstractClient::wantsTabFocus() const
|
|
{
|
|
return (isNormalWindow() || isDialog()) && wantsInput();
|
|
}
|
|
|
|
bool AbstractClient::isSpecialWindow() const
|
|
{
|
|
// TODO
|
|
return isDesktop() || isDock() || isSplash() || isToolbar() || isNotification() || isOnScreenDisplay() || isCriticalNotification();
|
|
}
|
|
|
|
void AbstractClient::demandAttention(bool set)
|
|
{
|
|
if (isActive())
|
|
set = false;
|
|
if (m_demandsAttention == set)
|
|
return;
|
|
m_demandsAttention = set;
|
|
if (info) {
|
|
info->setState(set ? NET::DemandsAttention : NET::States(), NET::DemandsAttention);
|
|
}
|
|
workspace()->clientAttentionChanged(this, set);
|
|
emit demandsAttentionChanged();
|
|
}
|
|
|
|
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));
|
|
|
|
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) {
|
|
desktops = QVector<VirtualDesktop*>({desktops.last()});
|
|
}
|
|
|
|
if (desktops == m_desktops) {
|
|
return;
|
|
}
|
|
|
|
int was_desk = AbstractClient::desktop();
|
|
const bool wasOnCurrentDesktop = isOnCurrentDesktop() && was_desk >= 0;
|
|
|
|
m_desktops = desktops;
|
|
|
|
if (windowManagementInterface()) {
|
|
if (m_desktops.isEmpty()) {
|
|
windowManagementInterface()->setOnAllDesktops(true);
|
|
} else {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
if (info) {
|
|
info->setDesktop(desktop());
|
|
}
|
|
|
|
if ((was_desk == NET::OnAllDesktops) != (desktop() == NET::OnAllDesktops)) {
|
|
// 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)
|
|
(*it)->setDesktops(desktops);
|
|
|
|
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())
|
|
c2->setDesktops(desktops);
|
|
}
|
|
|
|
doSetDesktop(desktop(), was_desk);
|
|
|
|
FocusChain::self()->update(this, FocusChain::MakeFirst);
|
|
updateWindowRules(Rules::Desktop);
|
|
|
|
emit desktopChanged();
|
|
if (wasOnCurrentDesktop != isOnCurrentDesktop())
|
|
emit desktopPresenceChanged(this, was_desk);
|
|
emit x11DesktopIdsChanged();
|
|
}
|
|
|
|
void AbstractClient::doSetDesktop(int desktop, int was_desk)
|
|
{
|
|
Q_UNUSED(desktop)
|
|
Q_UNUSED(was_desk)
|
|
}
|
|
|
|
void AbstractClient::enterDesktop(VirtualDesktop *virtualDesktop)
|
|
{
|
|
if (m_desktops.contains(virtualDesktop)) {
|
|
return;
|
|
}
|
|
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;
|
|
}
|
|
|
|
if (!currentDesktops.contains(virtualDesktop)) {
|
|
return;
|
|
}
|
|
auto desktops = currentDesktops;
|
|
desktops.removeOne(virtualDesktop);
|
|
setDesktops(desktops);
|
|
}
|
|
|
|
void AbstractClient::setOnAllDesktops(bool b)
|
|
{
|
|
if ((b && isOnAllDesktops()) ||
|
|
(!b && !isOnAllDesktops()))
|
|
return;
|
|
if (b)
|
|
setDesktop(NET::OnAllDesktops);
|
|
else
|
|
setDesktop(VirtualDesktopManager::self()->current());
|
|
}
|
|
|
|
QVector<uint> AbstractClient::x11DesktopIds() const
|
|
{
|
|
const auto desks = desktops();
|
|
QVector<uint> x11Ids;
|
|
x11Ids.reserve(desks.count());
|
|
std::transform(desks.constBegin(), desks.constEnd(),
|
|
std::back_inserter(x11Ids),
|
|
[] (const VirtualDesktop *vd) {
|
|
return vd->x11DesktopNumber();
|
|
}
|
|
);
|
|
return x11Ids;
|
|
}
|
|
|
|
bool AbstractClient::isShadeable() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void AbstractClient::setShade(bool set)
|
|
{
|
|
set ? setShade(ShadeNormal) : setShade(ShadeNone);
|
|
}
|
|
|
|
void AbstractClient::setShade(ShadeMode mode)
|
|
{
|
|
Q_UNUSED(mode)
|
|
}
|
|
|
|
ShadeMode AbstractClient::shadeMode() const
|
|
{
|
|
return ShadeNone;
|
|
}
|
|
|
|
AbstractClient::Position AbstractClient::titlebarPosition() const
|
|
{
|
|
// TODO: still needed, remove?
|
|
return PositionTop;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void AbstractClient::setMinimized(bool set)
|
|
{
|
|
set ? minimize() : unminimize();
|
|
}
|
|
|
|
void AbstractClient::minimize(bool avoid_animation)
|
|
{
|
|
if (!isMinimizable() || isMinimized())
|
|
return;
|
|
|
|
if (isShade() && info) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded
|
|
info->setState(NET::States(), NET::Shaded);
|
|
|
|
m_minimized = true;
|
|
|
|
doMinimize();
|
|
|
|
updateWindowRules(Rules::Minimize);
|
|
FocusChain::self()->update(this, FocusChain::MakeFirstMinimized);
|
|
// TODO: merge signal with s_minimized
|
|
emit clientMinimized(this, !avoid_animation);
|
|
emit minimizedChanged();
|
|
}
|
|
|
|
void AbstractClient::unminimize(bool avoid_animation)
|
|
{
|
|
if (!isMinimized())
|
|
return;
|
|
|
|
if (rules()->checkMinimize(false)) {
|
|
return;
|
|
}
|
|
|
|
if (isShade() && info) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded
|
|
info->setState(NET::Shaded, NET::Shaded);
|
|
|
|
m_minimized = false;
|
|
|
|
doMinimize();
|
|
|
|
updateWindowRules(Rules::Minimize);
|
|
emit clientUnminimized(this, !avoid_animation);
|
|
emit minimizedChanged();
|
|
}
|
|
|
|
void AbstractClient::doMinimize()
|
|
{
|
|
}
|
|
|
|
QPalette AbstractClient::palette() const
|
|
{
|
|
if (!m_palette) {
|
|
return QPalette();
|
|
}
|
|
return m_palette->palette();
|
|
}
|
|
|
|
const Decoration::DecorationPalette *AbstractClient::decorationPalette() const
|
|
{
|
|
return m_palette.get();
|
|
}
|
|
|
|
void AbstractClient::updateColorScheme(QString path)
|
|
{
|
|
if (path.isEmpty()) {
|
|
path = QStringLiteral("kdeglobals");
|
|
}
|
|
|
|
if (!m_palette || m_colorScheme != path) {
|
|
m_colorScheme = path;
|
|
|
|
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());
|
|
emit colorSchemeChanged();
|
|
}
|
|
}
|
|
|
|
void AbstractClient::handlePaletteChange()
|
|
{
|
|
emit paletteChanged(palette());
|
|
}
|
|
|
|
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())
|
|
resizeWithChecks(qMin(area.width(), width()), qMin(area.height(), height()));
|
|
}
|
|
int tx = x(), ty = y();
|
|
if (frameGeometry().right() > area.right() && width() <= area.width())
|
|
tx = area.right() - width() + 1;
|
|
if (frameGeometry().bottom() > area.bottom() && height() <= area.height())
|
|
ty = area.bottom() - height() + 1;
|
|
if (!area.contains(frameGeometry().topLeft())) {
|
|
if (tx < area.x())
|
|
tx = area.x();
|
|
if (ty < area.y())
|
|
ty = area.y();
|
|
}
|
|
if (tx != x() || ty != y())
|
|
move(tx, ty);
|
|
}
|
|
|
|
QSize AbstractClient::maxSize() const
|
|
{
|
|
return rules()->checkMaxSize(QSize(INT_MAX, INT_MAX));
|
|
}
|
|
|
|
QSize AbstractClient::minSize() const
|
|
{
|
|
return rules()->checkMinSize(QSize(0, 0));
|
|
}
|
|
|
|
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
|
|
const MaximizeMode oldMode = maximizeMode();
|
|
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;
|
|
}
|
|
doMove(x, y);
|
|
updateWindowRules(Rules::Position);
|
|
screens()->setCurrent(this);
|
|
workspace()->updateStackingOrder();
|
|
// client itself is not damaged
|
|
addRepaintDuringGeometryUpdates();
|
|
updateGeometryBeforeUpdateBlocking();
|
|
emit geometryChanged();
|
|
}
|
|
|
|
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());
|
|
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();
|
|
|
|
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()) {
|
|
workspace()->sendClientToScreen(this, screen()); // checks rule validity
|
|
if (maximizeMode() != MaximizeRestore)
|
|
checkWorkspacePosition();
|
|
}
|
|
|
|
if (isElectricBorderMaximizing()) {
|
|
setQuickTileMode(electricBorderMode());
|
|
setElectricBorderMaximizing(false);
|
|
} else if (!cancel) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
// When the user pressed mouse on the titlebar, don't activate move immediatelly,
|
|
// 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;
|
|
}
|
|
|
|
void AbstractClient::updateMoveResize(const QPointF ¤tGlobalCursor)
|
|
{
|
|
handleMoveResize(pos(), currentGlobalCursor.toPoint());
|
|
}
|
|
|
|
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();
|
|
SizeMode sizeMode = SizeModeAny;
|
|
auto calculateMoveResizeGeom = [this, &topleft, &bottomright, &orig, &sizeMode, &mode]() {
|
|
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()));
|
|
sizeMode = SizeModeFixedH; // try not to affect height
|
|
break;
|
|
case PositionBottom:
|
|
setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(orig.right(), bottomright.y())));
|
|
sizeMode = SizeModeFixedH;
|
|
break;
|
|
case PositionLeft:
|
|
setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.top()), orig.bottomRight()));
|
|
sizeMode = SizeModeFixedW;
|
|
break;
|
|
case PositionRight:
|
|
setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(bottomright.x(), orig.bottom())));
|
|
sizeMode = SizeModeFixedW;
|
|
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
|
|
QSize size = adjustedSize(moveResizeGeometry().size(), sizeMode);
|
|
// 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 ?
|
|
if (sizeMode == SizeModeFixedH)
|
|
orig.setRight(bottomright.x());
|
|
else if (sizeMode == SizeModeFixedW)
|
|
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);
|
|
QSize adjSize = adjustedSize(moveResizeGeom.size(), SizeModeMax);
|
|
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()) {
|
|
ScreenEdges::self()->check(globalPos, QDateTime::fromMSecsSinceEpoch(xTime()));
|
|
}
|
|
}
|
|
|
|
void AbstractClient::performMoveResize()
|
|
{
|
|
const QRect &moveResizeGeom = moveResizeGeometry();
|
|
if (isMove() || (isResize() && !haveResizeEffect())) {
|
|
setFrameGeometry(moveResizeGeom);
|
|
}
|
|
doPerformMoveResize();
|
|
if (isResize())
|
|
addRepaintFull();
|
|
positionGeometryTip();
|
|
emit clientStepUserMovedResized(this, moveResizeGeom);
|
|
}
|
|
|
|
bool AbstractClient::hasStrut() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void AbstractClient::setupWindowManagementInterface()
|
|
{
|
|
if (m_windowManagementInterface) {
|
|
// already setup
|
|
return;
|
|
}
|
|
if (!waylandServer() || !surface()) {
|
|
return;
|
|
}
|
|
if (!waylandServer()->windowManagement()) {
|
|
return;
|
|
}
|
|
using namespace KWayland::Server;
|
|
auto w = waylandServer()->windowManagement()->createWindow(waylandServer()->windowManagement());
|
|
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());
|
|
w->setIcon(icon());
|
|
auto updateAppId = [this, w] {
|
|
w->setAppId(QString::fromUtf8(m_desktopFileName.isEmpty() ? resourceClass() : m_desktopFileName));
|
|
};
|
|
updateAppId();
|
|
w->setSkipTaskbar(skipTaskbar());
|
|
w->setSkipSwitcher(skipSwitcher());
|
|
w->setPid(pid());
|
|
w->setShadeable(isShadeable());
|
|
w->setShaded(isShade());
|
|
w->setResizable(isResizable());
|
|
w->setMovable(isMovable());
|
|
w->setVirtualDesktopChangeable(true); // FIXME Matches X11Client::actionSupported(), but both should be implemented.
|
|
w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr);
|
|
w->setGeometry(frameGeometry());
|
|
connect(this, &AbstractClient::skipTaskbarChanged, w,
|
|
[w, this] {
|
|
w->setSkipTaskbar(skipTaskbar());
|
|
}
|
|
);
|
|
connect(this, &AbstractClient::skipSwitcherChanged, w,
|
|
[w, this] {
|
|
w->setSkipSwitcher(skipSwitcher());
|
|
}
|
|
);
|
|
connect(this, &AbstractClient::captionChanged, w, [w, this] { w->setTitle(caption()); });
|
|
|
|
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] {
|
|
w->setIcon(icon());
|
|
}
|
|
);
|
|
connect(this, &AbstractClient::windowClassChanged, w, updateAppId);
|
|
connect(this, &AbstractClient::desktopFileNameChanged, w, updateAppId);
|
|
connect(this, &AbstractClient::shadeChanged, w, [w, this] { w->setShaded(isShade()); });
|
|
connect(this, &AbstractClient::transientChanged, w,
|
|
[w, this] {
|
|
w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr);
|
|
}
|
|
);
|
|
connect(this, &AbstractClient::geometryChanged, w,
|
|
[w, this] {
|
|
w->setGeometry(frameGeometry());
|
|
}
|
|
);
|
|
connect(w, &PlasmaWindowInterface::closeRequested, this, [this] { closeWindow(); });
|
|
connect(w, &PlasmaWindowInterface::moveRequested, this,
|
|
[this] {
|
|
Cursor::setPos(frameGeometry().center());
|
|
performMouseCommand(Options::MouseMove, Cursor::pos());
|
|
}
|
|
);
|
|
connect(w, &PlasmaWindowInterface::resizeRequested, this,
|
|
[this] {
|
|
Cursor::setPos(frameGeometry().bottomRight());
|
|
performMouseCommand(Options::MouseResize, Cursor::pos());
|
|
}
|
|
);
|
|
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);
|
|
}
|
|
}
|
|
);
|
|
connect(w, &PlasmaWindowInterface::shadedRequested, this,
|
|
[this] (bool set) {
|
|
setShade(set);
|
|
}
|
|
);
|
|
|
|
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) {
|
|
enterDesktop(vd);
|
|
}
|
|
}
|
|
);
|
|
connect(w, &PlasmaWindowInterface::enterNewPlasmaVirtualDesktopRequested, this,
|
|
[this] () {
|
|
VirtualDesktopManager::self()->setCount(VirtualDesktopManager::self()->count() + 1);
|
|
enterDesktop(VirtualDesktopManager::self()->desktops().last());
|
|
}
|
|
);
|
|
connect(w, &PlasmaWindowInterface::leavePlasmaVirtualDesktopRequested, this,
|
|
[this] (const QString &desktopId) {
|
|
VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId.toUtf8());
|
|
if (vd) {
|
|
leaveDesktop(vd);
|
|
}
|
|
}
|
|
);
|
|
|
|
m_windowManagementInterface = w;
|
|
}
|
|
|
|
void AbstractClient::destroyWindowManagementInterface()
|
|
{
|
|
if (m_windowManagementInterface) {
|
|
m_windowManagementInterface->unmap();
|
|
m_windowManagementInterface = nullptr;
|
|
}
|
|
}
|
|
|
|
Options::MouseCommand AbstractClient::getMouseCommand(Qt::MouseButton button, bool *handled) const
|
|
{
|
|
*handled = false;
|
|
if (button == Qt::NoButton) {
|
|
return Options::MouseNothing;
|
|
}
|
|
if (isActive()) {
|
|
if (options->isClickRaise()) {
|
|
*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;
|
|
}
|
|
|
|
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);
|
|
// 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);
|
|
}
|
|
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
|
|
bool mustReplay = !rules()->checkAcceptFocus(acceptsFocus());
|
|
if (mustReplay) {
|
|
auto it = workspace()->stackingOrder().constEnd(),
|
|
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"
|
|
mustReplay = !(c->isOnCurrentDesktop() && c->isOnCurrentActivity() && c->frameGeometry().intersects(frameGeometry()));
|
|
}
|
|
}
|
|
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);
|
|
replay = replay || !rules()->checkAcceptFocus(acceptsFocus());
|
|
break;
|
|
case Options::MouseActivate:
|
|
replay = isActive(); // for clickraise mode
|
|
workspace()->takeActivity(this, Workspace::ActivityFocus);
|
|
screens()->setCurrent(globalPos);
|
|
replay = replay || !rules()->checkAcceptFocus(acceptsFocus());
|
|
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;
|
|
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;
|
|
}
|
|
case Options::MouseNothing:
|
|
default:
|
|
replay = true;
|
|
break;
|
|
}
|
|
return replay;
|
|
}
|
|
|
|
void AbstractClient::setTransientFor(AbstractClient *transientFor)
|
|
{
|
|
if (transientFor == this) {
|
|
// cannot be transient for one self
|
|
return;
|
|
}
|
|
if (m_transientFor == transientFor) {
|
|
return;
|
|
}
|
|
m_transientFor = transientFor;
|
|
emit transientChanged();
|
|
}
|
|
|
|
const AbstractClient *AbstractClient::transientFor() const
|
|
{
|
|
return m_transientFor;
|
|
}
|
|
|
|
AbstractClient *AbstractClient::transientFor()
|
|
{
|
|
return m_transientFor;
|
|
}
|
|
|
|
bool AbstractClient::hasTransientPlacementHint() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QRect AbstractClient::transientPlacement(const QRect &bounds) const
|
|
{
|
|
Q_UNUSED(bounds);
|
|
Q_UNREACHABLE();
|
|
return QRect();
|
|
}
|
|
|
|
bool AbstractClient::hasTransient(const AbstractClient *c, bool indirect) const
|
|
{
|
|
Q_UNUSED(indirect);
|
|
return c->transientFor() == this;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void AbstractClient::addTransient(AbstractClient *cl)
|
|
{
|
|
Q_ASSERT(!m_transients.contains(cl));
|
|
Q_ASSERT(cl != this);
|
|
m_transients.append(cl);
|
|
}
|
|
|
|
void AbstractClient::removeTransient(AbstractClient *cl)
|
|
{
|
|
m_transients.removeAll(cl);
|
|
if (cl->transientFor() == this) {
|
|
cl->setTransientFor(nullptr);
|
|
}
|
|
}
|
|
|
|
void AbstractClient::removeTransientFromList(AbstractClient *cl)
|
|
{
|
|
m_transients.removeAll(cl);
|
|
}
|
|
|
|
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
|
|
return ac && (ac == this || ac->screen() != screen()|| ac->allMainClients().contains(const_cast<AbstractClient*>(this)));
|
|
}
|
|
|
|
#define BORDER(which) \
|
|
int AbstractClient::border##which() const \
|
|
{ \
|
|
return isDecorated() ? decoration()->border##which() : 0; \
|
|
}
|
|
|
|
BORDER(Bottom)
|
|
BORDER(Left)
|
|
BORDER(Right)
|
|
BORDER(Top)
|
|
#undef BORDER
|
|
|
|
QSize AbstractClient::sizeForClientSize(const QSize &wsize, SizeMode mode, bool noframe) const
|
|
{
|
|
Q_UNUSED(mode)
|
|
Q_UNUSED(noframe)
|
|
return wsize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom());
|
|
}
|
|
|
|
void AbstractClient::addRepaintDuringGeometryUpdates()
|
|
{
|
|
const QRect deco_rect = visibleRect();
|
|
addLayerRepaint(m_visibleRectBeforeGeometryUpdate);
|
|
addLayerRepaint(deco_rect); // trigger repaint of window's new location
|
|
m_visibleRectBeforeGeometryUpdate = deco_rect;
|
|
}
|
|
|
|
QRect AbstractClient::bufferGeometryBeforeUpdateBlocking() const
|
|
{
|
|
return m_bufferGeometryBeforeUpdateBlocking;
|
|
}
|
|
|
|
QRect AbstractClient::frameGeometryBeforeUpdateBlocking() const
|
|
{
|
|
return m_frameGeometryBeforeUpdateBlocking;
|
|
}
|
|
|
|
void AbstractClient::updateGeometryBeforeUpdateBlocking()
|
|
{
|
|
m_bufferGeometryBeforeUpdateBlocking = bufferGeometry();
|
|
m_frameGeometryBeforeUpdateBlocking = frameGeometry();
|
|
}
|
|
|
|
void AbstractClient::doMove(int, int)
|
|
{
|
|
}
|
|
|
|
void AbstractClient::updateInitialMoveResizeGeometry()
|
|
{
|
|
m_moveResize.initialGeometry = frameGeometry();
|
|
m_moveResize.geometry = m_moveResize.initialGeometry;
|
|
m_moveResize.startScreen = screen();
|
|
}
|
|
|
|
void AbstractClient::updateCursor()
|
|
{
|
|
Position m = moveResizePointerMode();
|
|
if (!isResizable() || isShade())
|
|
m = PositionCenter;
|
|
CursorShape c = Qt::ArrowCursor;
|
|
switch(m) {
|
|
case PositionTopLeft:
|
|
c = KWin::ExtendedCursor::SizeNorthWest;
|
|
break;
|
|
case PositionBottomRight:
|
|
c = KWin::ExtendedCursor::SizeSouthEast;
|
|
break;
|
|
case PositionBottomLeft:
|
|
c = KWin::ExtendedCursor::SizeSouthWest;
|
|
break;
|
|
case PositionTopRight:
|
|
c = KWin::ExtendedCursor::SizeNorthEast;
|
|
break;
|
|
case PositionTop:
|
|
c = KWin::ExtendedCursor::SizeNorth;
|
|
break;
|
|
case PositionBottom:
|
|
c = KWin::ExtendedCursor::SizeSouth;
|
|
break;
|
|
case PositionLeft:
|
|
c = KWin::ExtendedCursor::SizeWest;
|
|
break;
|
|
case PositionRight:
|
|
c = KWin::ExtendedCursor::SizeEast;
|
|
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);
|
|
}
|
|
|
|
void AbstractClient::leaveMoveResize()
|
|
{
|
|
workspace()->setMoveResizeClient(nullptr);
|
|
setMoveResize(false);
|
|
if (ScreenEdges::self()->isDesktopSwitchingMovingClients())
|
|
ScreenEdges::self()->reserveDesktopSwitching(false, Qt::Vertical|Qt::Horizontal);
|
|
if (isElectricBorderMaximizing()) {
|
|
outline()->hide();
|
|
elevate(false);
|
|
}
|
|
}
|
|
|
|
bool AbstractClient::s_haveResizeEffect = false;
|
|
|
|
void AbstractClient::updateHaveResizeEffect()
|
|
{
|
|
s_haveResizeEffect = effects && static_cast<EffectsHandlerImpl*>(effects)->provides(Effect::Resize);
|
|
}
|
|
|
|
bool AbstractClient::doStartMoveResize()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void AbstractClient::positionGeometryTip()
|
|
{
|
|
}
|
|
|
|
void AbstractClient::doPerformMoveResize()
|
|
{
|
|
}
|
|
|
|
bool AbstractClient::isWaitingForMoveResizeSync() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void AbstractClient::doResizeSync()
|
|
{
|
|
}
|
|
|
|
void AbstractClient::checkQuickTilingMaximizationZones(int xroot, int yroot)
|
|
{
|
|
QuickTileMode mode = QuickTileFlag::None;
|
|
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) {
|
|
mode |= QuickTileFlag::Left;
|
|
innerBorder = isInScreen(QPoint(area.x() - 1, yroot));
|
|
} else if (xroot >= area.x() + area.width() - 20) {
|
|
mode |= QuickTileFlag::Right;
|
|
innerBorder = isInScreen(QPoint(area.right() + 1, yroot));
|
|
}
|
|
}
|
|
|
|
if (mode != QuickTileMode(QuickTileFlag::None)) {
|
|
if (yroot <= area.y() + area.height() * options->electricBorderCornerRatio())
|
|
mode |= QuickTileFlag::Top;
|
|
else if (yroot >= area.y() + area.height() - area.height() * options->electricBorderCornerRatio())
|
|
mode |= QuickTileFlag::Bottom;
|
|
} else if (options->electricBorderMaximize() && yroot <= area.y() + 5 && isMaximizable()) {
|
|
mode = QuickTileFlag::Maximize;
|
|
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())
|
|
setElectricBorderMaximizing(electricBorderMode() != QuickTileMode(QuickTileFlag::None));
|
|
});
|
|
}
|
|
m_electricMaximizingDelay->start();
|
|
} else {
|
|
setElectricBorderMaximizing(mode != QuickTileMode(QuickTileFlag::None));
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
QPoint pos = Cursor::pos();
|
|
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);
|
|
finishMoveResize(false);
|
|
updateCursor();
|
|
break;
|
|
case Qt::Key_Escape:
|
|
setMoveResizePointerButtonDown(false);
|
|
finishMoveResize(true);
|
|
updateCursor();
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
Cursor::setPos(pos);
|
|
}
|
|
|
|
QSize AbstractClient::resizeIncrements() const
|
|
{
|
|
return QSize(1, 1);
|
|
}
|
|
|
|
void AbstractClient::dontMoveResize()
|
|
{
|
|
setMoveResizePointerButtonDown(false);
|
|
stopDelayedMoveResize();
|
|
if (isMoveResize())
|
|
finishMoveResize(false);
|
|
}
|
|
|
|
AbstractClient::Position AbstractClient::mousePosition() const
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
return PositionCenter;
|
|
}
|
|
|
|
void AbstractClient::endMoveResize()
|
|
{
|
|
setMoveResizePointerButtonDown(false);
|
|
stopDelayedMoveResize();
|
|
if (isMoveResize()) {
|
|
finishMoveResize(false);
|
|
setMoveResizePointerMode(mousePosition());
|
|
}
|
|
updateCursor();
|
|
}
|
|
|
|
void AbstractClient::destroyDecoration()
|
|
{
|
|
delete m_decoration.decoration;
|
|
m_decoration.decoration = nullptr;
|
|
}
|
|
|
|
bool AbstractClient::decorationHasAlpha() const
|
|
{
|
|
if (!isDecorated() || decoration()->isOpaque()) {
|
|
// either no decoration or decoration has alpha disabled
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void AbstractClient::triggerDecorationRepaint()
|
|
{
|
|
if (isDecorated()) {
|
|
decoration()->update();
|
|
}
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
void AbstractClient::processDecorationMove(const QPoint &localPos, const QPoint &globalPos)
|
|
{
|
|
if (isMoveResizePointerButtonDown()) {
|
|
handleMoveResize(localPos.x(), localPos.y(), globalPos.x(), globalPos.y());
|
|
return;
|
|
}
|
|
// TODO: handle modifiers
|
|
Position newmode = mousePosition();
|
|
if (newmode != moveResizePointerMode()) {
|
|
setMoveResizePointerMode(newmode);
|
|
updateCursor();
|
|
}
|
|
}
|
|
|
|
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
|
|
if (event->button() == Qt::LeftButton && titlebarPositionUnderMouse()) {
|
|
if (m_decoration.doubleClickTimer.isValid()) {
|
|
const qint64 interval = m_decoration.doubleClickTimer.elapsed();
|
|
m_decoration.doubleClickTimer.invalidate();
|
|
if (interval > QGuiApplication::styleHints()->mouseDoubleClickInterval()) {
|
|
m_decoration.doubleClickTimer.start(); // expired -> new first click and pot. init
|
|
} 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
|
|
}
|
|
}
|
|
|
|
if (event->button() == Qt::LeftButton)
|
|
com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1();
|
|
else if (event->button() == Qt::MidButton)
|
|
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
|
|
&& com != Options::MouseMinimize) // mouse release event
|
|
{
|
|
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()) {
|
|
if (event->isAccepted() || !titlebarPositionUnderMouse()) {
|
|
invalidateDecorationDoubleClickTimer(); // click was for the deco and shall not init a doubleclick
|
|
}
|
|
}
|
|
|
|
if (event->buttons() == Qt::NoButton) {
|
|
setMoveResizePointerButtonDown(false);
|
|
stopDelayedMoveResize();
|
|
if (isMoveResize()) {
|
|
finishMoveResize(false);
|
|
setMoveResizePointerMode(mousePosition());
|
|
}
|
|
updateCursor();
|
|
}
|
|
}
|
|
|
|
|
|
void AbstractClient::startDecorationDoubleClickTimer()
|
|
{
|
|
m_decoration.doubleClickTimer.start();
|
|
}
|
|
|
|
void AbstractClient::invalidateDecorationDoubleClickTimer()
|
|
{
|
|
m_decoration.doubleClickTimer.invalidate();
|
|
}
|
|
|
|
bool AbstractClient::providesContextHelp() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void AbstractClient::showContextHelp()
|
|
{
|
|
}
|
|
|
|
QPointer<Decoration::DecoratedClientImpl> AbstractClient::decoratedClient() const
|
|
{
|
|
return m_decoration.client;
|
|
}
|
|
|
|
void AbstractClient::setDecoratedClient(QPointer< Decoration::DecoratedClientImpl > client)
|
|
{
|
|
m_decoration.client = client;
|
|
}
|
|
|
|
void AbstractClient::enterEvent(const QPoint &globalPos)
|
|
{
|
|
// TODO: shade hover
|
|
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);
|
|
}
|
|
}
|
|
|
|
void AbstractClient::leaveEvent()
|
|
{
|
|
cancelAutoRaise();
|
|
workspace()->cancelDelayFocus();
|
|
// TODO: shade hover
|
|
// TODO: send hover leave to deco
|
|
// TODO: handle Options::FocusStrictlyUnderMouse
|
|
}
|
|
|
|
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) {
|
|
AbstractClient *client = waylandServer()->findAbstractClient(i.key());
|
|
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());
|
|
}
|
|
|
|
QRect AbstractClient::inputGeometry() const
|
|
{
|
|
if (isDecorated()) {
|
|
return Toplevel::inputGeometry() + decoration()->resizeOnlyBorders();
|
|
}
|
|
return Toplevel::inputGeometry();
|
|
}
|
|
|
|
QRect AbstractClient::virtualKeyboardGeometry() const
|
|
{
|
|
return m_virtualKeyboardGeometry;
|
|
}
|
|
|
|
void AbstractClient::setVirtualKeyboardGeometry(const QRect &geo)
|
|
{
|
|
// No keyboard anymore
|
|
if (geo.isEmpty() && !m_keyboardGeometryRestore.isEmpty()) {
|
|
setFrameGeometry(m_keyboardGeometryRestore);
|
|
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()) {
|
|
m_keyboardGeometryRestore = frameGeometry();
|
|
}
|
|
|
|
m_virtualKeyboardGeometry = geo;
|
|
|
|
// Don't resize Desktop and fullscreen windows
|
|
if (isFullScreen() || isDesktop()) {
|
|
return;
|
|
}
|
|
|
|
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()));
|
|
|
|
setFrameGeometry(newWindowGeometry);
|
|
}
|
|
|
|
bool AbstractClient::dockWantsInput() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void AbstractClient::setDesktopFileName(QByteArray name)
|
|
{
|
|
name = rules()->checkDesktopFile(name).toUtf8();
|
|
if (name == m_desktopFileName) {
|
|
return;
|
|
}
|
|
m_desktopFileName = name;
|
|
updateWindowRules(Rules::DesktopFile);
|
|
emit desktopFileNameChanged();
|
|
}
|
|
|
|
QString AbstractClient::iconFromDesktopFile() const
|
|
{
|
|
if (m_desktopFileName.isEmpty()) {
|
|
return QString();
|
|
}
|
|
QString desktopFile = QString::fromUtf8(m_desktopFileName);
|
|
if (!desktopFile.endsWith(QLatin1String(".desktop"))) {
|
|
desktopFile.append(QLatin1String(".desktop"));
|
|
}
|
|
KDesktopFile df(desktopFile);
|
|
return df.readIcon();
|
|
}
|
|
|
|
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();
|
|
|
|
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();
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
QString AbstractClient::shortcutCaptionSuffix() const
|
|
{
|
|
if (shortcut().isEmpty()) {
|
|
return QString();
|
|
}
|
|
return QLatin1String(" {") + shortcut().toString() + QLatin1Char('}');
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
QString AbstractClient::caption() const
|
|
{
|
|
QString cap = captionNormal() + captionSuffix();
|
|
if (unresponsive()) {
|
|
cap += QLatin1String(" ");
|
|
cap += i18nc("Application is not responding, appended to window title", "(Not Responding)");
|
|
}
|
|
return cap;
|
|
}
|
|
|
|
void AbstractClient::removeRule(Rules* rule)
|
|
{
|
|
m_rules.remove(rule);
|
|
}
|
|
|
|
void AbstractClient::discardTemporaryRules()
|
|
{
|
|
m_rules.discardTemporary();
|
|
}
|
|
|
|
void AbstractClient::evaluateWindowRules()
|
|
{
|
|
setupWindowRules(true);
|
|
applyWindowRules();
|
|
}
|
|
|
|
void AbstractClient::setOnActivities(QStringList newActivitiesList)
|
|
{
|
|
Q_UNUSED(newActivitiesList)
|
|
}
|
|
|
|
void AbstractClient::checkNoBorder()
|
|
{
|
|
setNoBorder(false);
|
|
}
|
|
|
|
bool AbstractClient::groupTransient() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const Group *AbstractClient::group() const
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
Group *AbstractClient::group()
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
bool AbstractClient::isInternal() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool AbstractClient::supportsWindowRules() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
QMargins AbstractClient::frameMargins() const
|
|
{
|
|
return QMargins(borderLeft(), borderTop(), borderRight(), borderBottom());
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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)
|
|
outline()->show(electricBorderMaximizeGeometry(Cursor::pos(), desktop()), moveResizeGeometry());
|
|
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;
|
|
}
|
|
|
|
workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event
|
|
|
|
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);
|
|
}
|
|
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);
|
|
|
|
setFrameGeometry(electricBorderMaximizeGeometry(keyboard ? frameGeometry().center() : Cursor::pos(), desktop()), geom_mode);
|
|
// Store the mode change
|
|
m_quickTileMode = mode;
|
|
} else {
|
|
m_quickTileMode = mode;
|
|
setMaximize(false, false);
|
|
}
|
|
|
|
emit quickTileModeChanged();
|
|
|
|
return;
|
|
}
|
|
|
|
if (mode != QuickTileMode(QuickTileFlag::None)) {
|
|
QPoint whichScreen = keyboard ? frameGeometry().center() : Cursor::pos();
|
|
|
|
// 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
|
|
}
|
|
emit quickTileModeChanged();
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
if (screen() == newScreen) // Don't use isOnScreen(), that's true even when only partially
|
|
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);
|
|
}
|
|
|
|
// align geom_restore - checkWorkspacePosition operates on it
|
|
setGeometryRestore(frameGeometry());
|
|
|
|
checkWorkspacePosition(oldGeom);
|
|
|
|
// re-align geom_restore to constrained geometry
|
|
setGeometryRestore(frameGeometry());
|
|
|
|
// 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);
|
|
}
|
|
|
|
void AbstractClient::checkWorkspacePosition(QRect oldGeometry, int oldDesktop, QRect oldClientGeometry)
|
|
{
|
|
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 (isDesktop())
|
|
return;
|
|
if (isFullScreen()) {
|
|
QRect area = workspace()->clientArea(FullScreenArea, this);
|
|
if (frameGeometry() != area)
|
|
setFrameGeometry(area);
|
|
return;
|
|
}
|
|
if (isDock())
|
|
return;
|
|
|
|
if (maximizeMode() != MaximizeRestore) {
|
|
// TODO update geom_restore?
|
|
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())
|
|
newGeom.setSize(adjustedSize(newGeom.size()));
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
QSize AbstractClient::adjustedSize(const QSize& frame, SizeMode mode) const
|
|
{
|
|
// first, get the window size for the given frame size s
|
|
QSize wsize = frameSizeToClientSize(frame);
|
|
if (wsize.isEmpty())
|
|
wsize = QSize(qMax(wsize.width(), 1), qMax(wsize.height(), 1));
|
|
|
|
return sizeForClientSize(wsize, mode, false);
|
|
}
|
|
|
|
// this helper returns proper size even if the window is shaded
|
|
// see also the comment in X11Client::setGeometry()
|
|
QSize AbstractClient::adjustedSize() const
|
|
{
|
|
return sizeForClientSize(clientSize());
|
|
}
|
|
|
|
}
|