You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3563 lines
121 KiB
3563 lines
121 KiB
/* |
|
KWin - the KDE window manager |
|
This file is part of the KDE project. |
|
|
|
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org> |
|
SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org> |
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later |
|
*/ |
|
#include "abstract_client.h" |
|
|
|
#include "appmenu.h" |
|
#include "decorations/decoratedclient.h" |
|
#include "decorations/decorationpalette.h" |
|
#include "decorations/decorationbridge.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 <KWaylandServer/plasmawindowmanagement_interface.h> |
|
|
|
#include <KDecoration2/DecoratedClient> |
|
#include <KDecoration2/Decoration> |
|
|
|
#include <KDesktopFile> |
|
|
|
#include <QDir> |
|
#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::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::frameGeometryChanged, this, |
|
[this] (Toplevel *c, const QRect &old) { |
|
Q_UNUSED(c) |
|
if (isOnScreenDisplay() && !frameGeometry().isEmpty() && old.size() != frameGeometry().size() && isPlaceable()) { |
|
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) |
|
} |
|
|
|
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 (isZombie()) { |
|
return; |
|
} |
|
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() |
|
{ |
|
} |
|
|
|
bool AbstractClient::isZombie() const |
|
{ |
|
return m_zombie; |
|
} |
|
|
|
void AbstractClient::markAsZombie() |
|
{ |
|
Q_ASSERT(!m_zombie); |
|
m_zombie = true; |
|
addWorkspaceRepaint(visibleRect()); |
|
} |
|
|
|
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::placeIn(const QRect &area) |
|
{ |
|
// TODO: Get rid of this method eventually. We need to call setGeometryRestore() because |
|
// checkWorkspacePosition() operates on geometryRestore() and because of quick tiling. |
|
Placement::self()->place(this, area); |
|
setGeometryRestore(frameGeometry()); |
|
} |
|
|
|
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 (isLockScreen()) |
|
return UnmanagedLayer; |
|
if (isInputMethod()) |
|
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()) { |
|
return; |
|
} |
|
m_keepAbove = b; |
|
doSetKeepAbove(); |
|
workspace()->updateClientLayer(this); |
|
updateWindowRules(Rules::Above); |
|
|
|
emit keepAboveChanged(m_keepAbove); |
|
} |
|
|
|
void AbstractClient::doSetKeepAbove() |
|
{ |
|
} |
|
|
|
void AbstractClient::setKeepBelow(bool b) |
|
{ |
|
b = rules()->checkKeepBelow(b); |
|
if (b && !rules()->checkKeepAbove(false)) |
|
setKeepAbove(false); |
|
if (b == keepBelow()) { |
|
return; |
|
} |
|
m_keepBelow = b; |
|
doSetKeepBelow(); |
|
workspace()->updateClientLayer(this); |
|
updateWindowRules(Rules::Below); |
|
|
|
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::isMostRecentlyRaised() const |
|
{ |
|
// The last toplevel in the unconstrained stacking order is the most recently raised one. |
|
return workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), -1, true, false) == this; |
|
} |
|
|
|
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; |
|
doSetDemandsAttention(); |
|
workspace()->clientAttentionChanged(this, set); |
|
emit demandsAttentionChanged(); |
|
} |
|
|
|
void AbstractClient::doSetDemandsAttention() |
|
{ |
|
} |
|
|
|
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(); |
|
|
|
FocusChain::self()->update(this, FocusChain::MakeFirst); |
|
updateWindowRules(Rules::Desktop); |
|
|
|
emit desktopChanged(); |
|
if (wasOnCurrentDesktop != isOnCurrentDesktop()) |
|
emit desktopPresenceChanged(this, was_desk); |
|
emit x11DesktopIdsChanged(); |
|
} |
|
|
|
void AbstractClient::doSetDesktop() |
|
{ |
|
} |
|
|
|
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; |
|
} |
|
|
|
ShadeMode AbstractClient::shadeMode() const |
|
{ |
|
return m_shadeMode; |
|
} |
|
|
|
bool AbstractClient::isShadeable() const |
|
{ |
|
return false; |
|
} |
|
|
|
void AbstractClient::setShade(bool set) |
|
{ |
|
set ? setShade(ShadeNormal) : setShade(ShadeNone); |
|
} |
|
|
|
void AbstractClient::setShade(ShadeMode mode) |
|
{ |
|
if (!isShadeable()) |
|
return; |
|
if (mode == ShadeHover && isMove()) |
|
return; // causes geometry breaks and is probably nasty |
|
if (isSpecialWindow() || noBorder()) |
|
mode = ShadeNone; |
|
|
|
mode = rules()->checkShade(mode); |
|
if (m_shadeMode == mode) |
|
return; |
|
|
|
const bool wasShade = isShade(); |
|
const ShadeMode previousShadeMode = shadeMode(); |
|
m_shadeMode = mode; |
|
|
|
if (wasShade == isShade()) { |
|
// Decoration may want to update after e.g. hover-shade changes |
|
emit shadeChanged(); |
|
return; // No real change in shaded state |
|
} |
|
|
|
Q_ASSERT(isDecorated()); |
|
GeometryUpdatesBlocker blocker(this); |
|
|
|
doSetShade(previousShadeMode); |
|
|
|
discardWindowPixmap(); |
|
updateWindowRules(Rules::Shade); |
|
|
|
emit shadeChanged(); |
|
} |
|
|
|
void AbstractClient::doSetShade(ShadeMode previousShadeMode) |
|
{ |
|
Q_UNUSED(previousShadeMode) |
|
} |
|
|
|
void AbstractClient::shadeHover() |
|
{ |
|
setShade(ShadeHover); |
|
cancelShadeHoverTimer(); |
|
} |
|
|
|
void AbstractClient::shadeUnhover() |
|
{ |
|
setShade(ShadeNormal); |
|
cancelShadeHoverTimer(); |
|
} |
|
|
|
void AbstractClient::startShadeHoverTimer() |
|
{ |
|
if (!isShade()) |
|
return; |
|
m_shadeHoverTimer = new QTimer(this); |
|
connect(m_shadeHoverTimer, &QTimer::timeout, this, &AbstractClient::shadeHover); |
|
m_shadeHoverTimer->setSingleShot(true); |
|
m_shadeHoverTimer->start(options->shadeHoverInterval()); |
|
} |
|
|
|
void AbstractClient::startShadeUnhoverTimer() |
|
{ |
|
if (m_shadeMode == ShadeHover && !isMoveResize() && !isMoveResizePointerButtonDown()) { |
|
m_shadeHoverTimer = new QTimer(this); |
|
connect(m_shadeHoverTimer, &QTimer::timeout, this, &AbstractClient::shadeUnhover); |
|
m_shadeHoverTimer->setSingleShot(true); |
|
m_shadeHoverTimer->start(options->shadeHoverInterval()); |
|
} |
|
} |
|
|
|
void AbstractClient::cancelShadeHoverTimer() |
|
{ |
|
delete m_shadeHoverTimer; |
|
m_shadeHoverTimer = nullptr; |
|
} |
|
|
|
void AbstractClient::toggleShade() |
|
{ |
|
// If the mode is ShadeHover or ShadeActive, cancel shade too. |
|
setShade(shadeMode() == ShadeNone ? ShadeNormal : ShadeNone); |
|
} |
|
|
|
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; |
|
|
|
m_minimized = true; |
|
doMinimize(); |
|
|
|
updateWindowRules(Rules::Minimize); |
|
|
|
if (options->moveMinimizedWindowsToEndOfTabBoxFocusChain()) { |
|
FocusChain::self()->update(this, FocusChain::MakeFirstMinimized); |
|
} |
|
|
|
// TODO: merge signal with s_minimized |
|
addWorkspaceRepaint(visibleRect()); |
|
emit clientMinimized(this, !avoid_animation); |
|
emit minimizedChanged(); |
|
} |
|
|
|
void AbstractClient::unminimize(bool avoid_animation) |
|
{ |
|
if (!isMinimized()) |
|
return; |
|
|
|
if (rules()->checkMinimize(false)) { |
|
return; |
|
} |
|
|
|
m_minimized = false; |
|
doMinimize(); |
|
|
|
updateWindowRules(Rules::Minimize); |
|
emit clientUnminimized(this, !avoid_animation); |
|
emit minimizedChanged(); |
|
} |
|
|
|
void AbstractClient::doMinimize() |
|
{ |
|
} |
|
|
|
QPalette AbstractClient::palette() const |
|
{ |
|
if (!m_palette) { |
|
return QPalette(); |
|
} |
|
return m_palette->palette(); |
|
} |
|
|
|
const Decoration::DecorationPalette *AbstractClient::decorationPalette() const |
|
{ |
|
return m_palette.get(); |
|
} |
|
|
|
QString AbstractClient::preferredColorScheme() const |
|
{ |
|
return rules()->checkDecoColor(QString()); |
|
} |
|
|
|
QString AbstractClient::colorScheme() const |
|
{ |
|
return m_colorScheme; |
|
} |
|
|
|
void AbstractClient::setColorScheme(const QString &colorScheme) |
|
{ |
|
QString requestedColorScheme = colorScheme; |
|
if (requestedColorScheme.isEmpty()) { |
|
requestedColorScheme = QStringLiteral("kdeglobals"); |
|
} |
|
|
|
if (!m_palette || m_colorScheme != requestedColorScheme) { |
|
m_colorScheme = requestedColorScheme; |
|
|
|
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::updateColorScheme() |
|
{ |
|
setColorScheme(preferredColorScheme()); |
|
} |
|
|
|
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(size().boundedTo(area.size())); |
|
} |
|
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); |
|
} |
|
|
|
/** |
|
* Returns the maximum client size, not the maximum frame size. |
|
*/ |
|
QSize AbstractClient::maxSize() const |
|
{ |
|
return rules()->checkMaxSize(QSize(INT_MAX, INT_MAX)); |
|
} |
|
|
|
/** |
|
* Returns the minimum client size, not the minimum frame size. |
|
*/ |
|
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 = requestedMaximizeMode(); |
|
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; |
|
} |
|
const QRect oldBufferGeometry = bufferGeometryBeforeUpdateBlocking(); |
|
const QRect oldClientGeometry = clientGeometryBeforeUpdateBlocking(); |
|
const QRect oldFrameGeometry = frameGeometryBeforeUpdateBlocking(); |
|
doMove(x, y); |
|
updateGeometryBeforeUpdateBlocking(); |
|
updateWindowRules(Rules::Position); |
|
screens()->setCurrent(this); |
|
workspace()->updateStackingOrder(); |
|
// client itself is not damaged |
|
emit bufferGeometryChanged(this, oldBufferGeometry); |
|
emit clientGeometryChanged(this, oldClientGeometry); |
|
emit frameGeometryChanged(this, oldFrameGeometry); |
|
addRepaintDuringGeometryUpdates(); |
|
} |
|
|
|
bool AbstractClient::startMoveResize() |
|
{ |
|
Q_ASSERT(!isMoveResize()); |
|
Q_ASSERT(QWidget::keyboardGrabber() == nullptr); |
|
Q_ASSERT(QWidget::mouseGrabber() == nullptr); |
|
stopDelayedMoveResize(); |
|
if (QApplication::activePopupWidget() != nullptr) |
|
return false; // popups have grab |
|
if (isFullScreen() && (screens()->count() < 2 || !isMovableAcrossScreens())) |
|
return false; |
|
if (!doStartMoveResize()) { |
|
return false; |
|
} |
|
|
|
invalidateDecorationDoubleClickTimer(); |
|
|
|
setMoveResize(true); |
|
workspace()->setMoveResizeClient(this); |
|
|
|
const Position mode = moveResizePointerMode(); |
|
if (mode != PositionCenter) { // means "isResize()" but moveResizeMode = true is set below |
|
if (maximizeMode() == MaximizeFull) { // partial is cond. reset in finishMoveResize |
|
setGeometryRestore(frameGeometry()); // "restore" to current geometry |
|
setMaximize(false, false); |
|
} |
|
} |
|
|
|
if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && mode != PositionCenter) { // Cannot use isResize() yet |
|
// Exit quick tile mode when the user attempts to resize a tiled window |
|
updateQuickTileMode(QuickTileFlag::None); // Do so without restoring original geometry |
|
setGeometryRestore(frameGeometry()); |
|
doSetQuickTileMode(); |
|
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(); |
|
|
|
doFinishMoveResize(); |
|
|
|
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 immediately, |
|
// 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 = constrainFrameSize(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 = constrainFrameSize(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(), Qt::UTC)); |
|
} |
|
} |
|
|
|
void AbstractClient::performMoveResize() |
|
{ |
|
const QRect &moveResizeGeom = moveResizeGeometry(); |
|
if (isMove() || (isResize() && !haveResizeEffect())) { |
|
setFrameGeometry(moveResizeGeom); |
|
} |
|
doPerformMoveResize(); |
|
positionGeometryTip(); |
|
emit clientStepUserMovedResized(this, moveResizeGeom); |
|
} |
|
|
|
StrutRect AbstractClient::strutRect(StrutArea area) const |
|
{ |
|
Q_UNUSED(area) |
|
return StrutRect(); |
|
} |
|
|
|
StrutRects AbstractClient::strutRects() const |
|
{ |
|
StrutRects region; |
|
region += strutRect(StrutAreaTop); |
|
region += strutRect(StrutAreaRight); |
|
region += strutRect(StrutAreaBottom); |
|
region += strutRect(StrutAreaLeft); |
|
return region; |
|
} |
|
|
|
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 KWaylandServer; |
|
auto w = waylandServer()->windowManagement()->createWindow(waylandServer()->windowManagement(), internalId()); |
|
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->setApplicationMenuPaths(applicationMenuServiceName(), applicationMenuObjectPath()); |
|
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::frameGeometryChanged, w, |
|
[w, this] { |
|
w->setGeometry(frameGeometry()); |
|
} |
|
); |
|
connect(this, &AbstractClient::applicationMenuChanged, w, |
|
[w, this] { |
|
w->setApplicationMenuPaths(applicationMenuServiceName(), applicationMenuObjectPath()); |
|
} |
|
); |
|
connect(w, &PlasmaWindowInterface::closeRequested, this, [this] { closeWindow(); }); |
|
connect(w, &PlasmaWindowInterface::moveRequested, this, |
|
[this] { |
|
Cursors::self()->mouse()->setPos(frameGeometry().center()); |
|
performMouseCommand(Options::MouseMove, Cursors::self()->mouse()->pos()); |
|
} |
|
); |
|
connect(w, &PlasmaWindowInterface::resizeRequested, this, |
|
[this] { |
|
Cursors::self()->mouse()->setPos(frameGeometry().bottomRight()); |
|
performMouseCommand(Options::MouseResize, Cursors::self()->mouse()->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() && !isMostRecentlyRaised()) { |
|
*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::MouseShade: |
|
toggleShade(); |
|
cancelShadeHoverTimer(); |
|
break; |
|
case Options::MouseSetShade: |
|
setShade(ShadeNormal); |
|
cancelShadeHoverTimer(); |
|
break; |
|
case Options::MouseUnsetShade: |
|
setShade(ShadeNone); |
|
cancelShadeHoverTimer(); |
|
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 |
|
|
|
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; |
|
} |
|
|
|
QRect AbstractClient::clientGeometryBeforeUpdateBlocking() const |
|
{ |
|
return m_clientGeometryBeforeUpdateBlocking; |
|
} |
|
|
|
void AbstractClient::updateGeometryBeforeUpdateBlocking() |
|
{ |
|
m_bufferGeometryBeforeUpdateBlocking = bufferGeometry(); |
|
m_frameGeometryBeforeUpdateBlocking = frameGeometry(); |
|
m_clientGeometryBeforeUpdateBlocking = clientGeometry(); |
|
} |
|
|
|
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::doFinishMoveResize() |
|
{ |
|
} |
|
|
|
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 = Cursors::self()->mouse()->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; |
|
} |
|
Cursors::self()->mouse()->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::createDecoration(const QRect &oldGeometry) |
|
{ |
|
KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this); |
|
if (decoration) { |
|
QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection); |
|
connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::updateShadow); |
|
connect(decoration, &KDecoration2::Decoration::bordersChanged, |
|
this, &AbstractClient::updateDecorationInputShape); |
|
connect(decoration, &KDecoration2::Decoration::resizeOnlyBordersChanged, |
|
this, &AbstractClient::updateDecorationInputShape); |
|
connect(decoration, &KDecoration2::Decoration::bordersChanged, this, [this]() { |
|
GeometryUpdatesBlocker blocker(this); |
|
const QRect oldGeometry = frameGeometry(); |
|
if (!isShade()) { |
|
checkWorkspacePosition(oldGeometry); |
|
} |
|
emit geometryShapeChanged(this, oldGeometry); |
|
}); |
|
connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::sizeChanged, |
|
this, &AbstractClient::updateDecorationInputShape); |
|
} |
|
setDecoration(decoration); |
|
setFrameGeometry(QRect(oldGeometry.topLeft(), clientSizeToFrameSize(clientSize()))); |
|
updateDecorationInputShape(); |
|
|
|
emit geometryShapeChanged(this, oldGeometry); |
|
} |
|
|
|
void AbstractClient::destroyDecoration() |
|
{ |
|
delete m_decoration.decoration; |
|
m_decoration.decoration = nullptr; |
|
m_decoration.inputRegion = QRegion(); |
|
} |
|
|
|
void AbstractClient::updateDecorationInputShape() |
|
{ |
|
if (!isDecorated()) { |
|
return; |
|
} |
|
|
|
const QMargins borders = decoration()->borders(); |
|
const QMargins resizeBorders = decoration()->resizeOnlyBorders(); |
|
|
|
const QRect innerRect = QRect(QPoint(borderLeft(), borderTop()), decoratedClient()->size()); |
|
const QRect outerRect = innerRect + borders + resizeBorders; |
|
|
|
m_decoration.inputRegion = QRegion(outerRect) - innerRect; |
|
} |
|
|
|
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::MiddleButton) |
|
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) |
|
{ |
|
if (options->isShadeHover()) { |
|
cancelShadeHoverTimer(); |
|
startShadeHoverTimer(); |
|
} |
|
|
|
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(); |
|
cancelShadeHoverTimer(); |
|
startShadeUnhoverTimer(); |
|
// 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()->findClient(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(); |
|
} |
|
|
|
bool AbstractClient::hitTest(const QPoint &point) const |
|
{ |
|
if (isDecorated()) { |
|
if (m_decoration.inputRegion.contains(mapToFrame(point))) { |
|
return true; |
|
} |
|
} |
|
return Toplevel::hitTest(point); |
|
} |
|
|
|
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); |
|
} |
|
|
|
QRect AbstractClient::keyboardGeometryRestore() const |
|
{ |
|
return m_keyboardGeometryRestore; |
|
} |
|
|
|
void AbstractClient::setKeyboardGeometryRestore(const QRect &geom) |
|
{ |
|
m_keyboardGeometryRestore = geom; |
|
} |
|
|
|
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 {}; |
|
} |
|
|
|
const QString desktopFileName = QString::fromUtf8(m_desktopFileName); |
|
QString desktopFilePath; |
|
|
|
if (QDir::isAbsolutePath(desktopFileName)) { |
|
desktopFilePath = desktopFileName; |
|
} |
|
|
|
if (desktopFilePath.isEmpty()) { |
|
desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, |
|
desktopFileName); |
|
} |
|
if (desktopFilePath.isEmpty()) { |
|
desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, |
|
desktopFileName + QLatin1String(".desktop")); |
|
} |
|
|
|
KDesktopFile df(desktopFilePath); |
|
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(); |
|
|
|
emit applicationMenuChanged(); |
|
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(); |
|
|
|
emit applicationMenuChanged(); |
|
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 false; |
|
} |
|
|
|
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(Cursors::self()->mouse()->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(Cursors::self()->mouse()->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); |
|
} |
|
doSetQuickTileMode(); |
|
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() : Cursors::self()->mouse()->pos(), desktop()), geom_mode); |
|
// Store the mode change |
|
m_quickTileMode = mode; |
|
} else { |
|
m_quickTileMode = mode; |
|
setMaximize(false, false); |
|
} |
|
|
|
doSetQuickTileMode(); |
|
emit quickTileModeChanged(); |
|
|
|
return; |
|
} |
|
|
|
if (mode != QuickTileMode(QuickTileFlag::None)) { |
|
QPoint whichScreen = keyboard ? frameGeometry().center() : Cursors::self()->mouse()->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 |
|
} |
|
doSetQuickTileMode(); |
|
emit quickTileModeChanged(); |
|
} |
|
|
|
void AbstractClient::doSetQuickTileMode() |
|
{ |
|
} |
|
|
|
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) |
|
{ |
|
if (isDock() || isDesktop() || !isPlaceable()) { |
|
return; |
|
} |
|
enum { Left = 0, Top, Right, Bottom }; |
|
const int border[4] = { borderLeft(), borderTop(), borderRight(), borderBottom() }; |
|
if( !oldGeometry.isValid()) |
|
oldGeometry = frameGeometry(); |
|
if( oldDesktop == -2 ) |
|
oldDesktop = desktop(); |
|
if (!oldClientGeometry.isValid()) |
|
oldClientGeometry = oldGeometry.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]); |
|
if (isFullScreen()) { |
|
QRect area = workspace()->clientArea(FullScreenArea, geometryRestore().center(), desktop()); |
|
if (frameGeometry() != area) |
|
setFrameGeometry(area); |
|
return; |
|
} |
|
|
|
if (maximizeMode() != MaximizeRestore) { |
|
GeometryUpdatesBlocker block(this); |
|
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(constrainFrameSize(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); |
|
} |
|
} |
|
|
|
/** |
|
* Returns the appropriate frame size for the current client size. |
|
* |
|
* This is equivalent to clientSizeToFrameSize(constrainClientSize(clientSize())). |
|
*/ |
|
QSize AbstractClient::adjustedSize() const |
|
{ |
|
return clientSizeToFrameSize(constrainClientSize(clientSize())); |
|
} |
|
|
|
/** |
|
* Constrains the client size @p size according to a set of the window's size hints. |
|
* |
|
* Default implementation applies only minimum and maximum size constraints. |
|
*/ |
|
QSize AbstractClient::constrainClientSize(const QSize &size, SizeMode mode) const |
|
{ |
|
Q_UNUSED(mode) |
|
|
|
int width = size.width(); |
|
int height = size.height(); |
|
|
|
// When user is resizing the window, the move resize geometry may have negative width or |
|
// height. In which case, we need to set negative dimensions to reasonable values. |
|
if (width < 1) { |
|
width = 1; |
|
} |
|
if (height < 1) { |
|
height = 1; |
|
} |
|
|
|
const QSize minimumSize = minSize(); |
|
const QSize maximumSize = maxSize(); |
|
|
|
width = qBound(minimumSize.width(), width, maximumSize.width()); |
|
height = qBound(minimumSize.height(), height, maximumSize.height()); |
|
|
|
return QSize(width, height); |
|
} |
|
|
|
/** |
|
* Constrains the frame size @p size according to a set of the window's size hints. |
|
*/ |
|
QSize AbstractClient::constrainFrameSize(const QSize &size, SizeMode mode) const |
|
{ |
|
const QSize unconstrainedClientSize = frameSizeToClientSize(size); |
|
const QSize constrainedClientSize = constrainClientSize(unconstrainedClientSize, mode); |
|
return clientSizeToFrameSize(constrainedClientSize); |
|
} |
|
|
|
/** |
|
* Returns @c true if the AbstractClient can be shown in full screen mode; otherwise @c false. |
|
* |
|
* Default implementation returns @c false. |
|
*/ |
|
bool AbstractClient::isFullScreenable() const |
|
{ |
|
return false; |
|
} |
|
|
|
/** |
|
* Returns @c true if the AbstractClient is currently being shown in full screen mode; otherwise @c false. |
|
* |
|
* A client in full screen mode occupies the entire screen with no window frame around it. |
|
* |
|
* Default implementation returns @c false. |
|
*/ |
|
bool AbstractClient::isFullScreen() const |
|
{ |
|
return false; |
|
} |
|
|
|
/** |
|
* Returns whether requests initiated by the user to enter or leave full screen mode are honored. |
|
* |
|
* Default implementation returns @c false. |
|
*/ |
|
bool AbstractClient::userCanSetFullScreen() const |
|
{ |
|
return false; |
|
} |
|
|
|
/** |
|
* Asks the AbstractClient to enter or leave full screen mode. |
|
* |
|
* Default implementation does nothing. |
|
* |
|
* @param set @c true if the AbstractClient has to be shown in full screen mode, otherwise @c false |
|
* @param user @c true if the request is initiated by the user, otherwise @c false |
|
*/ |
|
void AbstractClient::setFullScreen(bool set, bool user) |
|
{ |
|
Q_UNUSED(set) |
|
Q_UNUSED(user) |
|
qCWarning(KWIN_CORE, "%s doesn't support setting fullscreen state", metaObject()->className()); |
|
} |
|
|
|
/** |
|
* Returns @c true if the AbstractClient can be minimized; otherwise @c false. |
|
* |
|
* Default implementation returns @c false. |
|
*/ |
|
bool AbstractClient::isMinimizable() const |
|
{ |
|
return false; |
|
} |
|
|
|
/** |
|
* Returns @c true if the AbstractClient can be maximized; otherwise @c false. |
|
* |
|
* Default implementation returns @c false. |
|
*/ |
|
bool AbstractClient::isMaximizable() const |
|
{ |
|
return false; |
|
} |
|
|
|
/** |
|
* Returns the currently applied maximize mode. |
|
* |
|
* Default implementation returns MaximizeRestore. |
|
*/ |
|
MaximizeMode AbstractClient::maximizeMode() const |
|
{ |
|
return MaximizeRestore; |
|
} |
|
|
|
/** |
|
* Returns the last requested maximize mode. |
|
* |
|
* On X11, this method always matches maximizeMode(). On Wayland, it is asynchronous. |
|
* |
|
* Default implementation matches maximizeMode(). |
|
*/ |
|
MaximizeMode AbstractClient::requestedMaximizeMode() const |
|
{ |
|
return maximizeMode(); |
|
} |
|
|
|
/** |
|
* Returns the geometry of the AbstractClient before it was maximized or quick tiled. |
|
*/ |
|
QRect AbstractClient::geometryRestore() const |
|
{ |
|
return m_maximizeGeometryRestore; |
|
} |
|
|
|
/** |
|
* Sets the geometry of the AbstractClient before it was maximized or quick tiled to @p rect. |
|
*/ |
|
void AbstractClient::setGeometryRestore(const QRect &rect) |
|
{ |
|
m_maximizeGeometryRestore = rect; |
|
} |
|
|
|
/** |
|
* Toggles the maximized state along specified dimensions @p horizontal and @p vertical. |
|
* |
|
* If @p adjust is @c true, only frame geometry will be updated to match requestedMaximizeMode(). |
|
* |
|
* Default implementation does nothing. |
|
*/ |
|
void AbstractClient::changeMaximize(bool horizontal, bool vertical, bool adjust) |
|
{ |
|
Q_UNUSED(horizontal) |
|
Q_UNUSED(vertical) |
|
Q_UNUSED(adjust) |
|
qCWarning(KWIN_CORE, "%s doesn't support setting maximized state", metaObject()->className()); |
|
} |
|
|
|
void AbstractClient::updateDecoration(bool check_workspace_pos, bool force) |
|
{ |
|
Q_UNUSED(check_workspace_pos) |
|
Q_UNUSED(force) |
|
qCWarning(KWIN_CORE, "%s doesn't support server side decorations", metaObject()->className()); |
|
} |
|
|
|
bool AbstractClient::noBorder() const |
|
{ |
|
return true; |
|
} |
|
|
|
bool AbstractClient::userCanSetNoBorder() const |
|
{ |
|
return false; |
|
} |
|
|
|
void AbstractClient::setNoBorder(bool set) |
|
{ |
|
Q_UNUSED(set) |
|
qCWarning(KWIN_CORE, "%s doesn't support setting decorations", metaObject()->className()); |
|
} |
|
|
|
void AbstractClient::showOnScreenEdge() |
|
{ |
|
qCWarning(KWIN_CORE, "%s doesn't support screen edge activation", metaObject()->className()); |
|
} |
|
|
|
bool AbstractClient::isPlaceable() const |
|
{ |
|
return true; |
|
} |
|
|
|
}
|
|
|