1
0
mirror of https://github.com/KDE/latte-dock.git synced 2025-03-17 02:50:08 +03:00
latte-dock/app/wm/waylandinterface.cpp
Michail Vourlakos 153f5dab27 remove obsolete draggingStarted signals/slots
--draggingStarted signals/slots were used in order
to handle the active window dragging from empty areas.
The new approach ungrabs the mouse properly through
View relevant function and at the same time sends a
QEvent::Leave event to inform applets that the mouse
has left the view. This is enough in order to remove
any older draggingStarted workarounds and hacks.
2019-08-26 16:30:48 +03:00

715 lines
19 KiB
C++

/*
* Copyright 2016 Smith AR <audoban@openmailbox.org>
* Michail Vourlakos <mvourlakos@gmail.com>
*
* This file is part of Latte-Dock
*
* Latte-Dock is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* Latte-Dock is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "waylandinterface.h"
// local
#include "view/screenedgeghostwindow.h"
#include "view/view.h"
#include "../lattecorona.h"
#include "../liblatte2/extras.h"
// Qt
#include <QDebug>
#include <QTimer>
#include <QApplication>
#include <QSignalMapper>
#include <QtX11Extras/QX11Info>
#include <QRasterWindow>
// KDE
#include <KWindowSystem>
#include <KWindowInfo>
#include <KWayland/Client/surface.h>
#if KF5_VERSION_MINOR >= 52
#include <KWayland/Client/plasmavirtualdesktop.h>
#endif
// X11
#include <NETWM>
using namespace KWayland::Client;
namespace Latte {
class Private::GhostWindow : public QRasterWindow
{
Q_OBJECT
public:
WindowSystem::WindowId m_winId;
GhostWindow(WindowSystem::WaylandInterface *waylandInterface)
: m_waylandInterface(waylandInterface) {
setFlags(Qt::FramelessWindowHint
| Qt::WindowStaysOnTopHint
| Qt::NoDropShadowWindowHint
| Qt::WindowDoesNotAcceptFocus);
connect(m_waylandInterface, &WindowSystem::AbstractWindowInterface::latteWindowAdded, this, &GhostWindow::identifyWinId);
setupWaylandIntegration();
show();
}
~GhostWindow() {
m_waylandInterface->unregisterIgnoredWindow(m_winId);
delete m_shellSurface;
}
void setGeometry(const QRect &rect) {
if (geometry() == rect) {
return;
}
m_validGeometry = rect;
setMinimumSize(rect.size());
setMaximumSize(rect.size());
resize(rect.size());
m_shellSurface->setPosition(rect.topLeft());
}
void setupWaylandIntegration() {
using namespace KWayland::Client;
if (m_shellSurface)
return;
Surface *s{Surface::fromWindow(this)};
if (!s)
return;
m_shellSurface = m_waylandInterface->waylandCoronaInterface()->createSurface(s, this);
qDebug() << "wayland ghost window surface was created...";
m_shellSurface->setSkipTaskbar(true);
m_shellSurface->setPanelTakesFocus(false);
m_shellSurface->setRole(PlasmaShellSurface::Role::Panel);
m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AlwaysVisible);
}
KWayland::Client::PlasmaShellSurface *m_shellSurface{nullptr};
WindowSystem::WaylandInterface *m_waylandInterface{nullptr};
//! geometry() function under wayland does not return nice results
QRect m_validGeometry;
public slots:
void identifyWinId() {
if (m_winId.isNull()) {
m_winId = m_waylandInterface->winIdFor("latte-dock", m_validGeometry);
m_waylandInterface->registerIgnoredWindow(m_winId);
}
}
};
namespace WindowSystem {
WaylandInterface::WaylandInterface(QObject *parent)
: AbstractWindowInterface(parent)
{
m_corona = qobject_cast<Latte::Corona *>(parent);
}
WaylandInterface::~WaylandInterface()
{
}
void WaylandInterface::init()
{
}
void WaylandInterface::initWindowManagement(KWayland::Client::PlasmaWindowManagement *windowManagement)
{
if (m_windowManagement == windowManagement) {
return;
}
m_windowManagement = windowManagement;
connect(m_windowManagement, &PlasmaWindowManagement::windowCreated, this, &WaylandInterface::windowCreatedProxy);
connect(m_windowManagement, &PlasmaWindowManagement::activeWindowChanged, this, [&]() noexcept {
auto w = m_windowManagement->activeWindow();
if (!w || (w && (!m_ignoredWindows.contains(w->internalId() && !isPlasmaDesktop(w)))) ) {
emit activeWindowChanged(w ? w->internalId() : 0);
}
}, Qt::QueuedConnection);
}
#if KF5_VERSION_MINOR >= 52
void WaylandInterface::initVirtualDesktopManagement(KWayland::Client::PlasmaVirtualDesktopManagement *virtualDesktopManagement)
{
if (m_virtualDesktopManagement == virtualDesktopManagement) {
return;
}
m_virtualDesktopManagement = virtualDesktopManagement;
connect(m_virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopCreated, this,
[this](const QString &id, quint32 position) {
addDesktop(id, position);
});
connect(m_virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopRemoved, this,
[this](const QString &id) {
m_desktops.removeAll(id);
if (m_currentDesktop == id) {
setCurrentDesktop(QString());
}
});
}
void WaylandInterface::addDesktop(const QString &id, quint32 position)
{
if (m_desktops.contains(id)) {
return;
}
m_desktops.append(id);
const KWayland::Client::PlasmaVirtualDesktop *desktop = m_virtualDesktopManagement->getVirtualDesktop(id);
QObject::connect(desktop, &KWayland::Client::PlasmaVirtualDesktop::activated, this,
[desktop, this]() {
setCurrentDesktop(desktop->id());
}
);
if (desktop->isActive()) {
setCurrentDesktop(id);
}
}
void WaylandInterface::setCurrentDesktop(QString desktop)
{
if (m_currentDesktop == desktop) {
return;
}
m_currentDesktop = desktop;
emit currentDesktopChanged();
}
#endif
KWayland::Client::PlasmaShell *WaylandInterface::waylandCoronaInterface() const
{
return m_corona->waylandCoronaInterface();
}
//! Register Latte Ignored Windows in order to NOT be tracked
void WaylandInterface::registerIgnoredWindow(WindowId wid)
{
if (!wid.isNull() && !m_ignoredWindows.contains(wid)) {
m_ignoredWindows.append(wid);
KWayland::Client::PlasmaWindow *w = windowFor(wid);
if (mapper && w) {
mapper->removeMappings(w);
}
emit windowChanged(wid);
}
}
void WaylandInterface::unregisterIgnoredWindow(WindowId wid)
{
if (m_ignoredWindows.contains(wid)) {
m_ignoredWindows.removeAll(wid);
emit windowRemoved(wid);
}
}
void WaylandInterface::setViewExtraFlags(QWindow &view)
{
Q_UNUSED(view)
}
void WaylandInterface::setViewStruts(QWindow &view, const QRect &rect, Plasma::Types::Location location)
{
if (!m_ghostWindows.contains(view.winId())) {
m_ghostWindows[view.winId()] = new Private::GhostWindow(this);
}
auto w = m_ghostWindows[view.winId()];
switch (location) {
case Plasma::Types::TopEdge:
case Plasma::Types::BottomEdge:
w->setGeometry({rect.x() + rect.width() / 2, rect.y(), 1, rect.height()});
break;
case Plasma::Types::LeftEdge:
case Plasma::Types::RightEdge:
w->setGeometry({rect.x(), rect.y() + rect.height() / 2, rect.width(), 1});
break;
default:
break;
}
}
void WaylandInterface::switchToNextVirtualDesktop() const
{
#if KF5_VERSION_MINOR >= 52
if (!m_virtualDesktopManagement || m_desktops.count() <= 1) {
return;
}
int curPos = m_desktops.indexOf(m_currentDesktop);
int nextPos = curPos + 1;
if (curPos == m_desktops.count()-1) {
nextPos = 0;
}
KWayland::Client::PlasmaVirtualDesktop *desktopObj = m_virtualDesktopManagement->getVirtualDesktop(m_desktops[nextPos]);
if (desktopObj) {
desktopObj->requestActivate();
}
#endif
}
void WaylandInterface::switchToPreviousVirtualDesktop() const
{
#if KF5_VERSION_MINOR >= 52
if (!m_virtualDesktopManagement || m_desktops.count() <= 1) {
return;
}
int curPos = m_desktops.indexOf(m_currentDesktop);
int nextPos = curPos - 1;
if (curPos == 0) {
nextPos = m_desktops.count()-1;
}
KWayland::Client::PlasmaVirtualDesktop *desktopObj = m_virtualDesktopManagement->getVirtualDesktop(m_desktops[nextPos]);
if (desktopObj) {
desktopObj->requestActivate();
}
#endif
}
void WaylandInterface::setWindowOnActivities(QWindow &window, const QStringList &activities)
{
//! needs to updated to wayland case
// KWindowSystem::setOnActivities(view.winId(), activities);
}
void WaylandInterface::removeViewStruts(QWindow &view) const
{
delete m_ghostWindows.take(view.winId());
}
WindowId WaylandInterface::activeWindow() const
{
if (!m_windowManagement) {
return 0;
}
auto wid = m_windowManagement->activeWindow();
return wid ? wid->internalId() : 0;
}
void WaylandInterface::setKeepAbove(const QDialog &dialog, bool above) const
{
if (above) {
KWindowSystem::setState(dialog.winId(), NET::KeepAbove);
} else {
KWindowSystem::clearState(dialog.winId(), NET::KeepAbove);
}
}
void WaylandInterface::skipTaskBar(const QDialog &dialog) const
{
KWindowSystem::setState(dialog.winId(), NET::SkipTaskbar);
}
void WaylandInterface::slideWindow(QWindow &view, AbstractWindowInterface::Slide location) const
{
auto slideLocation = KWindowEffects::NoEdge;
switch (location) {
case Slide::Top:
slideLocation = KWindowEffects::TopEdge;
break;
case Slide::Bottom:
slideLocation = KWindowEffects::BottomEdge;
break;
case Slide::Left:
slideLocation = KWindowEffects::LeftEdge;
break;
case Slide::Right:
slideLocation = KWindowEffects::RightEdge;
break;
default:
break;
}
KWindowEffects::slideWindow(view.winId(), slideLocation, -1);
}
void WaylandInterface::enableBlurBehind(QWindow &view) const
{
KWindowEffects::enableBlurBehind(view.winId());
}
void WaylandInterface::setEdgeStateFor(QWindow *view, bool active) const
{
ViewPart::ScreenEdgeGhostWindow *window = qobject_cast<ViewPart::ScreenEdgeGhostWindow *>(view);
if (!window) {
return;
}
if (window->parentView()->surface() && window->parentView()->visibility()
&& (window->parentView()->visibility()->mode() == Types::DodgeActive
|| window->parentView()->visibility()->mode() == Types::DodgeMaximized
|| window->parentView()->visibility()->mode() == Types::DodgeAllWindows
|| window->parentView()->visibility()->mode() == Types::AutoHide)) {
if (active) {
window->showWithMask();
window->surface()->requestHideAutoHidingPanel();
} else {
window->hideWithMask();
window->surface()->requestShowAutoHidingPanel();
}
}
}
WindowInfoWrap WaylandInterface::requestInfoActive() const
{
if (!m_windowManagement) {
return {};
}
auto w = m_windowManagement->activeWindow();
if (!w) return {};
return requestInfo(w->internalId());
}
WindowInfoWrap WaylandInterface::requestInfo(WindowId wid) const
{
WindowInfoWrap winfoWrap;
auto w = windowFor(wid);
if (w) {
if (isPlasmaDesktop(w)) {
winfoWrap.setIsValid(true);
winfoWrap.setIsPlasmaDesktop(true);
winfoWrap.setWid(wid);
} else if (isValidWindow(w)) {
winfoWrap.setIsValid(true);
winfoWrap.setWid(wid);
winfoWrap.setParentId(w->parentWindow() ? w->parentWindow()->internalId() : 0);
winfoWrap.setIsActive(w->isActive());
winfoWrap.setIsMinimized(w->isMinimized());
winfoWrap.setIsMaxVert(w->isMaximized());
winfoWrap.setIsMaxHoriz(w->isMaximized());
winfoWrap.setIsFullscreen(w->isFullscreen());
winfoWrap.setIsShaded(w->isShaded());
winfoWrap.setIsOnAllDesktops(w->isOnAllDesktops());
winfoWrap.setIsOnAllActivities(true);
winfoWrap.setGeometry(w->geometry());
winfoWrap.setHasSkipTaskbar(w->skipTaskbar());
winfoWrap.setDisplay(w->title());
#if KF5_VERSION_MINOR >= 52
winfoWrap.setDesktops(w->plasmaVirtualDesktops());
#endif
winfoWrap.setActivities(QStringList());
}
} else {
winfoWrap.setIsValid(false);
}
return winfoWrap;
}
AppData WaylandInterface::appDataFor(WindowId wid) const
{
auto window = windowFor(wid);
const AppData &data = appDataFromUrl(windowUrlFromMetadata(window->appId(),
window->pid(), rulesConfig));
return data;
}
KWayland::Client::PlasmaWindow *WaylandInterface::windowFor(WindowId wid) const
{
auto it = std::find_if(m_windowManagement->windows().constBegin(), m_windowManagement->windows().constEnd(), [&wid](PlasmaWindow * w) noexcept {
return w->isValid() && w->internalId() == wid;
});
if (it == m_windowManagement->windows().constEnd()) {
return nullptr;
}
return *it;
}
QIcon WaylandInterface::iconFor(WindowId wid) const
{
auto window = windowFor(wid);
if (window) {
return window->icon();
}
return QIcon();
}
WindowId WaylandInterface::winIdFor(QString appId, QRect geometry) const
{
auto it = std::find_if(m_windowManagement->windows().constBegin(), m_windowManagement->windows().constEnd(), [&appId, &geometry](PlasmaWindow * w) noexcept {
return w->isValid() && w->appId() == appId && w->geometry() == geometry;
});
if (it == m_windowManagement->windows().constEnd()) {
return QVariant();
}
return (*it)->internalId();
}
bool WaylandInterface::windowCanBeDragged(WindowId wid) const
{
auto w = windowFor(wid);
if (w && isValidWindow(w)) {
WindowInfoWrap winfo = requestInfo(wid);
return (winfo.isValid()
&& w->isMovable()
&& !winfo.isMinimized()
&& !winfo.isPlasmaDesktop());
}
return false;
}
bool WaylandInterface::windowCanBeMaximized(WindowId wid) const
{
auto w = windowFor(wid);
if (w && isValidWindow(w)) {
WindowInfoWrap winfo = requestInfo(wid);
return (winfo.isValid()
&& w->isMaximizeable()
&& !winfo.isMinimized()
&& !winfo.isPlasmaDesktop());
}
return false;
}
void WaylandInterface::requestActivate(WindowId wid) const
{
auto w = windowFor(wid);
if (w) {
w->requestActivate();
}
}
void WaylandInterface::requestClose(WindowId wid) const
{
auto w = windowFor(wid);
if (w) {
w->requestClose();
}
}
void WaylandInterface::requestMoveWindow(WindowId wid, QPoint from) const
{
if (windowCanBeDragged(wid)) {
auto w = windowFor(wid);
if (w && isValidWindow(w)) {
w->requestMove();
}
}
}
void WaylandInterface::requestToggleIsOnAllDesktops(WindowId wid) const
{
#if KF5_VERSION_MINOR >= 52
auto w = windowFor(wid);
if (w && isValidWindow(w) && m_desktops.count() > 1) {
if (w->isOnAllDesktops()) {
w->requestEnterVirtualDesktop(m_currentDesktop);
} else {
const QStringList &now = w->plasmaVirtualDesktops();
foreach (const QString &desktop, now) {
w->requestLeaveVirtualDesktop(desktop);
}
}
}
#endif
}
void WaylandInterface::requestToggleKeepAbove(WindowId wid) const
{
auto w = windowFor(wid);
if (w) {
w->requestToggleKeepAbove();
}
}
void WaylandInterface::requestToggleMinimized(WindowId wid) const
{
auto w = windowFor(wid);
if (w && isValidWindow(w)) {
#if KF5_VERSION_MINOR >= 52
if (!m_currentDesktop.isEmpty()) {
w->requestEnterVirtualDesktop(m_currentDesktop);
}
#endif
w->requestToggleMinimized();
}
}
void WaylandInterface::requestToggleMaximized(WindowId wid) const
{
auto w = windowFor(wid);
if (w && isValidWindow(w) && windowCanBeMaximized(wid)) {
#if KF5_VERSION_MINOR >= 52
if (!m_currentDesktop.isEmpty()) {
w->requestEnterVirtualDesktop(m_currentDesktop);
}
#endif
w->requestToggleMaximized();
}
}
bool WaylandInterface::isPlasmaDesktop(const KWayland::Client::PlasmaWindow *w) const
{
if (!w || (w->appId() != QLatin1String("org.kde.plasmashell"))) {
return false;
}
bool hasScreenGeometry{false};
for (const auto scr : qGuiApp->screens()) {
if (!w->geometry().isEmpty() && w->geometry() == scr->geometry()) {
hasScreenGeometry = true;
break;
}
}
return hasScreenGeometry;
}
bool WaylandInterface::isValidWindow(const KWayland::Client::PlasmaWindow *w) const
{
//! DEPRECATED comment is case we must reenable this
//! because wayland does not have any way yet to identify the window type
//! a trick is to just consider windows as valid when they can be shown in the
//! taskbar. Of course that creates issues with plasma native dialogs
//! e.g. widgets explorer, Activities etc. that are not used to hide
//! the dodge views appropriately
return w->isValid() && !isPlasmaDesktop(w) && !m_ignoredWindows.contains(w->internalId());
}
void WaylandInterface::windowCreatedProxy(KWayland::Client::PlasmaWindow *w)
{
if (!isValidWindow(w)) return;
if (!mapper) mapper = new QSignalMapper(this);
mapper->setMapping(w, w);
connect(w, &PlasmaWindow::unmapped, this, [ &, win = w]() noexcept {
mapper->removeMappings(win);
emit windowRemoved(win->internalId());
});
connect(w, SIGNAL(activeChanged()), mapper, SLOT(map()) );
connect(w, SIGNAL(titleChanged()), mapper, SLOT(map()) );
connect(w, SIGNAL(fullscreenChanged()), mapper, SLOT(map()) );
connect(w, SIGNAL(geometryChanged()), mapper, SLOT(map()) );
connect(w, SIGNAL(maximizedChanged()), mapper, SLOT(map()) );
connect(w, SIGNAL(minimizedChanged()), mapper, SLOT(map()) );
connect(w, SIGNAL(shadedChanged()), mapper, SLOT(map()) );
connect(w, SIGNAL(skipTaskbarChanged()), mapper, SLOT(map()) );
connect(w, SIGNAL(onAllDesktopsChanged()), mapper, SLOT(map()) );
connect(w, SIGNAL(virtualDesktopChanged()), mapper, SLOT(map()) );
connect(w, SIGNAL(parentWindowChanged()), mapper, SLOT(map()) );
#if KF5_VERSION_MINOR >= 52
connect(w, &KWayland::Client::PlasmaWindow::plasmaVirtualDesktopEntered, this,
[w, this] {
mapper->map(w);
});
connect(w, &KWayland::Client::PlasmaWindow::plasmaVirtualDesktopLeft, this,
[w, this] {
mapper->map(w);
});
#endif
connect(mapper, static_cast<void (QSignalMapper::*)(QObject *)>(&QSignalMapper::mapped)
, this, [&](QObject * w) noexcept {
//qDebug() << "window changed:" << qobject_cast<PlasmaWindow *>(w)->appId();
PlasmaWindow *pW = qobject_cast<PlasmaWindow*>(w);
if (pW && !m_ignoredWindows.contains(pW->internalId() && !isPlasmaDesktop(pW) )) {
considerWindowChanged(pW->internalId());
}
});
emit windowAdded(w->internalId());
if (w->appId() == "latte-dock") {
emit latteWindowAdded();
}
}
}
}
#include "waylandinterface.moc"