diff --git a/app/wm/waylandinterface.cpp b/app/wm/waylandinterface.cpp index 7a547a7b..b6b03b0c 100644 --- a/app/wm/waylandinterface.cpp +++ b/app/wm/waylandinterface.cpp @@ -1,695 +1,704 @@ /* * Copyright 2016 Smith AR * Michail Vourlakos * * 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 . */ #include "waylandinterface.h" // local #include "view/screenedgeghostwindow.h" #include "view/view.h" #include "../lattecorona.h" #include "../liblatte2/extras.h" // Qt #include #include #include #include #include #include // KDE #include #include #include #if KF5_VERSION_MINOR >= 52 #include #endif // X11 #include 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(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(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 { - WindowInfoWrap winfo = requestInfo(wid); - return (winfo.isValid() && !winfo.isMinimized() && !winfo.isPlasmaDesktop()); + auto w = windowFor(wid); + + if (w && isValidWindow(w)) { + WindowInfoWrap winfo = requestInfo(wid); + return (winfo.isValid() + && w->isMovable() + && !winfo.isMinimized() + && !winfo.isPlasmaDesktop()); + } + + return false; } void WaylandInterface::releaseMouseEventFor(WindowId wid) const { // this isnt really needed under wayland } 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)) { #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(&QSignalMapper::mapped) , this, [&](QObject * w) noexcept { //qDebug() << "window changed:" << qobject_cast(w)->appId(); PlasmaWindow *pW = qobject_cast(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" diff --git a/app/wm/xwindowinterface.cpp b/app/wm/xwindowinterface.cpp index 114e73a4..312dd2b9 100644 --- a/app/wm/xwindowinterface.cpp +++ b/app/wm/xwindowinterface.cpp @@ -1,675 +1,683 @@ /* * Copyright 2016 Smith AR * Michail Vourlakos * * 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 . */ #include "xwindowinterface.h" // local #include "tasktools.h" #include "view/screenedgeghostwindow.h" #include "view/view.h" #include "../liblatte2/extras.h" // Qt #include #include #include // KDE #include #include #include #include // X11 #include #include namespace Latte { namespace WindowSystem { XWindowInterface::XWindowInterface(QObject *parent) : AbstractWindowInterface(parent) { m_currentDesktop = QString(KWindowSystem::self()->currentDesktop()); connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &AbstractWindowInterface::activeWindowChanged); connect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, &AbstractWindowInterface::windowAdded); connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &AbstractWindowInterface::windowRemoved); connect(KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, this, [&](int desktop) { m_currentDesktop = QString(desktop); emit currentDesktopChanged(); }); connect(KWindowSystem::self() , static_cast (&KWindowSystem::windowChanged) , this, &XWindowInterface::windowChangedProxy); } XWindowInterface::~XWindowInterface() { } void XWindowInterface::setViewExtraFlags(QWindow &view) { NETWinInfo winfo(QX11Info::connection() , static_cast(view.winId()) , static_cast(view.winId()) , 0, 0); winfo.setAllowedActions(NET::ActionChangeDesktop); KWindowSystem::setType(view.winId(), NET::Dock); KWindowSystem::setState(view.winId(), NET::SkipTaskbar | NET::SkipPager); KWindowSystem::setOnAllDesktops(view.winId(), true); } void XWindowInterface::setViewStruts(QWindow &view, const QRect &rect , Plasma::Types::Location location) { NETExtendedStrut strut; const auto screen = view.screen(); const QRect currentScreen {screen->geometry()}; const QRect wholeScreen {{0, 0}, screen->virtualSize()}; switch (location) { case Plasma::Types::TopEdge: { const int topOffset {screen->geometry().top()}; strut.top_width = rect.height() + topOffset; strut.top_start = rect.x(); strut.top_end = rect.x() + rect.width() - 1; break; } case Plasma::Types::BottomEdge: { const int bottomOffset {wholeScreen.bottom() - currentScreen.bottom()}; strut.bottom_width = rect.height() + bottomOffset; strut.bottom_start = rect.x(); strut.bottom_end = rect.x() + rect.width() - 1; break; } case Plasma::Types::LeftEdge: { const int leftOffset = {screen->geometry().left()}; strut.left_width = rect.width() + leftOffset; strut.left_start = rect.y(); strut.left_end = rect.y() + rect.height() - 1; break; } case Plasma::Types::RightEdge: { const int rightOffset = {wholeScreen.right() - currentScreen.right()}; strut.right_width = rect.width() + rightOffset; strut.right_start = rect.y(); strut.right_end = rect.y() + rect.height() - 1; break; } default: qWarning() << "wrong location:" << qEnumToStr(location); return; } KWindowSystem::setExtendedStrut(view.winId(), strut.left_width, strut.left_start, strut.left_end, strut.right_width, strut.right_start, strut.right_end, strut.top_width, strut.top_start, strut.top_end, strut.bottom_width, strut.bottom_start, strut.bottom_end ); } void XWindowInterface::switchToNextVirtualDesktop() const { int desktops = KWindowSystem::numberOfDesktops(); if (desktops <= 1) { return; } int curPos = KWindowSystem::currentDesktop(); int nextPos = curPos + 1; if (curPos == desktops) { nextPos = 1; } KWindowSystem::setCurrentDesktop(nextPos); } void XWindowInterface::switchToPreviousVirtualDesktop() const { int desktops = KWindowSystem::numberOfDesktops(); if (desktops <= 1) { return; } int curPos = KWindowSystem::currentDesktop(); int nextPos = curPos - 1; if (curPos == 1) { nextPos = desktops; } KWindowSystem::setCurrentDesktop(nextPos); } void XWindowInterface::setWindowOnActivities(QWindow &window, const QStringList &activities) { KWindowSystem::setOnActivities(window.winId(), activities); } void XWindowInterface::removeViewStruts(QWindow &view) const { KWindowSystem::setStrut(view.winId(), 0, 0, 0, 0); } WindowId XWindowInterface::activeWindow() const { return KWindowSystem::self()->activeWindow(); } void XWindowInterface::setKeepAbove(const QDialog &dialog, bool above) const { if (above) { KWindowSystem::setState(dialog.winId(), NET::KeepAbove); } else { KWindowSystem::clearState(dialog.winId(), NET::KeepAbove); } } void XWindowInterface::skipTaskBar(const QDialog &dialog) const { KWindowSystem::setState(dialog.winId(), NET::SkipTaskbar); } void XWindowInterface::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 XWindowInterface::enableBlurBehind(QWindow &view) const { KWindowEffects::enableBlurBehind(view.winId()); } void XWindowInterface::setEdgeStateFor(QWindow *view, bool active) const { ViewPart::ScreenEdgeGhostWindow *window = qobject_cast(view); if (!window) { return; } xcb_connection_t *c = QX11Info::connection(); const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW"); xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData()); QScopedPointer atom(xcb_intern_atom_reply(c, atomCookie, nullptr)); if (!atom) { return; } if (!active) { xcb_delete_property(c, window->winId(), atom->atom); window->hideWithMask(); return; } window->showWithMask(); uint32_t value = 0; switch (window->location()) { case Plasma::Types::TopEdge: value = 0; break; case Plasma::Types::RightEdge: value = 1; break; case Plasma::Types::BottomEdge: value = 2; break; case Plasma::Types::LeftEdge: value = 3; break; case Plasma::Types::Floating: default: value = 4; break; } int hideType = 0; value |= hideType << 8; xcb_change_property(c, XCB_PROP_MODE_REPLACE, window->winId(), atom->atom, XCB_ATOM_CARDINAL, 32, 1, &value); } WindowInfoWrap XWindowInterface::requestInfoActive() const { return requestInfo(KWindowSystem::activeWindow()); } WindowInfoWrap XWindowInterface::requestInfo(WindowId wid) const { const KWindowInfo winfo{wid.value(), NET::WMFrameExtents | NET::WMWindowType | NET::WMGeometry | NET::WMDesktop | NET::WMState | NET::WMName | NET::WMVisibleName, NET::WM2WindowClass | NET::WM2Activities | NET::WM2TransientFor}; //! update desktop id bool isPlasmaDesktop{false}; if (winfo.windowClassName() == "plasmashell" && hasScreenGeometry(winfo)) { isPlasmaDesktop = true; windowsTracker()->setPlasmaDesktop(wid); } WindowInfoWrap winfoWrap; if (!winfo.valid()) { winfoWrap.setIsValid(false); } else if (isValidWindow(winfo) && !isPlasmaDesktop) { winfoWrap.setIsValid(true); winfoWrap.setWid(wid); winfoWrap.setParentId(winfo.transientFor()); winfoWrap.setIsActive(KWindowSystem::activeWindow() == wid.value()); winfoWrap.setIsMinimized(winfo.hasState(NET::Hidden)); winfoWrap.setIsMaxVert(winfo.hasState(NET::MaxVert)); winfoWrap.setIsMaxHoriz(winfo.hasState(NET::MaxHoriz)); winfoWrap.setIsFullscreen(winfo.hasState(NET::FullScreen)); winfoWrap.setIsShaded(winfo.hasState(NET::Shaded)); winfoWrap.setIsOnAllDesktops(winfo.onAllDesktops()); winfoWrap.setIsOnAllActivities(winfo.activities().empty()); winfoWrap.setGeometry(winfo.frameGeometry()); winfoWrap.setIsKeepAbove(winfo.hasState(NET::KeepAbove)); winfoWrap.setHasSkipTaskbar(winfo.hasState(NET::SkipTaskbar)); winfoWrap.setDisplay(winfo.visibleName()); winfoWrap.setDesktops({QString(winfo.desktop())}); winfoWrap.setActivities(winfo.activities()); } else if (m_desktopId == wid) { winfoWrap.setIsValid(true); winfoWrap.setIsPlasmaDesktop(true); winfoWrap.setWid(wid); winfoWrap.setParentId(0); winfoWrap.setHasSkipTaskbar(true); } return winfoWrap; } AppData XWindowInterface::appDataFor(WindowId wid) const { return appDataFromUrl(windowUrl(wid)); } QUrl XWindowInterface::windowUrl(WindowId wid) const { const KWindowInfo info(wid.value(), 0, NET::WM2WindowClass | NET::WM2DesktopFileName); QString desktopFile = QString::fromUtf8(info.desktopFileName()); if (!desktopFile.isEmpty()) { KService::Ptr service = KService::serviceByStorageId(desktopFile); if (service) { const QString &menuId = service->menuId(); // applications: URLs are used to refer to applications by their KService::menuId // (i.e. .desktop file name) rather than the absolute path to a .desktop file. if (!menuId.isEmpty()) { return QUrl(QStringLiteral("applications:") + menuId); } return QUrl::fromLocalFile(service->entryPath()); } if (!desktopFile.endsWith(QLatin1String(".desktop"))) { desktopFile.append(QLatin1String(".desktop")); } if (KDesktopFile::isDesktopFile(desktopFile) && QFile::exists(desktopFile)) { return QUrl::fromLocalFile(desktopFile); } } return windowUrlFromMetadata(info.windowClassClass(), NETWinInfo(QX11Info::connection(), wid.value(), QX11Info::appRootWindow(), NET::WMPid, NET::Properties2()).pid(), rulesConfig, info.windowClassName()); } bool XWindowInterface::windowCanBeDragged(WindowId wid) const { - WindowInfoWrap winfo = requestInfo(wid); - return (winfo.isValid() && !winfo.isMinimized() && !winfo.isPlasmaDesktop()); + KWindowInfo info(wid.value(), 0, NET::WM2AllowedActions); + if (info.valid()) { + WindowInfoWrap winfo = requestInfo(wid); + return (winfo.isValid() + && info.actionSupported(NET::ActionMove) + && !winfo.isMinimized() + && !winfo.isPlasmaDesktop()); + } + + return false; } void XWindowInterface::releaseMouseEventFor(WindowId wid) const { auto connection = QX11Info::connection(); xcb_button_release_event_t releaseEvent; memset(&releaseEvent, 0, sizeof(releaseEvent)); releaseEvent.response_type = XCB_BUTTON_RELEASE; releaseEvent.event = wid.toInt(); releaseEvent.child = XCB_WINDOW_NONE; releaseEvent.root = QX11Info::appRootWindow(); releaseEvent.event_x = -1; releaseEvent.event_y = -1; releaseEvent.root_x = -1; releaseEvent.root_y = -1; releaseEvent.detail = XCB_BUTTON_INDEX_1; releaseEvent.state = XCB_BUTTON_MASK_1; releaseEvent.time = XCB_CURRENT_TIME; releaseEvent.same_screen = true; xcb_send_event( connection, false, wid.toInt(), XCB_EVENT_MASK_BUTTON_RELEASE, reinterpret_cast(&releaseEvent)); } void XWindowInterface::requestActivate(WindowId wid) const { KWindowSystem::activateWindow(wid.toInt()); } QIcon XWindowInterface::iconFor(WindowId wid) const { QIcon icon; icon.addPixmap(KWindowSystem::icon(wid.value(), KIconLoader::SizeSmall, KIconLoader::SizeSmall, false)); icon.addPixmap(KWindowSystem::icon(wid.value(), KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium, false)); icon.addPixmap(KWindowSystem::icon(wid.value(), KIconLoader::SizeMedium, KIconLoader::SizeMedium, false)); icon.addPixmap(KWindowSystem::icon(wid.value(), KIconLoader::SizeLarge, KIconLoader::SizeLarge, false)); return icon; } WindowId XWindowInterface::winIdFor(QString appId, QRect geometry) const { return activeWindow(); } void XWindowInterface::requestClose(WindowId wid) const { WindowInfoWrap wInfo = requestInfo(wid); if (!wInfo.isValid() || wInfo.isPlasmaDesktop()) { return; } NETRootInfo ri(QX11Info::connection(), NET::CloseWindow); ri.closeWindowRequest(wInfo.wid().toUInt()); } void XWindowInterface::requestMoveWindow(WindowId wid, QPoint from) const { WindowInfoWrap wInfo = requestInfo(wid); if (!wInfo.isValid() || wInfo.isPlasmaDesktop()) { return; } int borderX{wInfo.geometry().width() > 120 ? 60 : 10}; int borderY{10}; //! find min/max values for x,y based on active window geometry int minX = wInfo.geometry().x() + borderX; int maxX = wInfo.geometry().x() + wInfo.geometry().width() - borderX; int minY = wInfo.geometry().y() + borderY; int maxY = wInfo.geometry().y() + wInfo.geometry().height() - borderY; //! set the point from which this window will be moved, //! make sure that it is in window boundaries int validX = qBound(minX, from.x(), maxX); int validY = qBound(minY, from.y(), maxY); NETRootInfo ri(QX11Info::connection(), NET::WMMoveResize); ri.moveResizeRequest(wInfo.wid().toUInt(), validX, validY, NET::Move); } void XWindowInterface::requestToggleIsOnAllDesktops(WindowId wid) const { WindowInfoWrap wInfo = requestInfo(wid); if (!wInfo.isValid() || wInfo.isPlasmaDesktop()) { return; } if (KWindowSystem::numberOfDesktops() <= 1) { return; } if (wInfo.isOnAllDesktops()) { KWindowSystem::setOnDesktop(wid.toUInt(), KWindowSystem::currentDesktop()); KWindowSystem::forceActiveWindow(wid.toUInt()); } else { KWindowSystem::setOnAllDesktops(wid.toUInt(), true); } } void XWindowInterface::requestToggleKeepAbove(WindowId wid) const { WindowInfoWrap wInfo = requestInfo(wid); if (!wInfo.isValid() || wInfo.isPlasmaDesktop()) { return; } NETWinInfo ni(QX11Info::connection(), wid.toUInt(), QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); if (wInfo.isKeepAbove()) { ni.setState(NET::States(), NET::StaysOnTop); } else { ni.setState(NET::StaysOnTop, NET::StaysOnTop); } } void XWindowInterface::requestToggleMinimized(WindowId wid) const { WindowInfoWrap wInfo = requestInfo(wid); if (!wInfo.isValid() || wInfo.isPlasmaDesktop()) { return; } if (wInfo.isMinimized()) { bool onCurrent = wInfo.isOnDesktop(m_currentDesktop); KWindowSystem::unminimizeWindow(wid.toUInt()); if (onCurrent) { KWindowSystem::forceActiveWindow(wid.toUInt()); } } else { KWindowSystem::minimizeWindow(wid.toUInt()); } } void XWindowInterface::requestToggleMaximized(WindowId wid) const { WindowInfoWrap wInfo = requestInfo(wid); bool restore = wInfo.isMaxHoriz() && wInfo.isMaxVert(); if (wInfo.isMinimized()) { KWindowSystem::unminimizeWindow(wid.toUInt()); } NETWinInfo ni(QX11Info::connection(), wid.toInt(), QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); if (restore) { ni.setState(NET::States(), NET::Max); } else { ni.setState(NET::Max, NET::Max); } } bool XWindowInterface::isValidWindow(WindowId wid) const { if (windowsTracker()->isValidFor(wid)) { return true; } const KWindowInfo winfo{wid.value(), NET::WMWindowType}; return isValidWindow(winfo); } bool XWindowInterface::isValidWindow(const KWindowInfo &winfo) const { if (windowsTracker()->isValidFor(winfo.win())) { return true; } constexpr auto types = NET::DockMask | NET::MenuMask | NET::SplashMask | NET::PopupMenuMask | NET::NormalMask | NET::DialogMask; NET::WindowType winType = winfo.windowType(types); const auto winClass = KWindowInfo(winfo.win(), 0, NET::WM2WindowClass).windowClassName(); //! ignored windows from tracking if (m_ignoredWindows.contains(winfo.win())) { return false; } if (m_desktopId == winfo.win()) { return false; } if (winType == -1) { // Trying to get more types for verify if the window have any other type winType = winfo.windowType(~types & NET::AllTypesMask); if (winType == -1) { qWarning() << KWindowInfo(winfo.win(), 0, NET::WM2WindowClass).windowClassName() << "doesn't have any WindowType, assuming as NET::Normal"; return true; } } bool isMenu = ((winType & NET::Menu) == true); bool isDock = ((winType & NET::Dock) == true); bool isPopup = ((winType & NET::PopupMenu) == true); bool isSplash = ((winType & NET::Splash) == true); //! GTK2+ dialogs case e.g. inkscape, gimp2, etc... //! are both Popups and Splash types, this is why //! we can not black list them here return !(isMenu || isDock); } bool XWindowInterface::hasScreenGeometry(const KWindowInfo &winfo) const { bool hasScreenGeometry{false}; for (const auto scr : qGuiApp->screens()) { if (!winfo.geometry().isEmpty() && winfo.geometry() == scr->geometry()) { hasScreenGeometry = true; break; } } return hasScreenGeometry; } void XWindowInterface::windowChangedProxy(WId wid, NET::Properties prop1, NET::Properties2 prop2) { const KWindowInfo info(wid, NET::WMGeometry, NET::WM2WindowClass); const auto winClass = info.windowClassName(); //! ignored windows do not trackd if (m_ignoredWindows.contains(wid)) { return; } //! update desktop id if (winClass == "plasmashell" && hasScreenGeometry(info)) { m_desktopId = wid; windowsTracker()->setPlasmaDesktop(wid); considerWindowChanged(wid); return; } //! accept only NET::Properties events, //! ignore when the user presses a key, or a window is sending X events etc. //! without needing to (e.g. Firefox, https://bugzilla.mozilla.org/show_bug.cgi?id=1389953) //! NET::WM2UserTime, NET::WM2IconPixmap etc.... if (prop1 == 0 && !(prop2 & (NET::WM2Activities | NET::WM2TransientFor))) { return; } //! accept only the following NET:Properties changed signals //! NET::WMState, NET::WMGeometry, NET::ActiveWindow if ( !(prop1 & NET::WMState) && !(prop1 & NET::WMGeometry) && !(prop1 & NET::ActiveWindow) && !(prop1 & NET::WMDesktop) && !(prop1 & (NET::WMName | NET::WMVisibleName) && !(prop2 & NET::WM2TransientFor) && !(prop2 & NET::WM2Activities)) ) { return; } //! ignore windows that do not respect normal windows types if (!isValidWindow(wid)) { return; } considerWindowChanged(wid); } } }