From f34b6911d2aff50d6d4b7499cb26d567653c97bb Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 9 Jul 2024 12:45:35 +0200 Subject: [PATCH 01/15] Initial backend plugin infrastructure --- panel/CMakeLists.txt | 17 +- panel/backends/CMakeLists.txt | 17 ++ ...ctbackend.cpp => ilxqtabstractwmiface.cpp} | 6 +- ...stractbackend.h => ilxqtabstractwmiface.h} | 35 +++- panel/backends/lxqtdummywmbackend.cpp | 170 +++++++++++++++++ ...bardummybackend.h => lxqtdummywmbackend.h} | 12 +- panel/backends/lxqttaskbardummybackend.cpp | 171 ------------------ panel/backends/xcb/CMakeLists.txt | 20 ++ ...rbackend_x11.cpp => lxqtwmbackend_x11.cpp} | 103 ++++++----- ...skbarbackend_x11.h => lxqtwmbackend_x11.h} | 24 ++- panel/lxqtpanel.cpp | 10 +- panel/lxqtpanelapplication.cpp | 113 ++++++++++-- panel/lxqtpanelapplication.h | 4 +- panel/lxqtpanelapplication_p.h | 6 +- plugin-desktopswitch/desktopswitch.cpp | 10 +- plugin-desktopswitch/desktopswitch.h | 4 +- .../desktopswitchconfiguration.cpp | 2 +- plugin-showdesktop/showdesktop.cpp | 2 +- plugin-taskbar/lxqttaskbar.cpp | 8 +- plugin-taskbar/lxqttaskbar.h | 6 +- plugin-taskbar/lxqttaskbarconfiguration.cpp | 2 +- plugin-taskbar/lxqttaskbarproxymodel.cpp | 18 +- plugin-taskbar/lxqttaskbarproxymodel.h | 8 +- plugin-taskbar/lxqttaskbutton.cpp | 2 +- plugin-taskbar/lxqttaskbutton.h | 4 +- plugin-taskbar/lxqttaskgroup.cpp | 6 +- 26 files changed, 470 insertions(+), 310 deletions(-) rename panel/backends/{ilxqttaskbarabstractbackend.cpp => ilxqtabstractwmiface.cpp} (64%) rename panel/backends/{ilxqttaskbarabstractbackend.h => ilxqtabstractwmiface.h} (76%) create mode 100644 panel/backends/lxqtdummywmbackend.cpp rename panel/backends/{lxqttaskbardummybackend.h => lxqtdummywmbackend.h} (89%) delete mode 100644 panel/backends/lxqttaskbardummybackend.cpp rename panel/backends/xcb/{lxqttaskbarbackend_x11.cpp => lxqtwmbackend_x11.cpp} (84%) rename panel/backends/xcb/{lxqttaskbarbackend_x11.h => lxqtwmbackend_x11.h} (83%) diff --git a/panel/CMakeLists.txt b/panel/CMakeLists.txt index 074c62af3..b9ba8f10f 100644 --- a/panel/CMakeLists.txt +++ b/panel/CMakeLists.txt @@ -1,6 +1,6 @@ set(PROJECT lxqt-panel) -# TODO +# Window Manager backends add_subdirectory(backends) set(PRIV_HEADERS @@ -21,12 +21,6 @@ set(PRIV_HEADERS config/configstyling.h config/configpluginswidget.h config/addplugindialog.h - - backends/ilxqttaskbarabstractbackend.h - backends/lxqttaskbartypes.h - - backends/lxqttaskbardummybackend.h - backends/xcb/lxqttaskbarbackend_x11.h ) # using LXQt namespace in the public headers. @@ -35,9 +29,6 @@ set(PUB_HEADERS pluginsettings.h ilxqtpanelplugin.h ilxqtpanel.h - - backends/ilxqttaskbarabstractbackend.h - backends/lxqttaskbartypes.h ) set(SOURCES @@ -57,11 +48,6 @@ set(SOURCES config/configstyling.cpp config/configpluginswidget.cpp config/addplugindialog.cpp - - backends/ilxqttaskbarabstractbackend.cpp - - backends/lxqttaskbardummybackend.cpp - backends/xcb/lxqttaskbarbackend_x11.cpp ) set(UI @@ -122,6 +108,7 @@ target_link_libraries(${PROJECT} KF6::WindowSystem LayerShellQt::Interface ${STATIC_PLUGINS} + lxqt-panel-backend-common ) set_property(TARGET ${PROJECT} PROPERTY ENABLE_EXPORTS TRUE) diff --git a/panel/backends/CMakeLists.txt b/panel/backends/CMakeLists.txt index 8f34a3c67..cf117c7e3 100644 --- a/panel/backends/CMakeLists.txt +++ b/panel/backends/CMakeLists.txt @@ -1 +1,18 @@ +# Common interface for Window Manager abstraction backend +# This also contains dummy backend + +add_library(lxqt-panel-backend-common STATIC + + lxqttaskbartypes.h + ilxqtabstractwmiface.h + ilxqtabstractwmiface.cpp + + lxqtdummywmbackend.h + lxqtdummywmbackend.cpp +) + +target_link_libraries(lxqt-panel-backend-common + Qt6::Gui +) + add_subdirectory(xcb) diff --git a/panel/backends/ilxqttaskbarabstractbackend.cpp b/panel/backends/ilxqtabstractwmiface.cpp similarity index 64% rename from panel/backends/ilxqttaskbarabstractbackend.cpp rename to panel/backends/ilxqtabstractwmiface.cpp index 137728263..080b07324 100644 --- a/panel/backends/ilxqttaskbarabstractbackend.cpp +++ b/panel/backends/ilxqtabstractwmiface.cpp @@ -1,13 +1,13 @@ -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "ilxqtabstractwmiface.h" -ILXQtTaskbarAbstractBackend::ILXQtTaskbarAbstractBackend(QObject *parent) +ILXQtAbstractWMInterface::ILXQtAbstractWMInterface(QObject *parent) : QObject(parent) { } -void ILXQtTaskbarAbstractBackend::moveApplicationToPrevNextDesktop(WId windowId, bool next) +void ILXQtAbstractWMInterface::moveApplicationToPrevNextDesktop(WId windowId, bool next) { int count = getWorkspacesCount(); if (count <= 1) diff --git a/panel/backends/ilxqttaskbarabstractbackend.h b/panel/backends/ilxqtabstractwmiface.h similarity index 76% rename from panel/backends/ilxqttaskbarabstractbackend.h rename to panel/backends/ilxqtabstractwmiface.h index 44840671a..4bd4c6561 100644 --- a/panel/backends/ilxqttaskbarabstractbackend.h +++ b/panel/backends/ilxqtabstractwmiface.h @@ -1,19 +1,20 @@ -#ifndef ILXQTTASKBARABSTRACTBACKEND_H -#define ILXQTTASKBARABSTRACTBACKEND_H +#ifndef ILXQT_ABSTRACT_WM_INTERFACE_H +#define ILXQT_ABSTRACT_WM_INTERFACE_H #include +#include "../lxqtpanelglobals.h" #include "lxqttaskbartypes.h" class QIcon; class QScreen; -class ILXQtTaskbarAbstractBackend : public QObject +class LXQT_PANEL_API ILXQtAbstractWMInterface : public QObject { Q_OBJECT public: - explicit ILXQtTaskbarAbstractBackend(QObject *parent = nullptr); + explicit ILXQtAbstractWMInterface(QObject *parent = nullptr); // Backend virtual bool supportsAction(WId windowId, LXQtTaskBarBackendAction action) const = 0; @@ -96,4 +97,28 @@ class ILXQtTaskbarAbstractBackend : public QObject void activeWindowChanged(WId windowId); }; -#endif // ILXQTTASKBARABSTRACTBACKEND_H +class LXQT_PANEL_API ILXQtWMBackendLibrary +{ +public: + /** + Destroys the ILXQtWMBackendLibrary object. + **/ + virtual ~ILXQtWMBackendLibrary() {} + + /** + Returns the score of this backend for current detected environment. + This is used to select correct backend at runtime + **/ + virtual int getBackendScore() const = 0; + + /** + Returns the root component object of the backend. When the library is finally unloaded, the root component will automatically be deleted. + **/ + virtual ILXQtAbstractWMInterface* instance() const = 0; +}; + + +Q_DECLARE_INTERFACE(ILXQtWMBackendLibrary, + "lxqt.org/Panel/WMInterface/1.0") + +#endif // ILXQT_ABSTRACT_WM_INTERFACE_H diff --git a/panel/backends/lxqtdummywmbackend.cpp b/panel/backends/lxqtdummywmbackend.cpp new file mode 100644 index 000000000..9d8e2f5e1 --- /dev/null +++ b/panel/backends/lxqtdummywmbackend.cpp @@ -0,0 +1,170 @@ +#include "lxqtdummywmbackend.h" + +#include + +LXQtDummyWMBackend::LXQtDummyWMBackend(QObject *parent) + : ILXQtAbstractWMInterface(parent) +{ + +} + +/************************************************ + * Windows function + ************************************************/ +bool LXQtDummyWMBackend::supportsAction(WId, LXQtTaskBarBackendAction) const +{ + return false; +} + +bool LXQtDummyWMBackend::reloadWindows() +{ + return false; +} + +QVector LXQtDummyWMBackend::getCurrentWindows() const +{ + return {}; +} + +QString LXQtDummyWMBackend::getWindowTitle(WId) const +{ + return QString(); +} + +bool LXQtDummyWMBackend::applicationDemandsAttention(WId) const +{ + return false; +} + +QIcon LXQtDummyWMBackend::getApplicationIcon(WId, int) const +{ + return QIcon(); +} + +QString LXQtDummyWMBackend::getWindowClass(WId) const +{ + return QString(); +} + +LXQtTaskBarWindowLayer LXQtDummyWMBackend::getWindowLayer(WId) const +{ + return LXQtTaskBarWindowLayer::Normal; +} + +bool LXQtDummyWMBackend::setWindowLayer(WId, LXQtTaskBarWindowLayer) +{ + return false; +} + +LXQtTaskBarWindowState LXQtDummyWMBackend::getWindowState(WId) const +{ + return LXQtTaskBarWindowState::Normal; +} + +bool LXQtDummyWMBackend::setWindowState(WId, LXQtTaskBarWindowState, bool) +{ + return false; +} + +bool LXQtDummyWMBackend::isWindowActive(WId) const +{ + return false; +} + +bool LXQtDummyWMBackend::raiseWindow(WId, bool) +{ + return false; +} + +bool LXQtDummyWMBackend::closeWindow(WId) +{ + return false; +} + +WId LXQtDummyWMBackend::getActiveWindow() const +{ + return 0; +} + + +/************************************************ + * Workspaces + ************************************************/ +int LXQtDummyWMBackend::getWorkspacesCount() const +{ + return 1; // Fake 1 workspace +} + +QString LXQtDummyWMBackend::getWorkspaceName(int) const +{ + return QString(); +} + +int LXQtDummyWMBackend::getCurrentWorkspace() const +{ + return 0; +} + +bool LXQtDummyWMBackend::setCurrentWorkspace(int) +{ + return false; +} + +int LXQtDummyWMBackend::getWindowWorkspace(WId) const +{ + return 0; +} + +bool LXQtDummyWMBackend::setWindowOnWorkspace(WId, int) +{ + return false; +} + +void LXQtDummyWMBackend::moveApplicationToPrevNextMonitor(WId, bool, bool) +{ + //No-op +} + +bool LXQtDummyWMBackend::isWindowOnScreen(QScreen *, WId) const +{ + return false; +} + +bool LXQtDummyWMBackend::setDesktopLayout(Qt::Orientation, int, int, bool) +{ + return false; +} + +/************************************************ + * X11 Specific + ************************************************/ +void LXQtDummyWMBackend::moveApplication(WId) +{ + //No-op +} + +void LXQtDummyWMBackend::resizeApplication(WId) +{ + //No-op +} + +void LXQtDummyWMBackend::refreshIconGeometry(WId, QRect const &) +{ + //No-op +} + +bool LXQtDummyWMBackend::isAreaOverlapped(const QRect &) const +{ + return false; +} + +bool LXQtDummyWMBackend::isShowingDesktop() const +{ + return false; +} + +bool LXQtDummyWMBackend::showDesktop(bool) +{ + return false; +} + diff --git a/panel/backends/lxqttaskbardummybackend.h b/panel/backends/lxqtdummywmbackend.h similarity index 89% rename from panel/backends/lxqttaskbardummybackend.h rename to panel/backends/lxqtdummywmbackend.h index 15506838f..75a9b04b7 100644 --- a/panel/backends/lxqttaskbardummybackend.h +++ b/panel/backends/lxqtdummywmbackend.h @@ -1,14 +1,14 @@ -#ifndef LXQTTASKBARDUMMYBACKEND_H -#define LXQTTASKBARDUMMYBACKEND_H +#ifndef LXQT_DUMMY_WM_BACKEND_H +#define LXQT_DUMMY_WM_BACKEND_H -#include "ilxqttaskbarabstractbackend.h" +#include "ilxqtabstractwmiface.h" -class LXQtTaskBarDummyBackend : public ILXQtTaskbarAbstractBackend +class LXQtDummyWMBackend : public ILXQtAbstractWMInterface { Q_OBJECT public: - explicit LXQtTaskBarDummyBackend(QObject *parent = nullptr); + explicit LXQtDummyWMBackend(QObject *parent = nullptr); // Backend bool supportsAction(WId windowId, LXQtTaskBarBackendAction action) const override; @@ -85,4 +85,4 @@ class LXQtTaskBarDummyBackend : public ILXQtTaskbarAbstractBackend void activeWindowChanged(WId windowId); }; -#endif // LXQTTASKBARDUMMYBACKEND_H +#endif // LXQT_DUMMY_WM_BACKEND_H diff --git a/panel/backends/lxqttaskbardummybackend.cpp b/panel/backends/lxqttaskbardummybackend.cpp deleted file mode 100644 index 15e7e1149..000000000 --- a/panel/backends/lxqttaskbardummybackend.cpp +++ /dev/null @@ -1,171 +0,0 @@ -#include "lxqttaskbardummybackend.h" - -#include - -LXQtTaskBarDummyBackend::LXQtTaskBarDummyBackend(QObject *parent) - : ILXQtTaskbarAbstractBackend(parent) -{ - -} - - -/************************************************ - * Windows function - ************************************************/ -bool LXQtTaskBarDummyBackend::supportsAction(WId, LXQtTaskBarBackendAction) const -{ - return false; -} - -bool LXQtTaskBarDummyBackend::reloadWindows() -{ - return false; -} - -QVector LXQtTaskBarDummyBackend::getCurrentWindows() const -{ - return {}; -} - -QString LXQtTaskBarDummyBackend::getWindowTitle(WId) const -{ - return QString(); -} - -bool LXQtTaskBarDummyBackend::applicationDemandsAttention(WId) const -{ - return false; -} - -QIcon LXQtTaskBarDummyBackend::getApplicationIcon(WId, int) const -{ - return QIcon(); -} - -QString LXQtTaskBarDummyBackend::getWindowClass(WId) const -{ - return QString(); -} - -LXQtTaskBarWindowLayer LXQtTaskBarDummyBackend::getWindowLayer(WId) const -{ - return LXQtTaskBarWindowLayer::Normal; -} - -bool LXQtTaskBarDummyBackend::setWindowLayer(WId, LXQtTaskBarWindowLayer) -{ - return false; -} - -LXQtTaskBarWindowState LXQtTaskBarDummyBackend::getWindowState(WId) const -{ - return LXQtTaskBarWindowState::Normal; -} - -bool LXQtTaskBarDummyBackend::setWindowState(WId, LXQtTaskBarWindowState, bool) -{ - return false; -} - -bool LXQtTaskBarDummyBackend::isWindowActive(WId) const -{ - return false; -} - -bool LXQtTaskBarDummyBackend::raiseWindow(WId, bool) -{ - return false; -} - -bool LXQtTaskBarDummyBackend::closeWindow(WId) -{ - return false; -} - -WId LXQtTaskBarDummyBackend::getActiveWindow() const -{ - return 0; -} - - -/************************************************ - * Workspaces - ************************************************/ -int LXQtTaskBarDummyBackend::getWorkspacesCount() const -{ - return 1; // Fake 1 workspace -} - -QString LXQtTaskBarDummyBackend::getWorkspaceName(int) const -{ - return QString(); -} - -int LXQtTaskBarDummyBackend::getCurrentWorkspace() const -{ - return 0; -} - -bool LXQtTaskBarDummyBackend::setCurrentWorkspace(int) -{ - return false; -} - -int LXQtTaskBarDummyBackend::getWindowWorkspace(WId) const -{ - return 0; -} - -bool LXQtTaskBarDummyBackend::setWindowOnWorkspace(WId, int) -{ - return false; -} - -void LXQtTaskBarDummyBackend::moveApplicationToPrevNextMonitor(WId, bool, bool) -{ - //No-op -} - -bool LXQtTaskBarDummyBackend::isWindowOnScreen(QScreen *, WId) const -{ - return false; -} - -bool LXQtTaskBarDummyBackend::setDesktopLayout(Qt::Orientation, int, int, bool) -{ - return false; -} - -/************************************************ - * X11 Specific - ************************************************/ -void LXQtTaskBarDummyBackend::moveApplication(WId) -{ - //No-op -} - -void LXQtTaskBarDummyBackend::resizeApplication(WId) -{ - //No-op -} - -void LXQtTaskBarDummyBackend::refreshIconGeometry(WId, QRect const &) -{ - //No-op -} - -bool LXQtTaskBarDummyBackend::isAreaOverlapped(const QRect &) const -{ - return false; -} - -bool LXQtTaskBarDummyBackend::isShowingDesktop() const -{ - return false; -} - -bool LXQtTaskBarDummyBackend::showDesktop(bool) -{ - return false; -} - diff --git a/panel/backends/xcb/CMakeLists.txt b/panel/backends/xcb/CMakeLists.txt index 8b1378917..08f2fe4b1 100644 --- a/panel/backends/xcb/CMakeLists.txt +++ b/panel/backends/xcb/CMakeLists.txt @@ -1 +1,21 @@ +set(NAME xcb_backend) +set(PROGRAM "lxqt-panel") +set(BACKEND "backend") +project(${PROGRAM}_${BACKEND}_${NAME}) +set(PROG_SHARE_DIR ${CMAKE_INSTALL_FULL_DATAROOTDIR}/lxqt/${PROGRAM}/${BACKEND}) +set(PLUGIN_SHARE_DIR ${PROG_SHARE_DIR}/${BACKEND}/${NAME}) +#************************************************ + +if (NOT DEFINED PLUGIN_DIR) + set (PLUGIN_DIR ${CMAKE_INSTALL_FULL_LIBDIR}/${PROGRAM}) +endif (NOT DEFINED PLUGIN_DIR) + +set(QTX_LIBRARIES Qt6::Gui) + +set(SRC lxqtwmbackend_x11.h lxqtwmbackend_x11.cpp) + +add_library(${NAME} MODULE ${SRC}) # build dynamically loadable modules +install(TARGETS ${NAME} DESTINATION ${PLUGIN_DIR}/${BACKEND}) # install the *.so file + +target_link_libraries(${NAME} ${QTX_LIBRARIES} KF6::WindowSystem) diff --git a/panel/backends/xcb/lxqttaskbarbackend_x11.cpp b/panel/backends/xcb/lxqtwmbackend_x11.cpp similarity index 84% rename from panel/backends/xcb/lxqttaskbarbackend_x11.cpp rename to panel/backends/xcb/lxqtwmbackend_x11.cpp index fe0452776..40fd06a81 100644 --- a/panel/backends/xcb/lxqttaskbarbackend_x11.cpp +++ b/panel/backends/xcb/lxqtwmbackend_x11.cpp @@ -1,4 +1,4 @@ -#include "lxqttaskbarbackend_x11.h" +#include "lxqtwmbackend_x11.h" #include #include @@ -16,28 +16,28 @@ #include #undef Bool -LXQtTaskbarX11Backend::LXQtTaskbarX11Backend(QObject *parent) - : ILXQtTaskbarAbstractBackend(parent) +LXQtWMBackendX11::LXQtWMBackendX11(QObject *parent) + : ILXQtAbstractWMInterface(parent) { auto *x11Application = qGuiApp->nativeInterface(); - Q_ASSERT_X(x11Application, "LXQtTaskbarX11Backend", "Constructed without X11 connection"); + Q_ASSERT_X(x11Application, "LXQtWMBackendX11", "Constructed without X11 connection"); m_X11Display = x11Application->display(); m_xcbConnection = x11Application->connection(); - connect(KX11Extras::self(), &KX11Extras::windowChanged, this, &LXQtTaskbarX11Backend::onWindowChanged); - connect(KX11Extras::self(), &KX11Extras::windowAdded, this, &LXQtTaskbarX11Backend::onWindowAdded); - connect(KX11Extras::self(), &KX11Extras::windowRemoved, this, &LXQtTaskbarX11Backend::onWindowRemoved); + connect(KX11Extras::self(), &KX11Extras::windowChanged, this, &LXQtWMBackendX11::onWindowChanged); + connect(KX11Extras::self(), &KX11Extras::windowAdded, this, &LXQtWMBackendX11::onWindowAdded); + connect(KX11Extras::self(), &KX11Extras::windowRemoved, this, &LXQtWMBackendX11::onWindowRemoved); - connect(KX11Extras::self(), &KX11Extras::numberOfDesktopsChanged, this, &ILXQtTaskbarAbstractBackend::workspacesCountChanged); - connect(KX11Extras::self(), &KX11Extras::currentDesktopChanged, this, &ILXQtTaskbarAbstractBackend::currentWorkspaceChanged); + connect(KX11Extras::self(), &KX11Extras::numberOfDesktopsChanged, this, &ILXQtAbstractWMInterface::workspacesCountChanged); + connect(KX11Extras::self(), &KX11Extras::currentDesktopChanged, this, &ILXQtAbstractWMInterface::currentWorkspaceChanged); - connect(KX11Extras::self(), &KX11Extras::activeWindowChanged, this, &ILXQtTaskbarAbstractBackend::activeWindowChanged); + connect(KX11Extras::self(), &KX11Extras::activeWindowChanged, this, &ILXQtAbstractWMInterface::activeWindowChanged); } /************************************************ * Model slots ************************************************/ -void LXQtTaskbarX11Backend::onWindowChanged(WId windowId, NET::Properties prop, NET::Properties2 prop2) +void LXQtWMBackendX11::onWindowChanged(WId windowId, NET::Properties prop, NET::Properties2 prop2) { if(!m_windows.contains(windowId)) { @@ -97,7 +97,7 @@ void LXQtTaskbarX11Backend::onWindowChanged(WId windowId, NET::Properties prop, emit windowPropertyChanged(windowId, int(LXQtTaskBarWindowProperty::Urgency)); } -void LXQtTaskbarX11Backend::onWindowAdded(WId windowId) +void LXQtWMBackendX11::onWindowAdded(WId windowId) { if(m_windows.contains(windowId)) return; @@ -108,7 +108,7 @@ void LXQtTaskbarX11Backend::onWindowAdded(WId windowId) addWindow_internal(windowId); } -void LXQtTaskbarX11Backend::onWindowRemoved(WId windowId) +void LXQtWMBackendX11::onWindowRemoved(WId windowId) { const int row = m_windows.indexOf(windowId); if(row == -1) @@ -122,7 +122,7 @@ void LXQtTaskbarX11Backend::onWindowRemoved(WId windowId) /************************************************ * Model private functions ************************************************/ -bool LXQtTaskbarX11Backend::acceptWindow(WId windowId) const +bool LXQtWMBackendX11::acceptWindow(WId windowId) const { QFlags ignoreList; ignoreList |= NET::DesktopMask; @@ -161,7 +161,7 @@ bool LXQtTaskbarX11Backend::acceptWindow(WId windowId) const return !NET::typeMatchesMask(info.windowType(NET::AllTypesMask), normalFlag); } -void LXQtTaskbarX11Backend::addWindow_internal(WId windowId, bool emitAdded) +void LXQtWMBackendX11::addWindow_internal(WId windowId, bool emitAdded) { m_windows.append(windowId); if(emitAdded) @@ -172,7 +172,7 @@ void LXQtTaskbarX11Backend::addWindow_internal(WId windowId, bool emitAdded) /************************************************ * Windows function ************************************************/ -bool LXQtTaskbarX11Backend::supportsAction(WId windowId, LXQtTaskBarBackendAction action) const +bool LXQtWMBackendX11::supportsAction(WId windowId, LXQtTaskBarBackendAction action) const { NET::Action x11Action; @@ -221,7 +221,7 @@ bool LXQtTaskbarX11Backend::supportsAction(WId windowId, LXQtTaskBarBackendActio return info.actionSupported(x11Action); } -bool LXQtTaskbarX11Backend::reloadWindows() +bool LXQtWMBackendX11::reloadWindows() { QVector oldWindows; qSwap(oldWindows, m_windows); @@ -254,37 +254,37 @@ bool LXQtTaskbarX11Backend::reloadWindows() return true; } -QVector LXQtTaskbarX11Backend::getCurrentWindows() const +QVector LXQtWMBackendX11::getCurrentWindows() const { return m_windows; } -QString LXQtTaskbarX11Backend::getWindowTitle(WId windowId) const +QString LXQtWMBackendX11::getWindowTitle(WId windowId) const { KWindowInfo info(windowId, NET::WMVisibleName | NET::WMName); QString title = info.visibleName().isEmpty() ? info.name() : info.visibleName(); return title; } -bool LXQtTaskbarX11Backend::applicationDemandsAttention(WId windowId) const +bool LXQtWMBackendX11::applicationDemandsAttention(WId windowId) const { WId appRootWindow = XDefaultRootWindow(m_X11Display); return NETWinInfo(m_xcbConnection, windowId, appRootWindow, NET::Properties{}, NET::WM2Urgency).urgency() || KWindowInfo{windowId, NET::WMState}.hasState(NET::DemandsAttention); } -QIcon LXQtTaskbarX11Backend::getApplicationIcon(WId windowId, int devicePixels) const +QIcon LXQtWMBackendX11::getApplicationIcon(WId windowId, int devicePixels) const { return KX11Extras::icon(windowId, devicePixels, devicePixels); } -QString LXQtTaskbarX11Backend::getWindowClass(WId windowId) const +QString LXQtWMBackendX11::getWindowClass(WId windowId) const { KWindowInfo info(windowId, NET::Properties(), NET::WM2WindowClass); return QString::fromUtf8(info.windowClassClass()); } -LXQtTaskBarWindowLayer LXQtTaskbarX11Backend::getWindowLayer(WId windowId) const +LXQtTaskBarWindowLayer LXQtWMBackendX11::getWindowLayer(WId windowId) const { NET::States state = KWindowInfo(windowId, NET::WMState).state(); if(state.testFlag(NET::KeepAbove)) @@ -294,7 +294,7 @@ LXQtTaskBarWindowLayer LXQtTaskbarX11Backend::getWindowLayer(WId windowId) const return LXQtTaskBarWindowLayer::Normal; } -bool LXQtTaskbarX11Backend::setWindowLayer(WId windowId, LXQtTaskBarWindowLayer layer) +bool LXQtWMBackendX11::setWindowLayer(WId windowId, LXQtTaskBarWindowLayer layer) { switch(layer) { @@ -317,7 +317,7 @@ bool LXQtTaskbarX11Backend::setWindowLayer(WId windowId, LXQtTaskBarWindowLayer return true; } -LXQtTaskBarWindowState LXQtTaskbarX11Backend::getWindowState(WId windowId) const +LXQtTaskBarWindowState LXQtWMBackendX11::getWindowState(WId windowId) const { KWindowInfo info(windowId,NET::WMState | NET::XAWMState); if(info.isMinimized()) @@ -340,7 +340,7 @@ LXQtTaskBarWindowState LXQtTaskbarX11Backend::getWindowState(WId windowId) const return LXQtTaskBarWindowState::Normal; } -bool LXQtTaskbarX11Backend::setWindowState(WId windowId, LXQtTaskBarWindowState state, bool set) +bool LXQtWMBackendX11::setWindowState(WId windowId, LXQtTaskBarWindowState state, bool set) { // NOTE: window activation is left to the caller @@ -393,12 +393,12 @@ bool LXQtTaskbarX11Backend::setWindowState(WId windowId, LXQtTaskBarWindowState return true; } -bool LXQtTaskbarX11Backend::isWindowActive(WId windowId) const +bool LXQtWMBackendX11::isWindowActive(WId windowId) const { return KX11Extras::activeWindow() == windowId; } -bool LXQtTaskbarX11Backend::raiseWindow(WId windowId, bool onCurrentWorkSpace) +bool LXQtWMBackendX11::raiseWindow(WId windowId, bool onCurrentWorkSpace) { if (onCurrentWorkSpace && getWindowState(windowId) == LXQtTaskBarWindowState::Minimized) { @@ -418,14 +418,14 @@ bool LXQtTaskbarX11Backend::raiseWindow(WId windowId, bool onCurrentWorkSpace) return true; } -bool LXQtTaskbarX11Backend::closeWindow(WId windowId) +bool LXQtWMBackendX11::closeWindow(WId windowId) { // FIXME: Why there is no such thing in KWindowSystem?? NETRootInfo(m_xcbConnection, NET::CloseWindow).closeWindowRequest(windowId); return true; } -WId LXQtTaskbarX11Backend::getActiveWindow() const +WId LXQtWMBackendX11::getActiveWindow() const { return KX11Extras::activeWindow(); } @@ -434,22 +434,22 @@ WId LXQtTaskbarX11Backend::getActiveWindow() const /************************************************ * Workspaces ************************************************/ -int LXQtTaskbarX11Backend::getWorkspacesCount() const +int LXQtWMBackendX11::getWorkspacesCount() const { return KX11Extras::numberOfDesktops(); } -QString LXQtTaskbarX11Backend::getWorkspaceName(int idx) const +QString LXQtWMBackendX11::getWorkspaceName(int idx) const { return KX11Extras::desktopName(idx); } -int LXQtTaskbarX11Backend::getCurrentWorkspace() const +int LXQtWMBackendX11::getCurrentWorkspace() const { return KX11Extras::currentDesktop(); } -bool LXQtTaskbarX11Backend::setCurrentWorkspace(int idx) +bool LXQtWMBackendX11::setCurrentWorkspace(int idx) { if(KX11Extras::currentDesktop() == idx) return true; @@ -458,19 +458,19 @@ bool LXQtTaskbarX11Backend::setCurrentWorkspace(int idx) return true; } -int LXQtTaskbarX11Backend::getWindowWorkspace(WId windowId) const +int LXQtWMBackendX11::getWindowWorkspace(WId windowId) const { KWindowInfo info(windowId, NET::WMDesktop); return info.desktop(); } -bool LXQtTaskbarX11Backend::setWindowOnWorkspace(WId windowId, int idx) +bool LXQtWMBackendX11::setWindowOnWorkspace(WId windowId, int idx) { KX11Extras::setOnDesktop(windowId, idx); return true; } -void LXQtTaskbarX11Backend::moveApplicationToPrevNextMonitor(WId windowId, bool next, bool raiseOnCurrentDesktop) +void LXQtWMBackendX11::moveApplicationToPrevNextMonitor(WId windowId, bool next, bool raiseOnCurrentDesktop) { KWindowInfo info(windowId, NET::WMDesktop); if (!info.isOnCurrentDesktop()) @@ -516,7 +516,7 @@ void LXQtTaskbarX11Backend::moveApplicationToPrevNextMonitor(WId windowId, bool } } -bool LXQtTaskbarX11Backend::isWindowOnScreen(QScreen *screen, WId windowId) const +bool LXQtWMBackendX11::isWindowOnScreen(QScreen *screen, WId windowId) const { //TODO: old code was: //return QApplication::desktop()->screenGeometry(parentTaskBar()).intersects(KWindowInfo(mWindow, NET::WMFrameExtents).frameGeometry()); @@ -528,7 +528,7 @@ bool LXQtTaskbarX11Backend::isWindowOnScreen(QScreen *screen, WId windowId) cons return screen->geometry().intersects(r); } -bool LXQtTaskbarX11Backend::setDesktopLayout(Qt::Orientation orientation, int rows, int columns, bool rightToLeft) +bool LXQtWMBackendX11::setDesktopLayout(Qt::Orientation orientation, int rows, int columns, bool rightToLeft) { NETRootInfo mDesktops(m_xcbConnection, NET::NumberOfDesktops | NET::CurrentDesktop | NET::DesktopNames, NET::WM2DesktopLayout); @@ -550,7 +550,7 @@ bool LXQtTaskbarX11Backend::setDesktopLayout(Qt::Orientation orientation, int ro /************************************************ * X11 Specific ************************************************/ -void LXQtTaskbarX11Backend::moveApplication(WId windowId) +void LXQtWMBackendX11::moveApplication(WId windowId) { KWindowInfo info(windowId, NET::WMDesktop); if (!info.isOnCurrentDesktop()) @@ -568,7 +568,7 @@ void LXQtTaskbarX11Backend::moveApplication(WId windowId) NETRootInfo(m_xcbConnection, NET::WMMoveResize).moveResizeRequest(windowId, X, Y, NET::Move); } -void LXQtTaskbarX11Backend::resizeApplication(WId windowId) +void LXQtWMBackendX11::resizeApplication(WId windowId) { KWindowInfo info(windowId, NET::WMDesktop); if (!info.isOnCurrentDesktop()) @@ -586,7 +586,7 @@ void LXQtTaskbarX11Backend::resizeApplication(WId windowId) NETRootInfo(m_xcbConnection, NET::WMMoveResize).moveResizeRequest(windowId, X, Y, NET::BottomRight); } -void LXQtTaskbarX11Backend::refreshIconGeometry(WId windowId, QRect const & geom) +void LXQtWMBackendX11::refreshIconGeometry(WId windowId, QRect const & geom) { // NOTE: This function announces where the task icon is, // such that X11 WMs can perform their related animations correctly. @@ -616,7 +616,7 @@ void LXQtTaskbarX11Backend::refreshIconGeometry(WId windowId, QRect const & geom info.setIconGeometry(nrect); } -bool LXQtTaskbarX11Backend::isAreaOverlapped(const QRect &area) const +bool LXQtWMBackendX11::isAreaOverlapped(const QRect &area) const { //TODO: reuse our m_windows cache? QFlags ignoreList; @@ -648,13 +648,26 @@ bool LXQtTaskbarX11Backend::isAreaOverlapped(const QRect &area) const return false; } -bool LXQtTaskbarX11Backend::isShowingDesktop() const +bool LXQtWMBackendX11::isShowingDesktop() const { return KWindowSystem::showingDesktop(); } -bool LXQtTaskbarX11Backend::showDesktop(bool value) +bool LXQtWMBackendX11::showDesktop(bool value) { KWindowSystem::setShowingDesktop(value); return true; } + +int LXQtWMBackendX11Library::getBackendScore() const +{ + auto *x11Application = qGuiApp->nativeInterface(); + if(x11Application) + return 80; + return 30; +} + +ILXQtAbstractWMInterface *LXQtWMBackendX11Library::instance() const +{ + return new LXQtWMBackendX11; +} diff --git a/panel/backends/xcb/lxqttaskbarbackend_x11.h b/panel/backends/xcb/lxqtwmbackend_x11.h similarity index 83% rename from panel/backends/xcb/lxqttaskbarbackend_x11.h rename to panel/backends/xcb/lxqtwmbackend_x11.h index 2478b3fff..1457c2b2d 100644 --- a/panel/backends/xcb/lxqttaskbarbackend_x11.h +++ b/panel/backends/xcb/lxqtwmbackend_x11.h @@ -1,20 +1,19 @@ -#ifndef LXQTTASKBARBACKEND_X11_H -#define LXQTTASKBARBACKEND_X11_H +#ifndef LXQT_WM_BACKEND_X11_H +#define LXQT_WM_BACKEND_X11_H -#include "../ilxqttaskbarabstractbackend.h" +#include "../ilxqtabstractwmiface.h" -//TODO: make PIMPL to forward declare NET::Properties, Display, xcb_connection_t #include typedef struct _XDisplay Display; struct xcb_connection_t; -class LXQtTaskbarX11Backend : public ILXQtTaskbarAbstractBackend +class LXQtWMBackendX11 : public ILXQtAbstractWMInterface { Q_OBJECT public: - explicit LXQtTaskbarX11Backend(QObject *parent = nullptr); + explicit LXQtWMBackendX11(QObject *parent = nullptr); // Backend virtual bool supportsAction(WId windowId, LXQtTaskBarBackendAction action) const override; @@ -86,4 +85,15 @@ private slots: QVector m_windows; }; -#endif // LXQTTASKBARBACKEND_X11_H +class LXQtWMBackendX11Library: public QObject, public ILXQtWMBackendLibrary +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "lxqt.org/Panel/WMInterface/1.0") + Q_INTERFACES(ILXQtWMBackendLibrary) +public: + int getBackendScore() const override; + + ILXQtAbstractWMInterface* instance() const override; +}; + +#endif // LXQT_WM_BACKEND_X11_H diff --git a/panel/lxqtpanel.cpp b/panel/lxqtpanel.cpp index f4144ff41..17e0c3520 100644 --- a/panel/lxqtpanel.cpp +++ b/panel/lxqtpanel.cpp @@ -52,7 +52,7 @@ #include #include -#include "backends/ilxqttaskbarabstractbackend.h" +#include "backends/ilxqtabstractwmiface.h" #include @@ -281,18 +281,18 @@ LXQtPanel::LXQtPanel(const QString &configGroup, LXQt::Settings *settings, QWidg LXQtPanelApplication *a = reinterpret_cast(qApp); auto wmBackend = a->getWMBackend(); - connect(wmBackend, &ILXQtTaskbarAbstractBackend::windowAdded, this, [this] { + connect(wmBackend, &ILXQtAbstractWMInterface::windowAdded, this, [this] { if (mHidable && mHideOnOverlap && !mHidden) { mShowDelayTimer.stop(); hidePanel(); } }); - connect(wmBackend, &ILXQtTaskbarAbstractBackend::windowRemoved, this, [this] { + connect(wmBackend, &ILXQtAbstractWMInterface::windowRemoved, this, [this] { if (mHidable && mHideOnOverlap && mHidden && !isPanelOverlapped()) mShowDelayTimer.start(); }); - connect(wmBackend, &ILXQtTaskbarAbstractBackend::currentWorkspaceChanged, this, [this] { + connect(wmBackend, &ILXQtAbstractWMInterface::currentWorkspaceChanged, this, [this] { if (mHidable && mHideOnOverlap) { if (!mHidden) @@ -304,7 +304,7 @@ LXQtPanel::LXQtPanel(const QString &configGroup, LXQt::Settings *settings, QWidg mShowDelayTimer.start(); } }); - connect(wmBackend, &ILXQtTaskbarAbstractBackend::windowPropertyChanged, + connect(wmBackend, &ILXQtAbstractWMInterface::windowPropertyChanged, this, [this] (WId /* id */, int prop) { if (mHidable && mHideOnOverlap diff --git a/panel/lxqtpanelapplication.cpp b/panel/lxqtpanelapplication.cpp index c3b666620..887284df7 100644 --- a/panel/lxqtpanelapplication.cpp +++ b/panel/lxqtpanelapplication.cpp @@ -38,28 +38,64 @@ #include #include -#include "backends/lxqttaskbardummybackend.h" -#include "backends/xcb/lxqttaskbarbackend_x11.h" +#include +#include +#include -ILXQtTaskbarAbstractBackend *createWMBackend() +#include "backends/lxqtdummywmbackend.h" + +QString findBestBackend() { - if(qGuiApp->nativeInterface()) - return new LXQtTaskbarX11Backend; + QStringList dirs; + dirs << QProcessEnvironment::systemEnvironment().value(QStringLiteral("LXQTPANEL_PLUGIN_PATH")).split(QStringLiteral(":")); + dirs << QStringLiteral(PLUGIN_DIR); + + QString lastBackendFile; + int lastBackendScore = 0; - qWarning() << "\n" - << "ERROR: Could not create a backend for window managment operations.\n" - << "Only X11 supported!\n" - << "Falling back to dummy backend. Some functions will not be available.\n" - << "\n"; + for(const QString& dir : std::as_const(dirs)) + { + QDir backendsDir(dir); + backendsDir.cd(QLatin1String("backend")); - return new LXQtTaskBarDummyBackend; + const auto entryList = backendsDir.entryList(QDir::Files); + for(const QString& fileName : entryList) + { + const QString absPath = backendsDir.absoluteFilePath(fileName); + QPluginLoader loader(absPath); + loader.load(); + if(!loader.isLoaded()) + { + QString err = loader.errorString(); + qWarning() << "Backend error:" << err; + } + + QObject *plugin = loader.instance(); + if(!plugin) + continue; + + ILXQtWMBackendLibrary *backend = qobject_cast(plugin); + if(backend) + { + int score = backend->getBackendScore(); + if(score > lastBackendScore) + { + lastBackendFile = absPath; + lastBackendScore = lastBackendScore; + } + } + loader.unload(); + } + } + + return lastBackendFile; } LXQtPanelApplicationPrivate::LXQtPanelApplicationPrivate(LXQtPanelApplication *q) : mSettings(nullptr), q_ptr(q) { - mWMBackend = createWMBackend(); + } @@ -90,6 +126,55 @@ ILXQtPanel::Position LXQtPanelApplicationPrivate::computeNewPanelPosition(const return static_cast (availablePosition); } +void LXQtPanelApplicationPrivate::loadBackend() +{ + QPluginLoader loader; + + // First try to load user preferred backend + QString preferredBackend = mSettings->value(QStringLiteral("preferred_backend")).toString(); + if(!preferredBackend.isEmpty()) + { + loader.setFileName(preferredBackend); + loader.load(); + if(!loader.isLoaded() || !loader.instance() || !qobject_cast(loader.instance())) + { + // Plugin not valid + loader.unload(); + preferredBackend.clear(); + } + } + + if(preferredBackend.isEmpty()) + { + // If user prefferred is not valid, find best available backend + QString fileName = findBestBackend(); + mSettings->setValue(QStringLiteral("preferred_backend"), fileName); + loader.setFileName(fileName); + loader.load(); + } + + QObject *plugin = loader.instance(); + ILXQtWMBackendLibrary *backend = qobject_cast(plugin); + if(backend) + { + mWMBackend = backend->instance(); + } + else + { + // If no backend can be found fall back to dummy backend + loader.unload(); + mWMBackend = new LXQtDummyWMBackend; + + qWarning() << "\n" + << "ERROR: Could not create a backend for window managment operations.\n" + << "Only X11 supported!\n" + << "Falling back to dummy backend. Some functions will not be available.\n" + << "\n"; + } + + mWMBackend->setParent(q_ptr); +} + LXQtPanelApplication::LXQtPanelApplication(int& argc, char** argv) : LXQt::Application(argc, argv, true), d_ptr(new LXQtPanelApplicationPrivate(this)) @@ -124,6 +209,8 @@ LXQtPanelApplication::LXQtPanelApplication(int& argc, char** argv) else d->mSettings = new LXQt::Settings(configFile, QSettings::IniFormat, this); + d->loadBackend(); + // This is a workaround for Qt 5 bug #40681. const auto allScreens = screens(); for(QScreen* screen : allScreens) @@ -309,7 +396,7 @@ bool LXQtPanelApplication::isPluginSingletonAndRunning(QString const & pluginId) return false; } -ILXQtTaskbarAbstractBackend *LXQtPanelApplication::getWMBackend() const +ILXQtAbstractWMInterface *LXQtPanelApplication::getWMBackend() const { Q_D(const LXQtPanelApplication); return d->mWMBackend; diff --git a/panel/lxqtpanelapplication.h b/panel/lxqtpanelapplication.h index 15c912884..9ad8e3141 100644 --- a/panel/lxqtpanelapplication.h +++ b/panel/lxqtpanelapplication.h @@ -37,7 +37,7 @@ class QScreen; class LXQtPanel; class LXQtPanelApplicationPrivate; -class ILXQtTaskbarAbstractBackend; +class ILXQtAbstractWMInterface; /*! * \brief The LXQtPanelApplication class inherits from LXQt::Application and @@ -91,7 +91,7 @@ class LXQtPanelApplication : public LXQt::Application */ bool isPluginSingletonAndRunning(QString const & pluginId) const; - ILXQtTaskbarAbstractBackend* getWMBackend() const; + ILXQtAbstractWMInterface* getWMBackend() const; public slots: /*! diff --git a/panel/lxqtpanelapplication_p.h b/panel/lxqtpanelapplication_p.h index db924bf62..609546d26 100644 --- a/panel/lxqtpanelapplication_p.h +++ b/panel/lxqtpanelapplication_p.h @@ -27,7 +27,7 @@ namespace LXQt { class Settings; } -class ILXQtTaskbarAbstractBackend; +class ILXQtAbstractWMInterface; class LXQtPanelApplicationPrivate { Q_DECLARE_PUBLIC(LXQtPanelApplication) @@ -37,10 +37,12 @@ class LXQtPanelApplicationPrivate { ~LXQtPanelApplicationPrivate() {}; LXQt::Settings *mSettings; - ILXQtTaskbarAbstractBackend *mWMBackend; + ILXQtAbstractWMInterface *mWMBackend; ILXQtPanel::Position computeNewPanelPosition(const LXQtPanel *p, const int screenNum); + void loadBackend(); + private: LXQtPanelApplication *const q_ptr; }; diff --git a/plugin-desktopswitch/desktopswitch.cpp b/plugin-desktopswitch/desktopswitch.cpp index ea2a0fce3..453594f3d 100644 --- a/plugin-desktopswitch/desktopswitch.cpp +++ b/plugin-desktopswitch/desktopswitch.cpp @@ -35,7 +35,7 @@ #include #include "../panel/lxqtpanelapplication.h" -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" #include @@ -75,11 +75,11 @@ DesktopSwitch::DesktopSwitch(const ILXQtPanelPluginStartupInfo &startupInfo) : connect(m_buttons, &QButtonGroup::idClicked, this, &DesktopSwitch::setDesktop); - connect(mBackend, &ILXQtTaskbarAbstractBackend::workspacesCountChanged, this, &DesktopSwitch::onNumberOfDesktopsChanged); - connect(mBackend, &ILXQtTaskbarAbstractBackend::currentWorkspaceChanged, this, &DesktopSwitch::onCurrentDesktopChanged); - connect(mBackend, &ILXQtTaskbarAbstractBackend::workspaceNameChanged, this, &DesktopSwitch::onDesktopNamesChanged); + connect(mBackend, &ILXQtAbstractWMInterface::workspacesCountChanged, this, &DesktopSwitch::onNumberOfDesktopsChanged); + connect(mBackend, &ILXQtAbstractWMInterface::currentWorkspaceChanged, this, &DesktopSwitch::onCurrentDesktopChanged); + connect(mBackend, &ILXQtAbstractWMInterface::workspaceNameChanged, this, &DesktopSwitch::onDesktopNamesChanged); - connect(mBackend, &ILXQtTaskbarAbstractBackend::windowPropertyChanged, this, &DesktopSwitch::onWindowChanged); + connect(mBackend, &ILXQtAbstractWMInterface::windowPropertyChanged, this, &DesktopSwitch::onWindowChanged); } void DesktopSwitch::registerShortcuts() diff --git a/plugin-desktopswitch/desktopswitch.h b/plugin-desktopswitch/desktopswitch.h index 182eb0ced..07f14ce8d 100644 --- a/plugin-desktopswitch/desktopswitch.h +++ b/plugin-desktopswitch/desktopswitch.h @@ -41,7 +41,7 @@ namespace LXQt { class GridLayout; } -class ILXQtTaskbarAbstractBackend; +class ILXQtAbstractWMInterface; class DesktopSwitchWidget: public QFrame { @@ -83,7 +83,7 @@ class DesktopSwitch : public QObject, public ILXQtPanelPlugin LXQt::GridLayout *mLayout; int mRows; bool mShowOnlyActive; - ILXQtTaskbarAbstractBackend *mBackend; + ILXQtAbstractWMInterface *mBackend; DesktopSwitchButton::LabelType mLabelType; void refresh(); diff --git a/plugin-desktopswitch/desktopswitchconfiguration.cpp b/plugin-desktopswitch/desktopswitchconfiguration.cpp index 33781f7d8..39fac7f47 100644 --- a/plugin-desktopswitch/desktopswitchconfiguration.cpp +++ b/plugin-desktopswitch/desktopswitchconfiguration.cpp @@ -28,7 +28,7 @@ #include "ui_desktopswitchconfiguration.h" #include "../panel/lxqtpanelapplication.h" -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" #include #include diff --git a/plugin-showdesktop/showdesktop.cpp b/plugin-showdesktop/showdesktop.cpp index fb69f6067..4fa14658d 100644 --- a/plugin-showdesktop/showdesktop.cpp +++ b/plugin-showdesktop/showdesktop.cpp @@ -33,7 +33,7 @@ #include "../panel/pluginsettings.h" #include "../panel/lxqtpanelapplication.h" -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" #define DEFAULT_SHORTCUT "Control+Alt+D" diff --git a/plugin-taskbar/lxqttaskbar.cpp b/plugin-taskbar/lxqttaskbar.cpp index fb41ae7e0..9f3d40484 100644 --- a/plugin-taskbar/lxqttaskbar.cpp +++ b/plugin-taskbar/lxqttaskbar.cpp @@ -50,7 +50,7 @@ #include "lxqttaskgroup.h" #include "../panel/pluginsettings.h" -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" #include "../panel/lxqtpanelapplication.h" using namespace LXQt; @@ -104,9 +104,9 @@ LXQtTaskBar::LXQtTaskBar(ILXQtPanelPlugin *plugin, QWidget *parent) : connect(mSignalMapper, &QSignalMapper::mappedInt, this, &LXQtTaskBar::activateTask); QTimer::singleShot(0, this, &LXQtTaskBar::registerShortcuts); - connect(mBackend, &ILXQtTaskbarAbstractBackend::windowPropertyChanged, this, &LXQtTaskBar::onWindowChanged); - connect(mBackend, &ILXQtTaskbarAbstractBackend::windowAdded, this, &LXQtTaskBar::onWindowAdded); - connect(mBackend, &ILXQtTaskbarAbstractBackend::windowRemoved, this, &LXQtTaskBar::onWindowRemoved); + connect(mBackend, &ILXQtAbstractWMInterface::windowPropertyChanged, this, &LXQtTaskBar::onWindowChanged); + connect(mBackend, &ILXQtAbstractWMInterface::windowAdded, this, &LXQtTaskBar::onWindowAdded); + connect(mBackend, &ILXQtAbstractWMInterface::windowRemoved, this, &LXQtTaskBar::onWindowRemoved); // Consider already fetched windows const auto initialWindows = mBackend->getCurrentWindows(); diff --git a/plugin-taskbar/lxqttaskbar.h b/plugin-taskbar/lxqttaskbar.h index 9f94a2958..c6d6a6d29 100644 --- a/plugin-taskbar/lxqttaskbar.h +++ b/plugin-taskbar/lxqttaskbar.h @@ -47,7 +47,7 @@ class LXQtTaskGroup; class LeftAlignedTextStyle; -class ILXQtTaskbarAbstractBackend; +class ILXQtAbstractWMInterface; namespace LXQt { class GridLayout; @@ -86,7 +86,7 @@ class LXQtTaskBar : public QFrame ILXQtPanel * panel() const; inline ILXQtPanelPlugin * plugin() const { return mPlugin; } - inline ILXQtTaskbarAbstractBackend *getBackend() const { return mBackend; } + inline ILXQtAbstractWMInterface *getBackend() const { return mBackend; } public slots: void settingsChanged(); @@ -158,7 +158,7 @@ private slots: QWidget *mPlaceHolder; LeftAlignedTextStyle *mStyle; - ILXQtTaskbarAbstractBackend *mBackend; + ILXQtAbstractWMInterface *mBackend; }; #endif // LXQTTASKBAR_H diff --git a/plugin-taskbar/lxqttaskbarconfiguration.cpp b/plugin-taskbar/lxqttaskbarconfiguration.cpp index 0dd528e51..0e441f51b 100644 --- a/plugin-taskbar/lxqttaskbarconfiguration.cpp +++ b/plugin-taskbar/lxqttaskbarconfiguration.cpp @@ -31,7 +31,7 @@ #include "ui_lxqttaskbarconfiguration.h" #include "../panel/lxqtpanelapplication.h" -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" LXQtTaskbarConfiguration::LXQtTaskbarConfiguration(PluginSettings *settings, QWidget *parent): LXQtPanelPluginConfigDialog(settings, parent), diff --git a/plugin-taskbar/lxqttaskbarproxymodel.cpp b/plugin-taskbar/lxqttaskbarproxymodel.cpp index fdcdfa4d4..e02317ff7 100644 --- a/plugin-taskbar/lxqttaskbarproxymodel.cpp +++ b/plugin-taskbar/lxqttaskbarproxymodel.cpp @@ -1,6 +1,6 @@ #include "lxqttaskbarproxymodel.h" -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" #include @@ -209,12 +209,12 @@ void LXQtTaskBarProxyModel::setGroupByWindowClass(bool newGroupByWindowClass) } -ILXQtTaskbarAbstractBackend *LXQtTaskBarProxyModel::backend() const +ILXQtAbstractWMInterface *LXQtTaskBarProxyModel::backend() const { return m_backend; } -void LXQtTaskBarProxyModel::setBackend(ILXQtTaskbarAbstractBackend *newBackend) +void LXQtTaskBarProxyModel::setBackend(ILXQtAbstractWMInterface *newBackend) { beginResetModel(); @@ -222,11 +222,11 @@ void LXQtTaskBarProxyModel::setBackend(ILXQtTaskbarAbstractBackend *newBackend) if(m_backend) { - disconnect(m_backend, &ILXQtTaskbarAbstractBackend::windowAdded, + disconnect(m_backend, &ILXQtAbstractWMInterface::windowAdded, this, &LXQtTaskBarProxyModel::onWindowAdded); - disconnect(m_backend, &ILXQtTaskbarAbstractBackend::windowRemoved, + disconnect(m_backend, &ILXQtAbstractWMInterface::windowRemoved, this, &LXQtTaskBarProxyModel::onWindowRemoved); - disconnect(m_backend, &ILXQtTaskbarAbstractBackend::windowPropertyChanged, + disconnect(m_backend, &ILXQtAbstractWMInterface::windowPropertyChanged, this, &LXQtTaskBarProxyModel::onWindowPropertyChanged); } @@ -234,11 +234,11 @@ void LXQtTaskBarProxyModel::setBackend(ILXQtTaskbarAbstractBackend *newBackend) if(m_backend) { - connect(m_backend, &ILXQtTaskbarAbstractBackend::windowAdded, + connect(m_backend, &ILXQtAbstractWMInterface::windowAdded, this, &LXQtTaskBarProxyModel::onWindowAdded); - connect(m_backend, &ILXQtTaskbarAbstractBackend::windowRemoved, + connect(m_backend, &ILXQtAbstractWMInterface::windowRemoved, this, &LXQtTaskBarProxyModel::onWindowRemoved); - connect(m_backend, &ILXQtTaskbarAbstractBackend::windowPropertyChanged, + connect(m_backend, &ILXQtAbstractWMInterface::windowPropertyChanged, this, &LXQtTaskBarProxyModel::onWindowPropertyChanged); // Reload current windows diff --git a/plugin-taskbar/lxqttaskbarproxymodel.h b/plugin-taskbar/lxqttaskbarproxymodel.h index 8bbb5ec49..c78c28f5b 100644 --- a/plugin-taskbar/lxqttaskbarproxymodel.h +++ b/plugin-taskbar/lxqttaskbarproxymodel.h @@ -6,7 +6,7 @@ #include "../panel/backends/lxqttaskbartypes.h" -class ILXQtTaskbarAbstractBackend; +class ILXQtAbstractWMInterface; class LXQtTaskBarProxyModelWindow { @@ -64,8 +64,8 @@ class LXQtTaskBarProxyModel : public QAbstractListModel QIcon getWindowIcon(int itemRow, int windowIdxInGroup, int devicePixels) const; - ILXQtTaskbarAbstractBackend *backend() const; - void setBackend(ILXQtTaskbarAbstractBackend *newBackend); + ILXQtAbstractWMInterface *backend() const; + void setBackend(ILXQtAbstractWMInterface *newBackend); bool groupByWindowClass() const; void setGroupByWindowClass(bool newGroupByWindowClass); @@ -90,7 +90,7 @@ private slots: } private: - ILXQtTaskbarAbstractBackend *m_backend; + ILXQtAbstractWMInterface *m_backend; QVector m_items; diff --git a/plugin-taskbar/lxqttaskbutton.cpp b/plugin-taskbar/lxqttaskbutton.cpp index c86e6af5f..5a581929c 100644 --- a/plugin-taskbar/lxqttaskbutton.cpp +++ b/plugin-taskbar/lxqttaskbutton.cpp @@ -50,7 +50,7 @@ #include #include -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" diff --git a/plugin-taskbar/lxqttaskbutton.h b/plugin-taskbar/lxqttaskbutton.h index 9ccca36fa..12e83a613 100644 --- a/plugin-taskbar/lxqttaskbutton.h +++ b/plugin-taskbar/lxqttaskbutton.h @@ -41,7 +41,7 @@ class QPalette; class QMimeData; class LXQtTaskBar; -class ILXQtTaskbarAbstractBackend; +class ILXQtAbstractWMInterface; class LeftAlignedTextStyle : public QProxyStyle { @@ -124,7 +124,7 @@ public slots: protected: //TODO: public getter instead? - ILXQtTaskbarAbstractBackend *mBackend; + ILXQtAbstractWMInterface *mBackend; private: void moveApplicationToPrevNextDesktop(bool next); diff --git a/plugin-taskbar/lxqttaskgroup.cpp b/plugin-taskbar/lxqttaskgroup.cpp index 8317c034f..7f44bdc28 100644 --- a/plugin-taskbar/lxqttaskgroup.cpp +++ b/plugin-taskbar/lxqttaskgroup.cpp @@ -42,7 +42,7 @@ #include #include -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" /************************************************ @@ -65,8 +65,8 @@ LXQtTaskGroup::LXQtTaskGroup(const QString &groupName, WId window, LXQtTaskBar * connect(parent, &LXQtTaskBar::buttonStyleRefreshed, this, &LXQtTaskGroup::setToolButtonsStyle); connect(parent, &LXQtTaskBar::showOnlySettingChanged, this, &LXQtTaskGroup::refreshVisibility); connect(parent, &LXQtTaskBar::popupShown, this, &LXQtTaskGroup::groupPopupShown); - connect(mBackend, &ILXQtTaskbarAbstractBackend::currentWorkspaceChanged, this, &LXQtTaskGroup::onDesktopChanged); - connect(mBackend, &ILXQtTaskbarAbstractBackend::activeWindowChanged, this, &LXQtTaskGroup::onActiveWindowChanged); + connect(mBackend, &ILXQtAbstractWMInterface::currentWorkspaceChanged, this, &LXQtTaskGroup::onDesktopChanged); + connect(mBackend, &ILXQtAbstractWMInterface::activeWindowChanged, this, &LXQtTaskGroup::onActiveWindowChanged); } /************************************************ From 632c551799407b3682a00c56a740c544283e1fff Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 9 Jul 2024 16:35:19 +0200 Subject: [PATCH 02/15] Update licenses --- panel/backends/ilxqtabstractwmiface.cpp | 29 +++++++++++++++++++++++- panel/backends/ilxqtabstractwmiface.h | 28 +++++++++++++++++++++++ panel/backends/lxqtdummywmbackend.cpp | 27 ++++++++++++++++++++++ panel/backends/lxqtdummywmbackend.h | 28 +++++++++++++++++++++++ panel/backends/lxqttaskbartypes.h | 28 +++++++++++++++++++++++ panel/backends/xcb/lxqtwmbackend_x11.cpp | 28 +++++++++++++++++++++++ panel/backends/xcb/lxqtwmbackend_x11.h | 28 +++++++++++++++++++++++ 7 files changed, 195 insertions(+), 1 deletion(-) diff --git a/panel/backends/ilxqtabstractwmiface.cpp b/panel/backends/ilxqtabstractwmiface.cpp index 080b07324..cb8678647 100644 --- a/panel/backends/ilxqtabstractwmiface.cpp +++ b/panel/backends/ilxqtabstractwmiface.cpp @@ -1,5 +1,32 @@ -#include "ilxqtabstractwmiface.h" +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + +#include "ilxqtabstractwmiface.h" ILXQtAbstractWMInterface::ILXQtAbstractWMInterface(QObject *parent) : QObject(parent) diff --git a/panel/backends/ilxqtabstractwmiface.h b/panel/backends/ilxqtabstractwmiface.h index 4bd4c6561..cce9639d9 100644 --- a/panel/backends/ilxqtabstractwmiface.h +++ b/panel/backends/ilxqtabstractwmiface.h @@ -1,3 +1,31 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + + #ifndef ILXQT_ABSTRACT_WM_INTERFACE_H #define ILXQT_ABSTRACT_WM_INTERFACE_H diff --git a/panel/backends/lxqtdummywmbackend.cpp b/panel/backends/lxqtdummywmbackend.cpp index 9d8e2f5e1..071cbbfbd 100644 --- a/panel/backends/lxqtdummywmbackend.cpp +++ b/panel/backends/lxqtdummywmbackend.cpp @@ -1,3 +1,30 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + #include "lxqtdummywmbackend.h" #include diff --git a/panel/backends/lxqtdummywmbackend.h b/panel/backends/lxqtdummywmbackend.h index 75a9b04b7..de6217903 100644 --- a/panel/backends/lxqtdummywmbackend.h +++ b/panel/backends/lxqtdummywmbackend.h @@ -1,3 +1,31 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + + #ifndef LXQT_DUMMY_WM_BACKEND_H #define LXQT_DUMMY_WM_BACKEND_H diff --git a/panel/backends/lxqttaskbartypes.h b/panel/backends/lxqttaskbartypes.h index 656591fbc..dc82f980c 100644 --- a/panel/backends/lxqttaskbartypes.h +++ b/panel/backends/lxqttaskbartypes.h @@ -1,3 +1,31 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + + #ifndef LXQTTASKBARTYPES_H #define LXQTTASKBARTYPES_H diff --git a/panel/backends/xcb/lxqtwmbackend_x11.cpp b/panel/backends/xcb/lxqtwmbackend_x11.cpp index 40fd06a81..8279f641b 100644 --- a/panel/backends/xcb/lxqtwmbackend_x11.cpp +++ b/panel/backends/xcb/lxqtwmbackend_x11.cpp @@ -1,3 +1,31 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + + #include "lxqtwmbackend_x11.h" #include diff --git a/panel/backends/xcb/lxqtwmbackend_x11.h b/panel/backends/xcb/lxqtwmbackend_x11.h index 1457c2b2d..804221e8e 100644 --- a/panel/backends/xcb/lxqtwmbackend_x11.h +++ b/panel/backends/xcb/lxqtwmbackend_x11.h @@ -1,3 +1,31 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + + #ifndef LXQT_WM_BACKEND_X11_H #define LXQT_WM_BACKEND_X11_H From 2986803efaafaa46901e4abd2ea8c2ea7a29b9cf Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 9 Jul 2024 16:40:24 +0200 Subject: [PATCH 03/15] lxqttaskbartypes.h: fix ShowOnAll desktops flag value --- panel/backends/lxqttaskbartypes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panel/backends/lxqttaskbartypes.h b/panel/backends/lxqttaskbartypes.h index dc82f980c..e821b410d 100644 --- a/panel/backends/lxqttaskbartypes.h +++ b/panel/backends/lxqttaskbartypes.h @@ -78,7 +78,7 @@ enum class LXQtTaskBarWindowLayer enum class LXQtTaskBarWorkspace { - ShowOnAll = -1 + ShowOnAll = 0 // Virtual destops have 1-based indexes }; #endif // LXQTTASKBARTYPES_H From 438be7053b8181c5e073f4d8635d721542478c90 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Fri, 12 Jul 2024 11:27:04 +0200 Subject: [PATCH 04/15] Fix backend load logic: do not load zero score backends - Fix X11 backend to return zero score on non-X11 platforms --- panel/backends/xcb/lxqtwmbackend_x11.cpp | 8 ++-- panel/lxqtpanelapplication.cpp | 50 ++++++++++++++++++------ 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/panel/backends/xcb/lxqtwmbackend_x11.cpp b/panel/backends/xcb/lxqtwmbackend_x11.cpp index 8279f641b..6e0be4889 100644 --- a/panel/backends/xcb/lxqtwmbackend_x11.cpp +++ b/panel/backends/xcb/lxqtwmbackend_x11.cpp @@ -690,9 +690,11 @@ bool LXQtWMBackendX11::showDesktop(bool value) int LXQtWMBackendX11Library::getBackendScore() const { auto *x11Application = qGuiApp->nativeInterface(); - if(x11Application) - return 80; - return 30; + if(!x11Application) + return 0; + + // Generic X11 backend + return 80; } ILXQtAbstractWMInterface *LXQtWMBackendX11Library::instance() const diff --git a/panel/lxqtpanelapplication.cpp b/panel/lxqtpanelapplication.cpp index 887284df7..548eb3b9b 100644 --- a/panel/lxqtpanelapplication.cpp +++ b/panel/lxqtpanelapplication.cpp @@ -81,19 +81,23 @@ QString findBestBackend() if(score > lastBackendScore) { lastBackendFile = absPath; - lastBackendScore = lastBackendScore; + lastBackendScore = score; } } loader.unload(); } } + if(lastBackendScore == 0) + return QString(); // No available backend is good for this environment + return lastBackendFile; } LXQtPanelApplicationPrivate::LXQtPanelApplicationPrivate(LXQtPanelApplication *q) - : mSettings(nullptr), - q_ptr(q) + : mSettings(nullptr) + , mWMBackend(nullptr) + , q_ptr(q) { } @@ -136,7 +140,14 @@ void LXQtPanelApplicationPrivate::loadBackend() { loader.setFileName(preferredBackend); loader.load(); - if(!loader.isLoaded() || !loader.instance() || !qobject_cast(loader.instance())) + + QObject *plugin = loader.instance(); + ILXQtWMBackendLibrary *backend = qobject_cast(plugin); + if(backend) + { + mWMBackend = backend->instance(); + } + else { // Plugin not valid loader.unload(); @@ -148,16 +159,33 @@ void LXQtPanelApplicationPrivate::loadBackend() { // If user prefferred is not valid, find best available backend QString fileName = findBestBackend(); - mSettings->setValue(QStringLiteral("preferred_backend"), fileName); - loader.setFileName(fileName); - loader.load(); + + if(!fileName.isEmpty()) + { + loader.setFileName(fileName); + loader.load(); + + QObject *plugin = loader.instance(); + ILXQtWMBackendLibrary *backend = qobject_cast(plugin); + if(backend) + { + // Save this backend for next startup + preferredBackend = fileName; + mSettings->setValue(QStringLiteral("preferred_backend"), preferredBackend); + + mWMBackend = backend->instance(); + } + else + { + // Plugin not valid + loader.unload(); + } + } } - QObject *plugin = loader.instance(); - ILXQtWMBackendLibrary *backend = qobject_cast(plugin); - if(backend) + if(mWMBackend) { - mWMBackend = backend->instance(); + qDebug() << "Panel backend:" << preferredBackend; } else { From 0b5ec82a7d7a7097250e778d12574609f87c701b Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Sat, 17 Aug 2024 13:26:06 +0200 Subject: [PATCH 05/15] LXQtPanelApplication: always find best backend at startup If preferred backend is set try it first. Do not set preferred backend automatically. It must be user choice. --- panel/lxqtpanelapplication.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/panel/lxqtpanelapplication.cpp b/panel/lxqtpanelapplication.cpp index 548eb3b9b..1c6e3cd91 100644 --- a/panel/lxqtpanelapplication.cpp +++ b/panel/lxqtpanelapplication.cpp @@ -151,11 +151,10 @@ void LXQtPanelApplicationPrivate::loadBackend() { // Plugin not valid loader.unload(); - preferredBackend.clear(); } } - if(preferredBackend.isEmpty()) + if(!mWMBackend) { // If user prefferred is not valid, find best available backend QString fileName = findBestBackend(); @@ -169,10 +168,6 @@ void LXQtPanelApplicationPrivate::loadBackend() ILXQtWMBackendLibrary *backend = qobject_cast(plugin); if(backend) { - // Save this backend for next startup - preferredBackend = fileName; - mSettings->setValue(QStringLiteral("preferred_backend"), preferredBackend); - mWMBackend = backend->instance(); } else @@ -185,7 +180,7 @@ void LXQtPanelApplicationPrivate::loadBackend() if(mWMBackend) { - qDebug() << "Panel backend:" << preferredBackend; + qDebug() << "\nPanel backend:" << preferredBackend << "\n"; } else { @@ -195,7 +190,6 @@ void LXQtPanelApplicationPrivate::loadBackend() qWarning() << "\n" << "ERROR: Could not create a backend for window managment operations.\n" - << "Only X11 supported!\n" << "Falling back to dummy backend. Some functions will not be available.\n" << "\n"; } From 1f2bcdc34870c5cb93b6e846b7b9fbf86e1b2126 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 20 Aug 2024 13:15:14 +0200 Subject: [PATCH 06/15] Panel backends: pass string argument for score calculation - Split XDG_CURRENT_DESKTOP - Skip LXQTPANEL_PLUGIN_PATH if empty --- panel/backends/ilxqtabstractwmiface.h | 2 +- panel/backends/xcb/lxqtwmbackend_x11.cpp | 4 +- panel/backends/xcb/lxqtwmbackend_x11.h | 2 +- panel/lxqtpanelapplication.cpp | 100 +++++++++++++++++------ 4 files changed, 81 insertions(+), 27 deletions(-) diff --git a/panel/backends/ilxqtabstractwmiface.h b/panel/backends/ilxqtabstractwmiface.h index cce9639d9..3f234f542 100644 --- a/panel/backends/ilxqtabstractwmiface.h +++ b/panel/backends/ilxqtabstractwmiface.h @@ -137,7 +137,7 @@ class LXQT_PANEL_API ILXQtWMBackendLibrary Returns the score of this backend for current detected environment. This is used to select correct backend at runtime **/ - virtual int getBackendScore() const = 0; + virtual int getBackendScore(const QString& key) const = 0; /** Returns the root component object of the backend. When the library is finally unloaded, the root component will automatically be deleted. diff --git a/panel/backends/xcb/lxqtwmbackend_x11.cpp b/panel/backends/xcb/lxqtwmbackend_x11.cpp index 6e0be4889..3196197bc 100644 --- a/panel/backends/xcb/lxqtwmbackend_x11.cpp +++ b/panel/backends/xcb/lxqtwmbackend_x11.cpp @@ -687,8 +687,10 @@ bool LXQtWMBackendX11::showDesktop(bool value) return true; } -int LXQtWMBackendX11Library::getBackendScore() const +int LXQtWMBackendX11Library::getBackendScore(const QString &key) const { + Q_UNUSED(key) + auto *x11Application = qGuiApp->nativeInterface(); if(!x11Application) return 0; diff --git a/panel/backends/xcb/lxqtwmbackend_x11.h b/panel/backends/xcb/lxqtwmbackend_x11.h index 804221e8e..e53b232f2 100644 --- a/panel/backends/xcb/lxqtwmbackend_x11.h +++ b/panel/backends/xcb/lxqtwmbackend_x11.h @@ -119,7 +119,7 @@ class LXQtWMBackendX11Library: public QObject, public ILXQtWMBackendLibrary Q_PLUGIN_METADATA(IID "lxqt.org/Panel/WMInterface/1.0") Q_INTERFACES(ILXQtWMBackendLibrary) public: - int getBackendScore() const override; + int getBackendScore(const QString& key) const override; ILXQtAbstractWMInterface* instance() const override; }; diff --git a/panel/lxqtpanelapplication.cpp b/panel/lxqtpanelapplication.cpp index 1c6e3cd91..c88e8c9c2 100644 --- a/panel/lxqtpanelapplication.cpp +++ b/panel/lxqtpanelapplication.cpp @@ -44,48 +44,100 @@ #include "backends/lxqtdummywmbackend.h" + +static inline QList detectDesktopEnvironment() +{ + const QByteArray xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP"); + if (!xdgCurrentDesktop.isEmpty()) + { + // KDE, GNOME, UNITY, LXDE, MATE, XFCE... + // But also LXQt:$COMPOSITOR:wlroots + QList list = xdgCurrentDesktop.toUpper().split(':'); + if(!list.isEmpty()) + { + if(list.first() == QByteArrayLiteral("LXQT")) + list.removeFirst(); + if(!list.isEmpty()) + return list; + } + } + + // Classic fallbacks + if (!qEnvironmentVariableIsEmpty("KDE_FULL_SESSION")) + return {QByteArrayLiteral("KDE")}; + + // Fallback to checking $DESKTOP_SESSION (unreliable) + QByteArray desktopSession = qgetenv("DESKTOP_SESSION"); + + // This can be a path in /usr/share/xsessions + int slash = desktopSession.lastIndexOf('/'); + // try decoding just the basename + desktopSession = desktopSession.mid(slash + 1); + + if (desktopSession == "kde" || desktopSession == "plasma") + return {QByteArrayLiteral("KDE")}; + + return {}; +} + QString findBestBackend() { QStringList dirs; - dirs << QProcessEnvironment::systemEnvironment().value(QStringLiteral("LXQTPANEL_PLUGIN_PATH")).split(QStringLiteral(":")); + + // LXQTPANEL_PLUGIN_PATH is not always defined, skip if empty + QStringList pluginPaths = QProcessEnvironment::systemEnvironment() + .value(QStringLiteral("LXQTPANEL_PLUGIN_PATH")) + .split(QStringLiteral(":"), Qt::SkipEmptyParts); + if(!pluginPaths.isEmpty()) + dirs << pluginPaths; + dirs << QStringLiteral(PLUGIN_DIR); QString lastBackendFile; int lastBackendScore = 0; - for(const QString& dir : std::as_const(dirs)) + QList desktops = detectDesktopEnvironment(); + for(const QByteArray& desktop : desktops) { - QDir backendsDir(dir); - backendsDir.cd(QLatin1String("backend")); + QString key = QString::fromUtf8(desktop); - const auto entryList = backendsDir.entryList(QDir::Files); - for(const QString& fileName : entryList) + for(const QString& dir : std::as_const(dirs)) { - const QString absPath = backendsDir.absoluteFilePath(fileName); - QPluginLoader loader(absPath); - loader.load(); - if(!loader.isLoaded()) + QDir backendsDir(dir); + backendsDir.cd(QLatin1String("backend")); + + const auto entryList = backendsDir.entryList(QDir::Files); + for(const QString& fileName : entryList) { - QString err = loader.errorString(); - qWarning() << "Backend error:" << err; - } + const QString absPath = backendsDir.absoluteFilePath(fileName); + QPluginLoader loader(absPath); + loader.load(); + if(!loader.isLoaded()) + { + QString err = loader.errorString(); + qWarning() << "Backend error:" << err; + } - QObject *plugin = loader.instance(); - if(!plugin) - continue; + QObject *plugin = loader.instance(); + if(!plugin) + continue; - ILXQtWMBackendLibrary *backend = qobject_cast(plugin); - if(backend) - { - int score = backend->getBackendScore(); - if(score > lastBackendScore) + ILXQtWMBackendLibrary *backend = qobject_cast(plugin); + if(backend) { - lastBackendFile = absPath; - lastBackendScore = score; + int score = backend->getBackendScore(key); + if(score > lastBackendScore) + { + lastBackendFile = absPath; + lastBackendScore = score; + } } + loader.unload(); } - loader.unload(); } + + // Double the score before going to next key + lastBackendScore *= 2; } if(lastBackendScore == 0) From dc336dfd54488be5181c0dd12b1b74775d2762dd Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 20 Aug 2024 13:22:20 +0200 Subject: [PATCH 07/15] Backends: change name scheme libwmbackend_.so --- panel/backends/xcb/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/panel/backends/xcb/CMakeLists.txt b/panel/backends/xcb/CMakeLists.txt index 08f2fe4b1..76e5c98a4 100644 --- a/panel/backends/xcb/CMakeLists.txt +++ b/panel/backends/xcb/CMakeLists.txt @@ -1,6 +1,9 @@ -set(NAME xcb_backend) +set(PLATFORM_NAME xcb) + +set(PREFIX_NAME wmbackend) set(PROGRAM "lxqt-panel") set(BACKEND "backend") +set(NAME ${PREFIX_NAME}_${PLATFORM_NAME}) project(${PROGRAM}_${BACKEND}_${NAME}) set(PROG_SHARE_DIR ${CMAKE_INSTALL_FULL_DATAROOTDIR}/lxqt/${PROGRAM}/${BACKEND}) From 30cf17aa5f692ebb8754c8076628f382eeeff4fc Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 20 Aug 2024 13:38:38 +0200 Subject: [PATCH 08/15] LXQtPanelApplication: only consider plugins with valid names --- panel/lxqtpanelapplication.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/panel/lxqtpanelapplication.cpp b/panel/lxqtpanelapplication.cpp index c88e8c9c2..5b4a0d6c1 100644 --- a/panel/lxqtpanelapplication.cpp +++ b/panel/lxqtpanelapplication.cpp @@ -104,8 +104,13 @@ QString findBestBackend() for(const QString& dir : std::as_const(dirs)) { QDir backendsDir(dir); - backendsDir.cd(QLatin1String("backend")); + if ( QFile::exists( dir + QStringLiteral("/backend") ) ) + { + backendsDir.cd(QLatin1String("backend")); + } + + backendsDir.setNameFilters({QLatin1String("libwmbackend_*.so")}); const auto entryList = backendsDir.entryList(QDir::Files); for(const QString& fileName : entryList) { From 1ae3ff2eca4fd1b5962467167c33712bfe1f946d Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 20 Aug 2024 13:58:17 +0200 Subject: [PATCH 09/15] LXQtPanelApplication: fix empty backend message --- panel/lxqtpanelapplication.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/panel/lxqtpanelapplication.cpp b/panel/lxqtpanelapplication.cpp index 5b4a0d6c1..59dd09eb0 100644 --- a/panel/lxqtpanelapplication.cpp +++ b/panel/lxqtpanelapplication.cpp @@ -226,6 +226,7 @@ void LXQtPanelApplicationPrivate::loadBackend() if(backend) { mWMBackend = backend->instance(); + preferredBackend = fileName; } else { From 5499594e8a0b38127d9e4cfc07f0a57e5fcca253 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Sat, 17 Feb 2024 19:18:09 +0100 Subject: [PATCH 10/15] TaskBar: add experimental KWin Wayland backend NOTE: works only on KWin - Choose backend at runtime - Windows filter logic is re-evaluated on window property changes LXQtTaskBarPlasmaWindowManagment: implement showDesktop() LXQtTaskbarWaylandBackend: do not show transient windows LXQtTaskBarPlasmaWindowManagment: fix destructor TODO TODO: is this correct? Seems to call wl_proxy_destroy underneath LXQtPanel: basic virtual desktop support on Plasma Wayland Add desktop file to be recognized by KWin Wayland NOTE: absolute path is needed inside .desktop file for this to work use CMake to get it. - Prevent double dekstop file installed in autostart LXQtTaskbarWaylandBackend: return only accepted windows - reloadWindows() force removal and readding of windows This fixes changing windows grouping settings and adding taskbar plugin AFTER panel is started. Both situations resulted in empty taskbar previously LXQtTaskbarWaylandBackend: fix workspace logic LXQtTaskbarWaylandBackend: fix workspace removal logic LXQtTaskbarWaylandBackend: implement moving window to virtual desktop workspace LXQtPlasmaWaylandWorkspaceInfo: fix signedness comparison CMake: move panel WM backends to separate libraries LXQtTaskbarWaylandBackend: possibly fix crash on showDesktop for non- KWin Update license headers LXQtTaskbarWaylandBackend: add dummy setDesktopLayout() Implement LXQtWMBackendKWinWaylandLibrary - Add Desktop Environment detection --- autostart/CMakeLists.txt | 11 +- autostart/lxqt-panel_wayland.desktop.in | 13 + panel/backends/CMakeLists.txt | 1 + panel/backends/wayland/CMakeLists.txt | 1 + .../wayland/kwin_wayland/CMakeLists.txt | 43 + .../kwin_wayland/lxqtplasmavirtualdesktop.cpp | 258 ++++++ .../kwin_wayland/lxqtplasmavirtualdesktop.h | 99 +++ .../lxqttaskbarplasmawindowmanagment.cpp | 311 +++++++ .../lxqttaskbarplasmawindowmanagment.h | 170 ++++ .../lxqtwmbackend_kwinwayland.cpp | 777 ++++++++++++++++++ .../kwin_wayland/lxqtwmbackend_kwinwayland.h | 135 +++ .../org-kde-plasma-virtual-desktop.xml | 110 +++ .../protocols/plasma-window-management.xml | 425 ++++++++++ .../org-kde-plasma-virtual-desktop.xml | 110 +++ .../protocols/plasma-window-management.xml | 425 ++++++++++ 15 files changed, 2888 insertions(+), 1 deletion(-) create mode 100644 autostart/lxqt-panel_wayland.desktop.in create mode 100644 panel/backends/wayland/CMakeLists.txt create mode 100644 panel/backends/wayland/kwin_wayland/CMakeLists.txt create mode 100644 panel/backends/wayland/kwin_wayland/lxqtplasmavirtualdesktop.cpp create mode 100644 panel/backends/wayland/kwin_wayland/lxqtplasmavirtualdesktop.h create mode 100644 panel/backends/wayland/kwin_wayland/lxqttaskbarplasmawindowmanagment.cpp create mode 100644 panel/backends/wayland/kwin_wayland/lxqttaskbarplasmawindowmanagment.h create mode 100644 panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp create mode 100644 panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.h create mode 100644 panel/backends/wayland/kwin_wayland/protocols/org-kde-plasma-virtual-desktop.xml create mode 100644 panel/backends/wayland/kwin_wayland/protocols/plasma-window-management.xml create mode 100644 panel/backends/wayland/protocols/org-kde-plasma-virtual-desktop.xml create mode 100644 panel/backends/wayland/protocols/plasma-window-management.xml diff --git a/autostart/CMakeLists.txt b/autostart/CMakeLists.txt index 098103168..6d044738b 100644 --- a/autostart/CMakeLists.txt +++ b/autostart/CMakeLists.txt @@ -1,4 +1,4 @@ -file(GLOB DESKTOP_FILES_IN *.desktop.in) +set(DESKTOP_FILES lxqt-panel.desktop.in) # Translations ********************************** lxqt_translate_desktop(DESKTOP_FILES @@ -14,3 +14,12 @@ install(FILES DESTINATION "${LXQT_ETC_XDG_DIR}/autostart" COMPONENT Runtime ) + +configure_file(lxqt-panel_wayland.desktop.in lxqt-panel_wayland.desktop @ONLY) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/lxqt-panel_wayland.desktop" + DESTINATION "/usr/share/applications" + RENAME "lxqt-panel.desktop" + COMPONENT Runtime +) diff --git a/autostart/lxqt-panel_wayland.desktop.in b/autostart/lxqt-panel_wayland.desktop.in new file mode 100644 index 000000000..089082aea --- /dev/null +++ b/autostart/lxqt-panel_wayland.desktop.in @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Application +TryExec=lxqt-panel + +# NOTE: KWin wants absolute path here, get it from CMake install path +Exec=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/lxqt-panel + +# NOTE: adding KDE to make it work under Plasma Wayland session +OnlyShowIn=LXQt;KDE +X-LXQt-Module=true + +# Make KWin recognize us as priviledged client +X-KDE-Wayland-Interfaces=org_kde_plasma_window_management diff --git a/panel/backends/CMakeLists.txt b/panel/backends/CMakeLists.txt index cf117c7e3..3c47cc9bd 100644 --- a/panel/backends/CMakeLists.txt +++ b/panel/backends/CMakeLists.txt @@ -15,4 +15,5 @@ target_link_libraries(lxqt-panel-backend-common Qt6::Gui ) +add_subdirectory(wayland) add_subdirectory(xcb) diff --git a/panel/backends/wayland/CMakeLists.txt b/panel/backends/wayland/CMakeLists.txt new file mode 100644 index 000000000..3f2c93189 --- /dev/null +++ b/panel/backends/wayland/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(kwin_wayland) diff --git a/panel/backends/wayland/kwin_wayland/CMakeLists.txt b/panel/backends/wayland/kwin_wayland/CMakeLists.txt new file mode 100644 index 000000000..a4a097f69 --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/CMakeLists.txt @@ -0,0 +1,43 @@ +set(PLATFORM_NAME kwin_wayland) + +set(PREFIX_NAME wmbackend) +set(PROGRAM "lxqt-panel") +set(BACKEND "backend") +set(NAME ${PREFIX_NAME}_${PLATFORM_NAME}) +project(${PROGRAM}_${BACKEND}_${NAME}) + +find_package(Qt6 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS WaylandClient Concurrent) + +set(PROG_SHARE_DIR ${CMAKE_INSTALL_FULL_DATAROOTDIR}/lxqt/${PROGRAM}/${BACKEND}) +set(PLUGIN_SHARE_DIR ${PROG_SHARE_DIR}/${BACKEND}/${NAME}) +#************************************************ + +if (NOT DEFINED PLUGIN_DIR) + set (PLUGIN_DIR ${CMAKE_INSTALL_FULL_LIBDIR}/${PROGRAM}) +endif (NOT DEFINED PLUGIN_DIR) + +set(QTX_LIBRARIES Qt6::Gui) + +set(SRC + lxqtwmbackend_kwinwayland.h + lxqtwmbackend_kwinwayland.cpp + + lxqtplasmavirtualdesktop.h + lxqtplasmavirtualdesktop.cpp + + lxqttaskbarplasmawindowmanagment.h + lxqttaskbarplasmawindowmanagment.cpp +) + +add_library(${NAME} MODULE ${SRC}) # build dynamically loadable modules +install(TARGETS ${NAME} DESTINATION ${PLUGIN_DIR}/${BACKEND}) # install the *.so file + +target_link_libraries(${NAME} ${QTX_LIBRARIES} Qt6::Concurrent Qt6::WaylandClient) + +qt6_generate_wayland_protocol_client_sources(${NAME} FILES + ${CMAKE_CURRENT_SOURCE_DIR}/protocols/plasma-window-management.xml +) + +qt6_generate_wayland_protocol_client_sources(${NAME} FILES + ${CMAKE_CURRENT_SOURCE_DIR}/protocols/org-kde-plasma-virtual-desktop.xml +) diff --git a/panel/backends/wayland/kwin_wayland/lxqtplasmavirtualdesktop.cpp b/panel/backends/wayland/kwin_wayland/lxqtplasmavirtualdesktop.cpp new file mode 100644 index 000000000..d26574e62 --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/lxqtplasmavirtualdesktop.cpp @@ -0,0 +1,258 @@ +/* + SPDX-FileCopyrightText: 2016 Eike Hein + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + + Adapted from KDE Plasma Workspace: plasma-workspace/libtaskmanager/waylandtasksmodel.cpp +*/ + + +#include "lxqtplasmavirtualdesktop.h" + +#include + +LXQtPlasmaVirtualDesktop::LXQtPlasmaVirtualDesktop(::org_kde_plasma_virtual_desktop *object, const QString &id) + : org_kde_plasma_virtual_desktop(object) + , id(id) +{ +} + +LXQtPlasmaVirtualDesktop::~LXQtPlasmaVirtualDesktop() +{ + wl_proxy_destroy(reinterpret_cast(object())); +} + +void LXQtPlasmaVirtualDesktop::org_kde_plasma_virtual_desktop_name(const QString &name) +{ + this->name = name; + Q_EMIT nameChanged(); +} + +void LXQtPlasmaVirtualDesktop::org_kde_plasma_virtual_desktop_done() +{ + Q_EMIT done(); +} + +void LXQtPlasmaVirtualDesktop::org_kde_plasma_virtual_desktop_activated() +{ + Q_EMIT activated(); +} + +LXQtPlasmaVirtualDesktopManagment::LXQtPlasmaVirtualDesktopManagment() + : QWaylandClientExtensionTemplate(version) +{ + connect(this, &QWaylandClientExtension::activeChanged, this, [this] { + if (!isActive()) { + org_kde_plasma_virtual_desktop_management_destroy(object()); + } + }); +} + +LXQtPlasmaVirtualDesktopManagment::~LXQtPlasmaVirtualDesktopManagment() +{ + if (isActive()) { + org_kde_plasma_virtual_desktop_management_destroy(object()); + } +} + +void LXQtPlasmaVirtualDesktopManagment::org_kde_plasma_virtual_desktop_management_desktop_created(const QString &desktop_id, uint32_t position) +{ + emit desktopCreated(desktop_id, position); +} + +void LXQtPlasmaVirtualDesktopManagment::org_kde_plasma_virtual_desktop_management_desktop_removed(const QString &desktop_id) +{ + emit desktopRemoved(desktop_id); +} + +void LXQtPlasmaVirtualDesktopManagment::org_kde_plasma_virtual_desktop_management_rows(uint32_t rows) +{ + emit rowsChanged(rows); +} + +LXQtPlasmaWaylandWorkspaceInfo::LXQtPlasmaWaylandWorkspaceInfo() +{ + init(); +} + +LXQtPlasmaWaylandWorkspaceInfo::VirtualDesktopsIterator LXQtPlasmaWaylandWorkspaceInfo::findDesktop(const QString &id) const +{ + return std::find_if(virtualDesktops.begin(), virtualDesktops.end(), + [&id](const std::unique_ptr &desktop) { + return desktop->id == id; + }); +} + +QString LXQtPlasmaWaylandWorkspaceInfo::getDesktopName(int pos) const +{ + if(pos < 0 || size_t(pos) >= virtualDesktops.size()) + return QString(); + return virtualDesktops[pos]->name; +} + +QString LXQtPlasmaWaylandWorkspaceInfo::getDesktopId(int pos) const +{ + if(pos < 0 || size_t(pos) >= virtualDesktops.size()) + return QString(); + return virtualDesktops[pos]->id; +} + +void LXQtPlasmaWaylandWorkspaceInfo::init() +{ + virtualDesktopManagement = std::make_unique(); + + connect(virtualDesktopManagement.get(), &LXQtPlasmaVirtualDesktopManagment::activeChanged, this, [this] { + if (!virtualDesktopManagement->isActive()) { + rows = 0; + virtualDesktops.clear(); + currentVirtualDesktop.clear(); + Q_EMIT currentDesktopChanged(); + Q_EMIT numberOfDesktopsChanged(); + Q_EMIT navigationWrappingAroundChanged(); + Q_EMIT desktopIdsChanged(); + Q_EMIT desktopLayoutRowsChanged(); + } + }); + + connect(virtualDesktopManagement.get(), &LXQtPlasmaVirtualDesktopManagment::desktopCreated, + this, &LXQtPlasmaWaylandWorkspaceInfo::addDesktop); + + connect(virtualDesktopManagement.get(), &LXQtPlasmaVirtualDesktopManagment::desktopRemoved, this, [this](const QString &id) { + + + virtualDesktops.erase(std::remove_if(virtualDesktops.begin(), virtualDesktops.end(), + [id](const std::unique_ptr &desktop) + { + return desktop->id == id; + }), + virtualDesktops.end()); + + Q_EMIT numberOfDesktopsChanged(); + Q_EMIT desktopIdsChanged(); + + if (currentVirtualDesktop == id) { + currentVirtualDesktop.clear(); + Q_EMIT currentDesktopChanged(); + } + }); + + connect(virtualDesktopManagement.get(), &LXQtPlasmaVirtualDesktopManagment::rowsChanged, this, [this](quint32 rows) { + this->rows = rows; + Q_EMIT desktopLayoutRowsChanged(); + }); +} + +void LXQtPlasmaWaylandWorkspaceInfo::addDesktop(const QString &id, quint32 pos) +{ + if (findDesktop(id) != virtualDesktops.end()) { + return; + } + + auto desktop = std::make_unique(virtualDesktopManagement->get_virtual_desktop(id), id); + + connect(desktop.get(), &LXQtPlasmaVirtualDesktop::activated, this, [id, this]() { + currentVirtualDesktop = id; + Q_EMIT currentDesktopChanged(); + }); + + connect(desktop.get(), &LXQtPlasmaVirtualDesktop::nameChanged, this, [id, this]() { + Q_EMIT desktopNameChanged(position(id)); + }); + + connect(desktop.get(), &LXQtPlasmaVirtualDesktop::done, this, [id, this]() { + Q_EMIT desktopNameChanged(position(id)); + }); + + virtualDesktops.insert(std::next(virtualDesktops.begin(), pos), std::move(desktop)); + + Q_EMIT numberOfDesktopsChanged(); + Q_EMIT desktopIdsChanged(); + Q_EMIT desktopNameChanged(position(id)); +} + +QVariant LXQtPlasmaWaylandWorkspaceInfo::currentDesktop() const +{ + return currentVirtualDesktop; +} + +int LXQtPlasmaWaylandWorkspaceInfo::numberOfDesktops() const +{ + return virtualDesktops.size(); +} + +quint32 LXQtPlasmaWaylandWorkspaceInfo::position(const QVariant &desktop) const +{ + return std::distance(virtualDesktops.begin(), findDesktop(desktop.toString())); +} + +QVariantList LXQtPlasmaWaylandWorkspaceInfo::desktopIds() const +{ + QVariantList ids; + ids.reserve(virtualDesktops.size()); + + std::transform(virtualDesktops.cbegin(), virtualDesktops.cend(), std::back_inserter(ids), [](const std::unique_ptr &desktop) { + return desktop->id; + }); + return ids; +} + +QStringList LXQtPlasmaWaylandWorkspaceInfo::desktopNames() const +{ + if (!virtualDesktopManagement->isActive()) { + return QStringList(); + } + QStringList names; + names.reserve(virtualDesktops.size()); + + std::transform(virtualDesktops.cbegin(), virtualDesktops.cend(), std::back_inserter(names), [](const std::unique_ptr &desktop) { + return desktop->name; + }); + return names; +} + +int LXQtPlasmaWaylandWorkspaceInfo::desktopLayoutRows() const +{ + if (!virtualDesktopManagement->isActive()) { + return 0; + } + + return rows; +} + +void LXQtPlasmaWaylandWorkspaceInfo::requestActivate(const QVariant &desktop) +{ + if (!virtualDesktopManagement->isActive()) { + return; + } + + if (auto it = findDesktop(desktop.toString()); it != virtualDesktops.end()) { + (*it)->request_activate(); + } +} + +void LXQtPlasmaWaylandWorkspaceInfo::requestCreateDesktop(quint32 position) +{ + if (!virtualDesktopManagement->isActive()) { + return; + } + + //TODO: translatestd + virtualDesktopManagement->request_create_virtual_desktop(QLatin1String("New Desktop"), position); +} + +void LXQtPlasmaWaylandWorkspaceInfo::requestRemoveDesktop(quint32 position) +{ + if (!virtualDesktopManagement->isActive()) { + return; + } + if (virtualDesktops.size() == 1) { + return; + } + + if (position > (virtualDesktops.size() - 1)) { + return; + } + + virtualDesktopManagement->request_remove_virtual_desktop(virtualDesktops.at(position)->id); +} + diff --git a/panel/backends/wayland/kwin_wayland/lxqtplasmavirtualdesktop.h b/panel/backends/wayland/kwin_wayland/lxqtplasmavirtualdesktop.h new file mode 100644 index 000000000..16935be1a --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/lxqtplasmavirtualdesktop.h @@ -0,0 +1,99 @@ +/* + SPDX-FileCopyrightText: 2016 Eike Hein + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + + Adapted from KDE Plasma Workspace: plasma-workspace/libtaskmanager/waylandtasksmodel.cpp +*/ + +#ifndef LXQTPLASMAVIRTUALDESKTOP_H +#define LXQTPLASMAVIRTUALDESKTOP_H + +#include +#include + +#include + +#include "qwayland-org-kde-plasma-virtual-desktop.h" + +class LXQtPlasmaVirtualDesktop : public QObject, public QtWayland::org_kde_plasma_virtual_desktop +{ + Q_OBJECT +public: + LXQtPlasmaVirtualDesktop(::org_kde_plasma_virtual_desktop *object, const QString &id); + ~LXQtPlasmaVirtualDesktop(); + const QString id; + QString name; +Q_SIGNALS: + void done(); + void activated(); + void nameChanged(); + +protected: + void org_kde_plasma_virtual_desktop_name(const QString &name) override; + void org_kde_plasma_virtual_desktop_done() override; + void org_kde_plasma_virtual_desktop_activated() override; +}; + + +class LXQtPlasmaVirtualDesktopManagment : public QWaylandClientExtensionTemplate, + public QtWayland::org_kde_plasma_virtual_desktop_management +{ + Q_OBJECT +public: + static constexpr int version = 2; + + LXQtPlasmaVirtualDesktopManagment(); + ~LXQtPlasmaVirtualDesktopManagment(); + +signals: + void desktopCreated(const QString &id, quint32 position); + void desktopRemoved(const QString &id); + void rowsChanged(const quint32 rows); + +protected: + virtual void org_kde_plasma_virtual_desktop_management_desktop_created(const QString &desktop_id, uint32_t position) override; + virtual void org_kde_plasma_virtual_desktop_management_desktop_removed(const QString &desktop_id) override; + virtual void org_kde_plasma_virtual_desktop_management_rows(uint32_t rows) override; +}; + +class Q_DECL_HIDDEN LXQtPlasmaWaylandWorkspaceInfo : public QObject +{ + Q_OBJECT +public: + LXQtPlasmaWaylandWorkspaceInfo(); + + QVariant currentVirtualDesktop; + std::vector> virtualDesktops; + std::unique_ptr virtualDesktopManagement; + quint32 rows; + + typedef std::vector>::const_iterator VirtualDesktopsIterator; + + VirtualDesktopsIterator findDesktop(const QString &id) const; + + QString getDesktopName(int pos) const; + QString getDesktopId(int pos) const; + + void init(); + void addDesktop(const QString &id, quint32 pos); + QVariant currentDesktop() const; + int numberOfDesktops() const; + QVariantList desktopIds() const; + QStringList desktopNames() const; + quint32 position(const QVariant &desktop) const; + int desktopLayoutRows() const; + void requestActivate(const QVariant &desktop); + void requestCreateDesktop(quint32 position); + void requestRemoveDesktop(quint32 position); + +signals: + void currentDesktopChanged(); + void numberOfDesktopsChanged(); + void navigationWrappingAroundChanged(); + void desktopIdsChanged(); + void desktopNameChanged(quint32 position); + void desktopLayoutRowsChanged(); +}; + +#endif // LXQTPLASMAVIRTUALDESKTOP_H diff --git a/panel/backends/wayland/kwin_wayland/lxqttaskbarplasmawindowmanagment.cpp b/panel/backends/wayland/kwin_wayland/lxqttaskbarplasmawindowmanagment.cpp new file mode 100644 index 000000000..d64ee9b0d --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/lxqttaskbarplasmawindowmanagment.cpp @@ -0,0 +1,311 @@ +/* + SPDX-FileCopyrightText: 2016 Eike Hein + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + + Adapted from KDE Plasma Workspace: plasma-workspace/libtaskmanager/waylandtasksmodel.cpp +*/ + +#include "lxqttaskbarplasmawindowmanagment.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * LXQtTaskBarPlasmaWindow + */ + +LXQtTaskBarPlasmaWindow::LXQtTaskBarPlasmaWindow(const QString &uuid, ::org_kde_plasma_window *id) + : org_kde_plasma_window(id) + , uuid(uuid) +{ +} + +LXQtTaskBarPlasmaWindow::~LXQtTaskBarPlasmaWindow() +{ + destroy(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_unmapped() +{ + wasUnmapped = true; + Q_EMIT unmapped(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_title_changed(const QString &title) +{ + if(this->title == title) + return; + this->title = title; + Q_EMIT titleChanged(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_app_id_changed(const QString &app_id) +{ + if(appId == app_id) + return; + appId = app_id; + Q_EMIT appIdChanged(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_icon_changed() +{ + int pipeFds[2]; + if (pipe2(pipeFds, O_CLOEXEC) != 0) { + qWarning() << "TaskManager: failed creating pipe"; + return; + } + get_icon(pipeFds[1]); + ::close(pipeFds[1]); + auto readIcon = [uuid = uuid](int fd) { + auto closeGuard = qScopeGuard([fd]() { + ::close(fd); + }); + pollfd pollFd; + pollFd.fd = fd; + pollFd.events = POLLIN; + QByteArray data; + while (true) { + int ready = poll(&pollFd, 1, 1000); + if (ready < 0 && errno != EINTR) { + qWarning() << "TaskManager: polling for icon of window" << uuid << "failed"; + return QIcon(); + } else if (ready == 0) { + qWarning() << "TaskManager: time out polling for icon of window" << uuid; + return QIcon(); + } else { + char buffer[4096]; + int n = read(fd, buffer, sizeof(buffer)); + if (n < 0) { + qWarning() << "TaskManager: error reading icon of window" << uuid; + return QIcon(); + } else if (n > 0) { + data.append(buffer, n); + } else { + QIcon icon; + QDataStream ds(data); + ds >> icon; + return icon; + } + } + } + }; + QFuture future = QtConcurrent::run(readIcon, pipeFds[0]); + auto watcher = new QFutureWatcher(); + watcher->setFuture(future); + connect(watcher, &QFutureWatcher::finished, this, [this, watcher] { + icon = watcher->future().result(); + Q_EMIT iconChanged(); + }); + connect(watcher, &QFutureWatcher::finished, watcher, &QObject::deleteLater); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_themed_icon_name_changed(const QString &name) +{ + icon = QIcon::fromTheme(name); + Q_EMIT iconChanged(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_state_changed(uint32_t flags) +{ + auto diff = windowState ^ flags; + if (diff & state::state_active) { + windowState.setFlag(state::state_active, flags & state::state_active); + Q_EMIT activeChanged(); + } + if (diff & state::state_minimized) { + windowState.setFlag(state::state_minimized, flags & state::state_minimized); + Q_EMIT minimizedChanged(); + } + if (diff & state::state_maximized) { + windowState.setFlag(state::state_maximized, flags & state::state_maximized); + Q_EMIT maximizedChanged(); + } + if (diff & state::state_fullscreen) { + windowState.setFlag(state::state_fullscreen, flags & state::state_fullscreen); + Q_EMIT fullscreenChanged(); + } + if (diff & state::state_keep_above) { + windowState.setFlag(state::state_keep_above, flags & state::state_keep_above); + Q_EMIT keepAboveChanged(); + } + if (diff & state::state_keep_below) { + windowState.setFlag(state::state_keep_below, flags & state::state_keep_below); + Q_EMIT keepBelowChanged(); + } + if (diff & state::state_on_all_desktops) { + windowState.setFlag(state::state_on_all_desktops, flags & state::state_on_all_desktops); + Q_EMIT onAllDesktopsChanged(); + } + if (diff & state::state_demands_attention) { + windowState.setFlag(state::state_demands_attention, flags & state::state_demands_attention); + Q_EMIT demandsAttentionChanged(); + } + if (diff & state::state_closeable) { + windowState.setFlag(state::state_closeable, flags & state::state_closeable); + Q_EMIT closeableChanged(); + } + if (diff & state::state_minimizable) { + windowState.setFlag(state::state_minimizable, flags & state::state_minimizable); + Q_EMIT minimizeableChanged(); + } + if (diff & state::state_maximizable) { + windowState.setFlag(state::state_maximizable, flags & state::state_maximizable); + Q_EMIT maximizeableChanged(); + } + if (diff & state::state_fullscreenable) { + windowState.setFlag(state::state_fullscreenable, flags & state::state_fullscreenable); + Q_EMIT fullscreenableChanged(); + } + if (diff & state::state_skiptaskbar) { + windowState.setFlag(state::state_skiptaskbar, flags & state::state_skiptaskbar); + Q_EMIT skipTaskbarChanged(); + } + if (diff & state::state_shadeable) { + windowState.setFlag(state::state_shadeable, flags & state::state_shadeable); + Q_EMIT shadeableChanged(); + } + if (diff & state::state_shaded) { + windowState.setFlag(state::state_shaded, flags & state::state_shaded); + Q_EMIT shadedChanged(); + } + if (diff & state::state_movable) { + windowState.setFlag(state::state_movable, flags & state::state_movable); + Q_EMIT movableChanged(); + } + if (diff & state::state_resizable) { + windowState.setFlag(state::state_resizable, flags & state::state_resizable); + Q_EMIT resizableChanged(); + } + if (diff & state::state_virtual_desktop_changeable) { + windowState.setFlag(state::state_virtual_desktop_changeable, flags & state::state_virtual_desktop_changeable); + Q_EMIT virtualDesktopChangeableChanged(); + } + if (diff & state::state_skipswitcher) { + windowState.setFlag(state::state_skipswitcher, flags & state::state_skipswitcher); + Q_EMIT skipSwitcherChanged(); + } +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_virtual_desktop_entered(const QString &id) +{ + virtualDesktops.push_back(id); + Q_EMIT virtualDesktopEntered(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_virtual_desktop_left(const QString &id) +{ + virtualDesktops.removeAll(id); + Q_EMIT virtualDesktopLeft(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_geometry(int32_t x, int32_t y, uint32_t width, uint32_t height) +{ + geometry = QRect(x, y, width, height); + Q_EMIT geometryChanged(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_application_menu(const QString &service_name, const QString &object_path) +{ + applicationMenuService = service_name; + applicationMenuObjectPath = object_path; + Q_EMIT applicationMenuChanged(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_activity_entered(const QString &id) +{ + activities.push_back(id); + Q_EMIT activitiesChanged(); +} +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_activity_left(const QString &id) +{ + activities.removeAll(id); + Q_EMIT activitiesChanged(); +} +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_pid_changed(uint32_t pid) +{ + this->pid = pid; +} +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_resource_name_changed(const QString &resource_name) +{ + resourceName = resource_name; +} +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_parent_window(::org_kde_plasma_window *parent) +{ + LXQtTaskBarPlasmaWindow *parentWindow = nullptr; + if (parent) { + parentWindow = dynamic_cast(LXQtTaskBarPlasmaWindow::fromObject(parent)); + } + setParentWindow(parentWindow); +} +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_initial_state() +{ + Q_EMIT initialStateDone(); +} + +void LXQtTaskBarPlasmaWindow::setParentWindow(LXQtTaskBarPlasmaWindow *parent) +{ + const auto old = parentWindow; + QObject::disconnect(parentWindowUnmappedConnection); + + if (parent && !parent->wasUnmapped) { + parentWindow = QPointer(parent); + parentWindowUnmappedConnection = QObject::connect(parent, &LXQtTaskBarPlasmaWindow::unmapped, this, [this] { + setParentWindow(nullptr); + }); + } else { + parentWindow = QPointer(); + parentWindowUnmappedConnection = QMetaObject::Connection(); + } + + if (parentWindow.data() != old.data()) { + Q_EMIT parentWindowChanged(); + } +} + +/* + * LXQtTaskBarPlasmaWindowManagment + */ + +LXQtTaskBarPlasmaWindowManagment::LXQtTaskBarPlasmaWindowManagment() + : QWaylandClientExtensionTemplate(version) +{ + connect(this, &QWaylandClientExtension::activeChanged, this, [this] { + if (!isActive()) { + org_kde_plasma_window_management_destroy(object()); + } + }); +} + +LXQtTaskBarPlasmaWindowManagment::~LXQtTaskBarPlasmaWindowManagment() +{ + if (isActive()) { + org_kde_plasma_window_management_destroy(object()); + } +} + +void LXQtTaskBarPlasmaWindowManagment::org_kde_plasma_window_management_show_desktop_changed(uint32_t state) +{ + m_isShowingDesktop = (state == show_desktop::show_desktop_enabled); +} + +void LXQtTaskBarPlasmaWindowManagment::org_kde_plasma_window_management_window_with_uuid(uint32_t id, const QString &uuid) +{ + Q_UNUSED(id) + Q_EMIT windowCreated(new LXQtTaskBarPlasmaWindow(uuid, get_window_by_uuid(uuid))); +} +void LXQtTaskBarPlasmaWindowManagment::org_kde_plasma_window_management_stacking_order_uuid_changed(const QString &uuids) +{ + Q_EMIT stackingOrderChanged(uuids); +} diff --git a/panel/backends/wayland/kwin_wayland/lxqttaskbarplasmawindowmanagment.h b/panel/backends/wayland/kwin_wayland/lxqttaskbarplasmawindowmanagment.h new file mode 100644 index 000000000..b7c6d1f00 --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/lxqttaskbarplasmawindowmanagment.h @@ -0,0 +1,170 @@ +/* + SPDX-FileCopyrightText: 2016 Eike Hein + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + + Adapted from KDE Plasma Workspace: plasma-workspace/libtaskmanager/waylandtasksmodel.cpp +*/ + +#ifndef LXQTTASKBARPLASMAWINDOWMANAGMENT_H +#define LXQTTASKBARPLASMAWINDOWMANAGMENT_H + +#include +#include +#include + +#include "qwayland-plasma-window-management.h" + +typedef quintptr WId; + +class LXQtTaskBarPlasmaWindowManagment; + +class LXQtTaskBarPlasmaWindow : public QObject, + public QtWayland::org_kde_plasma_window +{ + Q_OBJECT +public: + LXQtTaskBarPlasmaWindow(const QString &uuid, ::org_kde_plasma_window *id); + ~LXQtTaskBarPlasmaWindow(); + + inline WId getWindowId() const { return reinterpret_cast(this); } + + using state = QtWayland::org_kde_plasma_window_management::state; + const QString uuid; + QString title; + QString appId; + QIcon icon; + QFlags windowState; + QList virtualDesktops; + QRect geometry; + QString applicationMenuService; + QString applicationMenuObjectPath; + QList activities; + quint32 pid; + QString resourceName; + QPointer parentWindow; + bool wasUnmapped = false; + bool acceptedInTaskBar = false; + +Q_SIGNALS: + void unmapped(); + void titleChanged(); + void appIdChanged(); + void iconChanged(); + void activeChanged(); + void minimizedChanged(); + void maximizedChanged(); + void fullscreenChanged(); + void keepAboveChanged(); + void keepBelowChanged(); + void onAllDesktopsChanged(); + void demandsAttentionChanged(); + void closeableChanged(); + void minimizeableChanged(); + void maximizeableChanged(); + void fullscreenableChanged(); + void skiptaskbarChanged(); + void shadeableChanged(); + void shadedChanged(); + void movableChanged(); + void resizableChanged(); + void virtualDesktopChangeableChanged(); + void skipSwitcherChanged(); + void virtualDesktopEntered(); + void virtualDesktopLeft(); + void geometryChanged(); + void skipTaskbarChanged(); + void applicationMenuChanged(); + void activitiesChanged(); + void parentWindowChanged(); + void initialStateDone(); + +protected: + void org_kde_plasma_window_unmapped() override; + void org_kde_plasma_window_title_changed(const QString &title) override; + void org_kde_plasma_window_app_id_changed(const QString &app_id) override; + void org_kde_plasma_window_icon_changed() override; + void org_kde_plasma_window_themed_icon_name_changed(const QString &name) override; + void org_kde_plasma_window_state_changed(uint32_t flags) override; + void org_kde_plasma_window_virtual_desktop_entered(const QString &id) override; + + void org_kde_plasma_window_virtual_desktop_left(const QString &id) override; + void org_kde_plasma_window_geometry(int32_t x, int32_t y, uint32_t width, uint32_t height) override; + void org_kde_plasma_window_application_menu(const QString &service_name, const QString &object_path) override; + void org_kde_plasma_window_activity_entered(const QString &id) override; + void org_kde_plasma_window_activity_left(const QString &id) override; + void org_kde_plasma_window_pid_changed(uint32_t pid) override; + void org_kde_plasma_window_resource_name_changed(const QString &resource_name) override; + void org_kde_plasma_window_parent_window(::org_kde_plasma_window *parent) override; + void org_kde_plasma_window_initial_state() override; + +private: + void setParentWindow(LXQtTaskBarPlasmaWindow *parent); + + QMetaObject::Connection parentWindowUnmappedConnection; +}; + +class LXQtTaskBarPlasmaWindowManagment : public QWaylandClientExtensionTemplate, + public QtWayland::org_kde_plasma_window_management +{ + Q_OBJECT +public: + static constexpr int version = 16; + + LXQtTaskBarPlasmaWindowManagment(); + ~LXQtTaskBarPlasmaWindowManagment(); + + inline bool isShowingDesktop() const { return m_isShowingDesktop; } + +protected: + void org_kde_plasma_window_management_show_desktop_changed(uint32_t state) override; + void org_kde_plasma_window_management_window_with_uuid(uint32_t id, const QString &uuid) override; + void org_kde_plasma_window_management_stacking_order_uuid_changed(const QString &uuids) override; + +Q_SIGNALS: + void windowCreated(LXQtTaskBarPlasmaWindow *window); + void stackingOrderChanged(const QString &uuids); + +private: + bool m_isShowingDesktop = false; +}; + +// class Q_DECL_HIDDEN WaylandTasksModel::Private +// { +// public: +// Private(WaylandTasksModel *q); +// QHash appDataCache; +// QHash lastActivated; +// PlasmaWindow *activeWindow = nullptr; +// std::vector> windows; +// // key=transient child, value=leader +// QHash transients; +// // key=leader, values=transient children +// QMultiHash transientsDemandingAttention; +// std::unique_ptr windowManagement; +// KSharedConfig::Ptr rulesConfig; +// KDirWatch *configWatcher = nullptr; +// VirtualDesktopInfo *virtualDesktopInfo = nullptr; +// static QUuid uuid; +// QList stackingOrder; + +// void init(); +// void initWayland(); +// auto findWindow(PlasmaWindow *window) const; +// void addWindow(PlasmaWindow *window); + +// const AppData &appData(PlasmaWindow *window); + +// QIcon icon(PlasmaWindow *window); + +// static QString mimeType(); +// static QString groupMimeType(); + +// void dataChanged(PlasmaWindow *window, int role); +// void dataChanged(PlasmaWindow *window, const QList &roles); + +// private: +// WaylandTasksModel *q; +// }; + +#endif // LXQTTASKBARPLASMAWINDOWMANAGMENT_H diff --git a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp new file mode 100644 index 000000000..5eb83d688 --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp @@ -0,0 +1,777 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + +#include "lxqtwmbackend_kwinwayland.h" + +#include "lxqttaskbarplasmawindowmanagment.h" +#include "lxqtplasmavirtualdesktop.h" + +#include +#include +#include + +auto findWindow(const std::vector>& windows, LXQtTaskBarPlasmaWindow *window) +{ + //TODO: use algorithms + auto end = windows.end(); + for(auto it = windows.begin(); it != end; it++) + { + if((*it).get() == window) + { + return it; + } + } + + return windows.end(); +} + +LXQtWMBackend_KWinWayland::LXQtWMBackend_KWinWayland(QObject *parent) : + ILXQtAbstractWMInterface(parent) +{ + m_managment.reset(new LXQtTaskBarPlasmaWindowManagment); + m_workspaceInfo.reset(new LXQtPlasmaWaylandWorkspaceInfo); + + connect(m_managment.get(), &LXQtTaskBarPlasmaWindowManagment::windowCreated, this, [this](LXQtTaskBarPlasmaWindow *window) { + connect(window, &LXQtTaskBarPlasmaWindow::initialStateDone, this, [this, window] { + addWindow(window); + }); + }); + + // connect(m_managment.get(), &LXQtTaskBarPlasmaWindowManagment::stackingOrderChanged, + // this, [this](const QString &order) { + // // stackingOrder = order.split(QLatin1Char(';')); + // // for (const auto &window : std::as_const(windows)) { + // // this->dataChanged(window.get(), StackingOrder); + // // } + // }); + + connect(m_workspaceInfo.get(), &LXQtPlasmaWaylandWorkspaceInfo::currentDesktopChanged, this, + [this](){ + int idx = m_workspaceInfo->position(m_workspaceInfo->currentDesktop()); + idx += 1; // Make 1-based + emit currentWorkspaceChanged(idx); + }); + + connect(m_workspaceInfo.get(), &LXQtPlasmaWaylandWorkspaceInfo::numberOfDesktopsChanged, + this, &ILXQtAbstractWMInterface::workspacesCountChanged); + + connect(m_workspaceInfo.get(), &LXQtPlasmaWaylandWorkspaceInfo::desktopNameChanged, + this, [this](int idx) { + emit workspaceNameChanged(idx + 1); // Make 1-based + }); +} + +bool LXQtWMBackend_KWinWayland::supportsAction(WId windowId, LXQtTaskBarBackendAction action) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + LXQtTaskBarPlasmaWindow::state state; + + switch (action) + { + case LXQtTaskBarBackendAction::Move: + state = LXQtTaskBarPlasmaWindow::state::state_movable; + break; + + case LXQtTaskBarBackendAction::Resize: + state = LXQtTaskBarPlasmaWindow::state::state_resizable; + break; + + case LXQtTaskBarBackendAction::Maximize: + state = LXQtTaskBarPlasmaWindow::state::state_maximizable; + break; + + case LXQtTaskBarBackendAction::Minimize: + state = LXQtTaskBarPlasmaWindow::state::state_minimizable; + break; + + case LXQtTaskBarBackendAction::RollUp: + state = LXQtTaskBarPlasmaWindow::state::state_shadeable; + break; + + case LXQtTaskBarBackendAction::FullScreen: + state = LXQtTaskBarPlasmaWindow::state::state_fullscreenable; + break; + + default: + return false; + } + + return window->windowState.testFlag(state); +} + +bool LXQtWMBackend_KWinWayland::reloadWindows() +{ + const QVector wids = getCurrentWindows(); + + // Force removal and re-adding + for(WId windowId : wids) + { + emit windowRemoved(windowId); + } + for(WId windowId : wids) + { + emit windowAdded(windowId); + } + + return true; +} + +QVector LXQtWMBackend_KWinWayland::getCurrentWindows() const +{ + QVector wids; + wids.reserve(wids.size()); + + for(const std::unique_ptr& window : std::as_const(windows)) + { + if(window->acceptedInTaskBar) + wids << window->getWindowId(); + } + return wids; +} + +QString LXQtWMBackend_KWinWayland::getWindowTitle(WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return QString(); + + return window->title; +} + +bool LXQtWMBackend_KWinWayland::applicationDemandsAttention(WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + return window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_demands_attention) || transientsDemandingAttention.contains(window); +} + +QIcon LXQtWMBackend_KWinWayland::getApplicationIcon(WId windowId, int devicePixels) const +{ + Q_UNUSED(devicePixels) + + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return QIcon(); + + return window->icon; +} + +QString LXQtWMBackend_KWinWayland::getWindowClass(WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return QString(); + return window->appId; +} + +LXQtTaskBarWindowLayer LXQtWMBackend_KWinWayland::getWindowLayer(WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return LXQtTaskBarWindowLayer::Normal; + + if(window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_keep_above)) + return LXQtTaskBarWindowLayer::KeepAbove; + + if(window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_keep_below)) + return LXQtTaskBarWindowLayer::KeepBelow; + + return LXQtTaskBarWindowLayer::Normal; +} + +bool LXQtWMBackend_KWinWayland::setWindowLayer(WId windowId, LXQtTaskBarWindowLayer layer) +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + if(getWindowLayer(windowId) == layer) + return true; //TODO: make more efficient + + LXQtTaskBarPlasmaWindow::state plasmaState = LXQtTaskBarPlasmaWindow::state::state_keep_above; + switch (layer) + { + case LXQtTaskBarWindowLayer::Normal: + case LXQtTaskBarWindowLayer::KeepAbove: + break; + case LXQtTaskBarWindowLayer::KeepBelow: + plasmaState = LXQtTaskBarPlasmaWindow::state::state_keep_below; + break; + default: + return false; + } + + window->set_state(plasmaState, layer == LXQtTaskBarWindowLayer::Normal ? 0 : plasmaState); + return false; +} + +LXQtTaskBarWindowState LXQtWMBackend_KWinWayland::getWindowState(WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return LXQtTaskBarWindowState::Normal; + + if(window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_minimized)) + return LXQtTaskBarWindowState::Hidden; + if(window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_maximizable)) + return LXQtTaskBarWindowState::Maximized; + if(window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_shaded)) + return LXQtTaskBarWindowState::RolledUp; + if(window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_fullscreen)) + return LXQtTaskBarWindowState::FullScreen; + + return LXQtTaskBarWindowState::Normal; +} + +bool LXQtWMBackend_KWinWayland::setWindowState(WId windowId, LXQtTaskBarWindowState state, bool set) +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + LXQtTaskBarPlasmaWindow::state plasmaState; + switch (state) + { + case LXQtTaskBarWindowState::Minimized: + { + plasmaState = LXQtTaskBarPlasmaWindow::state::state_minimized; + break; + } + case LXQtTaskBarWindowState::Maximized: + case LXQtTaskBarWindowState::MaximizedVertically: + case LXQtTaskBarWindowState::MaximizedHorizontally: + { + plasmaState = LXQtTaskBarPlasmaWindow::state::state_maximized; + break; + } + case LXQtTaskBarWindowState::Normal: + { + plasmaState = LXQtTaskBarPlasmaWindow::state::state_maximized; + set = !set; //TODO: correct + break; + } + case LXQtTaskBarWindowState::RolledUp: + { + plasmaState = LXQtTaskBarPlasmaWindow::state::state_shaded; + break; + } + default: + return false; + } + + window->set_state(plasmaState, set ? plasmaState : 0); + return true; +} + +bool LXQtWMBackend_KWinWayland::isWindowActive(WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + return activeWindow == window || window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_active); +} + +bool LXQtWMBackend_KWinWayland::raiseWindow(WId windowId, bool onCurrentWorkSpace) +{ + Q_UNUSED(onCurrentWorkSpace) //TODO + + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + // Pull forward any transient demanding attention. + if (auto *transientDemandingAttention = transientsDemandingAttention.value(window)) + { + window = transientDemandingAttention; + } + else + { + // TODO Shouldn't KWin take care of that? + // Bringing a transient to the front usually brings its parent with it + // but focus is not handled properly. + // TODO take into account d->lastActivation instead + // of just taking the first one. + while (transients.key(window)) + { + window = transients.key(window); + } + } + + window->set_state(LXQtTaskBarPlasmaWindow::state::state_active, LXQtTaskBarPlasmaWindow::state::state_active); + return true; +} + +bool LXQtWMBackend_KWinWayland::closeWindow(WId windowId) +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + window->close(); + return true; +} + +WId LXQtWMBackend_KWinWayland::getActiveWindow() const +{ + if(activeWindow) + return activeWindow->getWindowId(); + return 0; +} + +int LXQtWMBackend_KWinWayland::getWorkspacesCount() const +{ + return m_workspaceInfo->numberOfDesktops(); +} + +QString LXQtWMBackend_KWinWayland::getWorkspaceName(int idx) const +{ + return m_workspaceInfo->getDesktopName(idx - 1); //Return to 0-based +} + +int LXQtWMBackend_KWinWayland::getCurrentWorkspace() const +{ + if(!m_workspaceInfo->currentDesktop().isValid()) + return 0; + return m_workspaceInfo->position(m_workspaceInfo->currentDesktop()) + 1; // 1-based +} + +bool LXQtWMBackend_KWinWayland::setCurrentWorkspace(int idx) +{ + QString id = m_workspaceInfo->getDesktopId(idx - 1); //Return to 0-based + if(id.isEmpty()) + return false; + m_workspaceInfo->requestActivate(id); + return true; +} + +int LXQtWMBackend_KWinWayland::getWindowWorkspace(WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return 0; + + // TODO: this protocol seems to allow multiple desktop for each window + // We do not support that yet + // Also from KDE Plasma task switch it's not clear how to actually put + // a window on multiple desktops (which is different from "All desktops") + QString id = window->virtualDesktops.value(0, QString()); + if(id.isEmpty()) + return 0; + + return m_workspaceInfo->position(id) + 1; //Make 1-based +} + +bool LXQtWMBackend_KWinWayland::setWindowOnWorkspace(WId windowId, int idx) +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + // Prepare for future multiple virtual desktops per window + QList newDesktops; + + // Fill the list + newDesktops.append(m_workspaceInfo->getDesktopId(idx - 1)); //Return to 0-based + + // Keep only valid IDs + newDesktops.erase(std::remove_if(newDesktops.begin(), newDesktops.end(), + [](const QString& id) { return id.isEmpty(); }), + newDesktops.end()); + + // Add to new requested desktops + for(const QString& id : std::as_const(newDesktops)) + { + if(!window->virtualDesktops.contains(id)) + window->request_enter_virtual_desktop(id); + } + + // Remove from non-requested destops + const QList currentDesktops = window->virtualDesktops; + for(const QString& id : std::as_const(currentDesktops)) + { + if(!newDesktops.contains(id)) + window->request_leave_virtual_desktop(id); + } + + return true; +} + +void LXQtWMBackend_KWinWayland::moveApplicationToPrevNextMonitor(WId windowId, bool next, bool raiseOnCurrentDesktop) +{ + +} + +bool LXQtWMBackend_KWinWayland::isWindowOnScreen(QScreen *screen, WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + return screen->geometry().intersects(window->geometry); +} + +bool LXQtWMBackend_KWinWayland::setDesktopLayout(Qt::Orientation, int, int, bool) +{ + //TODO: implement + return false; +} + +void LXQtWMBackend_KWinWayland::moveApplication(WId windowId) +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return; + + window->set_state(LXQtTaskBarPlasmaWindow::state::state_active, LXQtTaskBarPlasmaWindow::state::state_active); + window->request_move(); +} + +void LXQtWMBackend_KWinWayland::resizeApplication(WId windowId) +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return; + + window->set_state(LXQtTaskBarPlasmaWindow::state::state_active, LXQtTaskBarPlasmaWindow::state::state_active); + window->request_resize(); +} + +void LXQtWMBackend_KWinWayland::refreshIconGeometry(WId windowId, const QRect &geom) +{ + +} + +bool LXQtWMBackend_KWinWayland::isAreaOverlapped(const QRect &area) const +{ + for(auto &window : std::as_const(windows)) + { + if(window->geometry.intersects(area)) + return true; + } + return false; +} + +bool LXQtWMBackend_KWinWayland::isShowingDesktop() const +{ + return m_managment->isActive() ? m_managment->isShowingDesktop() : false; +} + +bool LXQtWMBackend_KWinWayland::showDesktop(bool value) +{ + if(!m_managment->isActive()) + return false; + + enum LXQtTaskBarPlasmaWindowManagment::show_desktop flag_; + if(value) + flag_ = LXQtTaskBarPlasmaWindowManagment::show_desktop::show_desktop_enabled; + else + flag_ = LXQtTaskBarPlasmaWindowManagment::show_desktop::show_desktop_disabled; + + m_managment->show_desktop(flag_); + return true; +} + +void LXQtWMBackend_KWinWayland::addWindow(LXQtTaskBarPlasmaWindow *window) +{ + if (findWindow(windows, window) != windows.end() || transients.contains(window)) + { + return; + } + + auto removeWindow = [window, this] + { + auto it = findWindow(windows, window); + if (it != windows.end()) + { + if(window->acceptedInTaskBar) + emit windowRemoved(window->getWindowId()); + windows.erase(it); + transientsDemandingAttention.remove(window); + lastActivated.remove(window); + } + else + { + // Could be a transient. + // Removing a transient might change the demands attention state of the leader. + if (transients.remove(window)) + { + if (LXQtTaskBarPlasmaWindow *leader = transientsDemandingAttention.key(window)) { + transientsDemandingAttention.remove(leader, window); + emit windowPropertyChanged(leader->getWindowId(), int(LXQtTaskBarWindowProperty::Urgency)); + } + } + } + + if (activeWindow == window) + { + activeWindow = nullptr; + emit activeWindowChanged(0); + } + }; + + connect(window, &LXQtTaskBarPlasmaWindow::unmapped, this, removeWindow); + + connect(window, &LXQtTaskBarPlasmaWindow::titleChanged, this, [window, this] { + updateWindowAcceptance(window); + if(window->acceptedInTaskBar) + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::Title)); + }); + + connect(window, &LXQtTaskBarPlasmaWindow::iconChanged, this, [window, this] { + updateWindowAcceptance(window); + if(window->acceptedInTaskBar) + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::Icon)); + }); + + connect(window, &LXQtTaskBarPlasmaWindow::geometryChanged, this, [window, this] { + updateWindowAcceptance(window); + if(window->acceptedInTaskBar) + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::Geometry)); + }); + + connect(window, &LXQtTaskBarPlasmaWindow::appIdChanged, this, [window, this] { + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::WindowClass)); + }); + + if (window->windowState & LXQtTaskBarPlasmaWindow::state::state_active) { + LXQtTaskBarPlasmaWindow *effectiveActive = window; + while (effectiveActive->parentWindow) { + effectiveActive = effectiveActive->parentWindow; + } + + lastActivated[effectiveActive] = QTime::currentTime(); + activeWindow = effectiveActive; + } + + connect(window, &LXQtTaskBarPlasmaWindow::activeChanged, this, [window, this] { + const bool active = window->windowState & LXQtTaskBarPlasmaWindow::state::state_active; + + LXQtTaskBarPlasmaWindow *effectiveWindow = window; + + while (effectiveWindow->parentWindow) + { + effectiveWindow = effectiveWindow->parentWindow; + } + + if (active) + { + lastActivated[effectiveWindow] = QTime::currentTime(); + + if (activeWindow != effectiveWindow) + { + activeWindow = effectiveWindow; + emit activeWindowChanged(activeWindow->getWindowId()); + } + } + else + { + if (activeWindow == effectiveWindow) + { + activeWindow = nullptr; + emit activeWindowChanged(0); + } + } + }); + + connect(window, &LXQtTaskBarPlasmaWindow::parentWindowChanged, this, [window, this] { + LXQtTaskBarPlasmaWindow *leader = window->parentWindow.data(); + + // Migrate demanding attention to new leader. + if (window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_demands_attention)) + { + if (auto *oldLeader = transientsDemandingAttention.key(window)) + { + if (window->parentWindow != oldLeader) + { + transientsDemandingAttention.remove(oldLeader, window); + transientsDemandingAttention.insert(leader, window); + emit windowPropertyChanged(oldLeader->getWindowId(), int(LXQtTaskBarWindowProperty::Urgency)); + emit windowPropertyChanged(leader->getWindowId(), int(LXQtTaskBarWindowProperty::Urgency)); + } + } + } + + if (transients.remove(window)) + { + if (leader) + { + // leader change. + transients.insert(window, leader); + } + else + { + // lost a leader, add to regular windows list. + Q_ASSERT(findWindow(windows, window) == windows.end()); + + windows.emplace_back(window); + } + } + else if (leader) + { + // gained a leader, remove from regular windows list. + auto it = findWindow(windows, window); + Q_ASSERT(it != windows.end()); + + windows.erase(it); + lastActivated.remove(window); + } + }); + + auto stateChanged = [window, this] { + updateWindowAcceptance(window); + if(window->acceptedInTaskBar) + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::State)); + }; + + connect(window, &LXQtTaskBarPlasmaWindow::fullscreenChanged, this, stateChanged); + + connect(window, &LXQtTaskBarPlasmaWindow::maximizedChanged, this, stateChanged); + + connect(window, &LXQtTaskBarPlasmaWindow::minimizedChanged, this, stateChanged); + + connect(window, &LXQtTaskBarPlasmaWindow::shadedChanged, this, stateChanged); + + auto workspaceChanged = [window, this] { + updateWindowAcceptance(window); + if(window->acceptedInTaskBar) + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::Workspace)); + }; + + connect(window, &LXQtTaskBarPlasmaWindow::virtualDesktopEntered, this, workspaceChanged); + connect(window, &LXQtTaskBarPlasmaWindow::virtualDesktopLeft, this, workspaceChanged); + + connect(window, &LXQtTaskBarPlasmaWindow::demandsAttentionChanged, this, [window, this] { + // Changes to a transient's state might change demands attention state for leader. + if (auto *leader = transients.value(window)) + { + if (window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_demands_attention)) + { + if (!transientsDemandingAttention.values(leader).contains(window)) + { + transientsDemandingAttention.insert(leader, window); + emit windowPropertyChanged(leader->getWindowId(), int(LXQtTaskBarWindowProperty::Urgency)); + } + } + else if (transientsDemandingAttention.remove(window)) + { + emit windowPropertyChanged(leader->getWindowId(), int(LXQtTaskBarWindowProperty::Urgency)); + } + } + else + { + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::Urgency)); + } + }); + + connect(window, &LXQtTaskBarPlasmaWindow::skipTaskbarChanged, this, [window, this] { + updateWindowAcceptance(window); + }); + + // QObject::connect(window, &PlasmaWindow::applicationMenuChanged, q, [window, this] { + // this->dataChanged(window, QList{ApplicationMenuServiceName, ApplicationMenuObjectPath}); + // }); + + // Handle transient. + if (LXQtTaskBarPlasmaWindow *leader = window->parentWindow.data()) + { + transients.insert(window, leader); + + // Update demands attention state for leader. + if (window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_demands_attention)) + { + transientsDemandingAttention.insert(leader, window); + if(leader->acceptedInTaskBar) + emit windowPropertyChanged(leader->getWindowId(), int(LXQtTaskBarWindowProperty::Urgency)); + } + } + else + { + windows.emplace_back(window); + updateWindowAcceptance(window); + } +} + +bool LXQtWMBackend_KWinWayland::acceptWindow(LXQtTaskBarPlasmaWindow *window) const +{ + if(window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_skiptaskbar)) + return false; + + if(transients.contains(window)) + return false; + + return true; +} + +void LXQtWMBackend_KWinWayland::updateWindowAcceptance(LXQtTaskBarPlasmaWindow *window) +{ + if(!window->acceptedInTaskBar && acceptWindow(window)) + { + window->acceptedInTaskBar = true; + emit windowAdded(window->getWindowId()); + } + else if(window->acceptedInTaskBar && !acceptWindow(window)) + { + window->acceptedInTaskBar = false; + emit windowRemoved(window->getWindowId()); + } +} + +LXQtTaskBarPlasmaWindow *LXQtWMBackend_KWinWayland::getWindow(WId windowId) const +{ + for(auto &window : std::as_const(windows)) + { + if(window->getWindowId() == windowId) + return window.get(); + } + + return nullptr; +} + +int LXQtWMBackendKWinWaylandLibrary::getBackendScore(const QString &key) const +{ + auto *waylandApplication = qGuiApp->nativeInterface(); + if(!waylandApplication) + return 0; + + // Detect KWin Plasma + if(key == QLatin1String("KDE") || key == QLatin1String("KWIN")) + return 100; + + // It's not useful for other wayland compositors + return 0; +} + +ILXQtAbstractWMInterface *LXQtWMBackendKWinWaylandLibrary::instance() const +{ + return new LXQtWMBackend_KWinWayland; +} diff --git a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.h b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.h new file mode 100644 index 000000000..d44ae6ed6 --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.h @@ -0,0 +1,135 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + +#ifndef LXQT_WM_BACKEND_KWIN_WAYLAND_H +#define LXQT_WM_BACKEND_KWIN_WAYLAND_H + +#include "../../ilxqtabstractwmiface.h" + +#include +#include +#include + +class LXQtTaskBarPlasmaWindow; +class LXQtTaskBarPlasmaWindowManagment; +class LXQtPlasmaWaylandWorkspaceInfo; + + +class LXQtWMBackend_KWinWayland : public ILXQtAbstractWMInterface +{ + Q_OBJECT + +public: + explicit LXQtWMBackend_KWinWayland(QObject *parent = nullptr); + + // Backend + virtual bool supportsAction(WId windowId, LXQtTaskBarBackendAction action) const override; + + // Windows + virtual bool reloadWindows() override; + + virtual QVector getCurrentWindows() const override; + virtual QString getWindowTitle(WId windowId) const override; + virtual bool applicationDemandsAttention(WId windowId) const override; + virtual QIcon getApplicationIcon(WId windowId, int devicePixels) const override; + virtual QString getWindowClass(WId windowId) const override; + + virtual LXQtTaskBarWindowLayer getWindowLayer(WId windowId) const override; + virtual bool setWindowLayer(WId windowId, LXQtTaskBarWindowLayer layer) override; + + virtual LXQtTaskBarWindowState getWindowState(WId windowId) const override; + virtual bool setWindowState(WId windowId, LXQtTaskBarWindowState state, bool set) override; + + virtual bool isWindowActive(WId windowId) const override; + virtual bool raiseWindow(WId windowId, bool onCurrentWorkSpace) override; + + virtual bool closeWindow(WId windowId) override; + + virtual WId getActiveWindow() const override; + + // Workspaces + virtual int getWorkspacesCount() const override; + virtual QString getWorkspaceName(int idx) const override; + + virtual int getCurrentWorkspace() const override; + virtual bool setCurrentWorkspace(int idx) override; + + virtual int getWindowWorkspace(WId windowId) const override; + virtual bool setWindowOnWorkspace(WId windowId, int idx) override; + + virtual void moveApplicationToPrevNextMonitor(WId windowId, bool next, bool raiseOnCurrentDesktop) override; + + virtual bool isWindowOnScreen(QScreen *screen, WId windowId) const override; + + virtual bool setDesktopLayout(Qt::Orientation orientation, int rows, int columns, bool rightToLeft) override; + + // X11 Specific + virtual void moveApplication(WId windowId) override; + virtual void resizeApplication(WId windowId) override; + + virtual void refreshIconGeometry(WId windowId, const QRect &geom) override; + + // Panel internal + virtual bool isAreaOverlapped(const QRect& area) const override; + + // Show Destop + virtual bool isShowingDesktop() const override; + virtual bool showDesktop(bool value) override; + +private: + void addWindow(LXQtTaskBarPlasmaWindow *window); + bool acceptWindow(LXQtTaskBarPlasmaWindow *window) const; + void updateWindowAcceptance(LXQtTaskBarPlasmaWindow *window); + +private: + LXQtTaskBarPlasmaWindow *getWindow(WId windowId) const; + + std::unique_ptr m_workspaceInfo; + + std::unique_ptr m_managment; + + QHash lastActivated; + LXQtTaskBarPlasmaWindow *activeWindow = nullptr; + std::vector> windows; + // key=transient child, value=leader + QHash transients; + // key=leader, values=transient children + QMultiHash transientsDemandingAttention; +}; + +class LXQtWMBackendKWinWaylandLibrary: public QObject, public ILXQtWMBackendLibrary +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "lxqt.org/Panel/WMInterface/1.0") + Q_INTERFACES(ILXQtWMBackendLibrary) +public: + int getBackendScore(const QString& key) const override; + + ILXQtAbstractWMInterface* instance() const override; +}; + +#endif // LXQT_WM_BACKEND_KWIN_WAYLAND_H diff --git a/panel/backends/wayland/kwin_wayland/protocols/org-kde-plasma-virtual-desktop.xml b/panel/backends/wayland/kwin_wayland/protocols/org-kde-plasma-virtual-desktop.xml new file mode 100644 index 000000000..0e0551b0a --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/protocols/org-kde-plasma-virtual-desktop.xml @@ -0,0 +1,110 @@ + + + + + + + + Given the id of a particular virtual desktop, get the corresponding org_kde_plasma_virtual_desktop which represents only the desktop with that id. + + + + + + + + Ask the server to create a new virtual desktop, and position it at a specified position. If the position is zero or less, it will be positioned at the beginning, if the position is the count or more, it will be positioned at the end. + + + + + + + + Ask the server to get rid of a virtual desktop, the server may or may not acconsent to the request. + + + + + + + + + + + + + + + + + + + This event is sent after all other properties has been + sent after binding to the desktop manager object and after any + other property changes done after that. This allows + changes to the org_kde_plasma_virtual_desktop_management properties to be seen as + atomic, even if they happen via multiple events. + + + + + + + + + + + + + Request the server to set the status of this desktop to active: The server is free to consent or deny the request. This will be the new "current" virtual desktop of the system. + + + + + + The format of the id is decided by the compositor implementation. A desktop id univocally identifies a virtual desktop and must be guaranteed to never exist two desktops with the same id. The format of the string id is up to the server implementation. + + + + + + + + + + + The desktop will be the new "current" desktop of the system. The server may support either one virtual desktop active at a time, or other combinations such as one virtual desktop active per screen. + Windows associated to this virtual desktop will be shown. + + + + + + Windows that were associated only to this desktop will be hidden. + + + + + + This event is sent after all other properties has been + sent after binding to the desktop object and after any + other property changes done after that. This allows + changes to the org_kde_plasma_virtual_desktop properties to be seen as + atomic, even if they happen via multiple events. + + + + + + This virtual desktop has just been removed by the server: + All windows will lose the association to this desktop. + + + + + diff --git a/panel/backends/wayland/kwin_wayland/protocols/plasma-window-management.xml b/panel/backends/wayland/kwin_wayland/protocols/plasma-window-management.xml new file mode 100644 index 000000000..5990ebfda --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/protocols/plasma-window-management.xml @@ -0,0 +1,425 @@ + + + + + + + This interface manages application windows. + It provides requests to show and hide the desktop and emits + an event every time a window is created so that the client can + use it to manage the window. + + Only one client can bind this interface at a time. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tell the compositor to show/hide the desktop. + + + + + + Deprecated: use get_window_by_uuid + + + + + + + + + + + + This event will be sent whenever the show desktop mode changes. E.g. when it is entered + or left. + + On binding the interface the current state is sent. + + + + + + + This event will be sent immediately after a window is mapped. + + + + + + + This event will be sent when stacking order changed and on bind + + + + + + + This event will be sent when stacking order changed and on bind + + + + + + + This event will be sent immediately after a window is mapped. + + + + + + + + + Manages and control an application window. + + Only one client can bind this interface at a time. + + + + + Set window state. + + Values for state argument are described by org_kde_plasma_window_management.state + and can be used together in a bitfield. The flags bitfield describes which flags are + supposed to be set, the state bitfield the value for the set flags + + + + + + + + Deprecated: use enter_virtual_desktop + Maps the window to a different virtual desktop. + + To show the window on all virtual desktops, call the + org_kde_plasma_window.set_state request and specify a on_all_desktops + state in the bitfield. + + + + + + + Sets the geometry of the taskbar entry for this window. + The geometry is relative to a panel in particular. + + + + + + + + + + + Remove the task geometry information for a particular panel. + + + + + + + + + Close this window. + + + + + + Request an interactive move for this window. + + + + + + Request an interactive resize for this window. + + + + + + Removes the resource bound for this org_kde_plasma_window. + + + + + + The compositor will write the window icon into the provided file descriptor. + The data is a serialized QIcon with QDataStream. + + + + + + + This event will be sent as soon as the window title is changed. + + + + + + + This event will be sent as soon as the application + identifier is changed. + + + + + + + This event will be sent as soon as the window state changes. + + Values for state argument are described by org_kde_plasma_window_management.state. + + + + + + + DEPRECATED: use virtual_desktop_entered and virtual_desktop_left instead + This event will be sent when a window is moved to another + virtual desktop. + + It is not sent if it becomes visible on all virtual desktops though. + + + + + + + This event will be sent whenever the themed icon name changes. May be null. + + + + + + + This event will be sent immediately after the window is closed + and its surface is unmapped. + + + + + + This event will be sent immediately after all initial state been sent to the client. + If the Plasma window is already unmapped, the unmapped event will be sent before the + initial_state event. + + + + + + This event will be sent whenever the parent window of this org_kde_plasma_window changes. + The passed parent is another org_kde_plasma_window and this org_kde_plasma_window is a + transient window to the parent window. If the parent argument is null, this + org_kde_plasma_window does not have a parent window. + + + + + + + This event will be sent whenever the window geometry of this org_kde_plasma_window changes. + The coordinates are in absolute coordinates of the windowing system. + + + + + + + + + + This event will be sent whenever the icon of the window changes, but there is no themed + icon name. Common examples are Xwayland windows which have a pixmap based icon. + + The client can request the icon using get_icon. + + + + + + This event will be sent when the compositor has set the process id this window belongs to. + This should be set once before the initial_state is sent. + + + + + + + + + + Make the window enter a virtual desktop. A window can enter more + than one virtual desktop. if the id is empty or invalid, no action will be performed. + + + + + + RFC: do this with an empty id to request_enter_virtual_desktop? + Make the window enter a new virtual desktop. If the server consents the request, + it will create a new virtual desktop and assign the window to it. + + + + + + Make the window exit a virtual desktop. If it exits all desktops it will be considered on all of them. + + + + + + + This event will be sent when the window has entered a new virtual desktop. The window can be on more than one desktop, or none: then is considered on all of them. + + + + + + + This event will be sent when the window left a virtual desktop. If the window leaves all desktops, it can be considered on all. + If the window gets manually added on all desktops, the server has to send virtual_desktop_left for every previous desktop it was in for the window to be really considered on all desktops. + + + + + + + + + This event will be sent after the application menu + for the window has changed. + + + + + + + + Make the window enter an activity. A window can enter more activity. If the id is empty or invalid, no action will be performed. + + + + + + + Make the window exit a an activity. If it exits all activities it will be considered on all of them. + + + + + + + This event will be sent when the window has entered an activity. The window can be on more than one activity, or none: then is considered on all of them. + + + + + + + This event will be sent when the window left an activity. If the window leaves all activities, it will be considered on all. + If the window gets manually added on all activities, the server has to send activity_left for every previous activity it was in for the window to be really considered on all activities. + + + + + + + Requests this window to be displayed in a specific output. + + + + + + + This event will be sent when the X11 resource name of the window has changed. + This is only set for XWayland windows. + + + + + + + + The activation manager interface provides a way to get notified + when an application is about to be activated. + + + + + Destroy the activation manager object. The activation objects introduced + by this manager object will be unaffected. + + + + + + Will be issued when an app is set to be activated. It offers + an instance of org_kde_plasma_activation that will tell us the app_id + and the extent of the activation. + + + + + + + + + Notify the compositor that the org_kde_plasma_activation object will no + longer be used. + + + + + + + + + + + + + diff --git a/panel/backends/wayland/protocols/org-kde-plasma-virtual-desktop.xml b/panel/backends/wayland/protocols/org-kde-plasma-virtual-desktop.xml new file mode 100644 index 000000000..0e0551b0a --- /dev/null +++ b/panel/backends/wayland/protocols/org-kde-plasma-virtual-desktop.xml @@ -0,0 +1,110 @@ + + + + + + + + Given the id of a particular virtual desktop, get the corresponding org_kde_plasma_virtual_desktop which represents only the desktop with that id. + + + + + + + + Ask the server to create a new virtual desktop, and position it at a specified position. If the position is zero or less, it will be positioned at the beginning, if the position is the count or more, it will be positioned at the end. + + + + + + + + Ask the server to get rid of a virtual desktop, the server may or may not acconsent to the request. + + + + + + + + + + + + + + + + + + + This event is sent after all other properties has been + sent after binding to the desktop manager object and after any + other property changes done after that. This allows + changes to the org_kde_plasma_virtual_desktop_management properties to be seen as + atomic, even if they happen via multiple events. + + + + + + + + + + + + + Request the server to set the status of this desktop to active: The server is free to consent or deny the request. This will be the new "current" virtual desktop of the system. + + + + + + The format of the id is decided by the compositor implementation. A desktop id univocally identifies a virtual desktop and must be guaranteed to never exist two desktops with the same id. The format of the string id is up to the server implementation. + + + + + + + + + + + The desktop will be the new "current" desktop of the system. The server may support either one virtual desktop active at a time, or other combinations such as one virtual desktop active per screen. + Windows associated to this virtual desktop will be shown. + + + + + + Windows that were associated only to this desktop will be hidden. + + + + + + This event is sent after all other properties has been + sent after binding to the desktop object and after any + other property changes done after that. This allows + changes to the org_kde_plasma_virtual_desktop properties to be seen as + atomic, even if they happen via multiple events. + + + + + + This virtual desktop has just been removed by the server: + All windows will lose the association to this desktop. + + + + + diff --git a/panel/backends/wayland/protocols/plasma-window-management.xml b/panel/backends/wayland/protocols/plasma-window-management.xml new file mode 100644 index 000000000..5990ebfda --- /dev/null +++ b/panel/backends/wayland/protocols/plasma-window-management.xml @@ -0,0 +1,425 @@ + + + + + + + This interface manages application windows. + It provides requests to show and hide the desktop and emits + an event every time a window is created so that the client can + use it to manage the window. + + Only one client can bind this interface at a time. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tell the compositor to show/hide the desktop. + + + + + + Deprecated: use get_window_by_uuid + + + + + + + + + + + + This event will be sent whenever the show desktop mode changes. E.g. when it is entered + or left. + + On binding the interface the current state is sent. + + + + + + + This event will be sent immediately after a window is mapped. + + + + + + + This event will be sent when stacking order changed and on bind + + + + + + + This event will be sent when stacking order changed and on bind + + + + + + + This event will be sent immediately after a window is mapped. + + + + + + + + + Manages and control an application window. + + Only one client can bind this interface at a time. + + + + + Set window state. + + Values for state argument are described by org_kde_plasma_window_management.state + and can be used together in a bitfield. The flags bitfield describes which flags are + supposed to be set, the state bitfield the value for the set flags + + + + + + + + Deprecated: use enter_virtual_desktop + Maps the window to a different virtual desktop. + + To show the window on all virtual desktops, call the + org_kde_plasma_window.set_state request and specify a on_all_desktops + state in the bitfield. + + + + + + + Sets the geometry of the taskbar entry for this window. + The geometry is relative to a panel in particular. + + + + + + + + + + + Remove the task geometry information for a particular panel. + + + + + + + + + Close this window. + + + + + + Request an interactive move for this window. + + + + + + Request an interactive resize for this window. + + + + + + Removes the resource bound for this org_kde_plasma_window. + + + + + + The compositor will write the window icon into the provided file descriptor. + The data is a serialized QIcon with QDataStream. + + + + + + + This event will be sent as soon as the window title is changed. + + + + + + + This event will be sent as soon as the application + identifier is changed. + + + + + + + This event will be sent as soon as the window state changes. + + Values for state argument are described by org_kde_plasma_window_management.state. + + + + + + + DEPRECATED: use virtual_desktop_entered and virtual_desktop_left instead + This event will be sent when a window is moved to another + virtual desktop. + + It is not sent if it becomes visible on all virtual desktops though. + + + + + + + This event will be sent whenever the themed icon name changes. May be null. + + + + + + + This event will be sent immediately after the window is closed + and its surface is unmapped. + + + + + + This event will be sent immediately after all initial state been sent to the client. + If the Plasma window is already unmapped, the unmapped event will be sent before the + initial_state event. + + + + + + This event will be sent whenever the parent window of this org_kde_plasma_window changes. + The passed parent is another org_kde_plasma_window and this org_kde_plasma_window is a + transient window to the parent window. If the parent argument is null, this + org_kde_plasma_window does not have a parent window. + + + + + + + This event will be sent whenever the window geometry of this org_kde_plasma_window changes. + The coordinates are in absolute coordinates of the windowing system. + + + + + + + + + + This event will be sent whenever the icon of the window changes, but there is no themed + icon name. Common examples are Xwayland windows which have a pixmap based icon. + + The client can request the icon using get_icon. + + + + + + This event will be sent when the compositor has set the process id this window belongs to. + This should be set once before the initial_state is sent. + + + + + + + + + + Make the window enter a virtual desktop. A window can enter more + than one virtual desktop. if the id is empty or invalid, no action will be performed. + + + + + + RFC: do this with an empty id to request_enter_virtual_desktop? + Make the window enter a new virtual desktop. If the server consents the request, + it will create a new virtual desktop and assign the window to it. + + + + + + Make the window exit a virtual desktop. If it exits all desktops it will be considered on all of them. + + + + + + + This event will be sent when the window has entered a new virtual desktop. The window can be on more than one desktop, or none: then is considered on all of them. + + + + + + + This event will be sent when the window left a virtual desktop. If the window leaves all desktops, it can be considered on all. + If the window gets manually added on all desktops, the server has to send virtual_desktop_left for every previous desktop it was in for the window to be really considered on all desktops. + + + + + + + + + This event will be sent after the application menu + for the window has changed. + + + + + + + + Make the window enter an activity. A window can enter more activity. If the id is empty or invalid, no action will be performed. + + + + + + + Make the window exit a an activity. If it exits all activities it will be considered on all of them. + + + + + + + This event will be sent when the window has entered an activity. The window can be on more than one activity, or none: then is considered on all of them. + + + + + + + This event will be sent when the window left an activity. If the window leaves all activities, it will be considered on all. + If the window gets manually added on all activities, the server has to send activity_left for every previous activity it was in for the window to be really considered on all activities. + + + + + + + Requests this window to be displayed in a specific output. + + + + + + + This event will be sent when the X11 resource name of the window has changed. + This is only set for XWayland windows. + + + + + + + + The activation manager interface provides a way to get notified + when an application is about to be activated. + + + + + Destroy the activation manager object. The activation objects introduced + by this manager object will be unaffected. + + + + + + Will be issued when an app is set to be activated. It offers + an instance of org_kde_plasma_activation that will tell us the app_id + and the extent of the activation. + + + + + + + + + Notify the compositor that the org_kde_plasma_activation object will no + longer be used. + + + + + + + + + + + + + From 1a2dd53343f908a90449a8339098f895e489ea76 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Sat, 17 Feb 2024 23:32:46 +0100 Subject: [PATCH 11/15] LXQtPanel: workaround KAcceleratorManager changing button text FIXME TODO TODO: is this correct approach? --- plugin-taskbar/lxqttaskbutton.cpp | 27 ++++++++++++++++++++++++++- plugin-taskbar/lxqttaskbutton.h | 4 ++++ plugin-taskbar/lxqttaskgroup.cpp | 6 +++--- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/plugin-taskbar/lxqttaskbutton.cpp b/plugin-taskbar/lxqttaskbutton.cpp index 5a581929c..3f6ea7dbf 100644 --- a/plugin-taskbar/lxqttaskbutton.cpp +++ b/plugin-taskbar/lxqttaskbutton.cpp @@ -128,7 +128,7 @@ LXQtTaskButton::~LXQtTaskButton() = default; void LXQtTaskButton::updateText() { QString title = mBackend->getWindowTitle(mWindow); - setText(title.replace(QStringLiteral("&"), QStringLiteral("&&"))); + setTextExplicitly(title.replace(QStringLiteral("&"), QStringLiteral("&&"))); setToolTip(title); } @@ -314,6 +314,30 @@ QMimeData * LXQtTaskButton::mimeData() return mimedata; } +/*! + * \brief LXQtTaskButton::setTextExplicitly + * \param str + * + * This is needed to workaround flickering caused by KAcceleratorManager + * This class is hooked by KDE Integration and adds accelerators to button text + * (Adds some '&' characters) + * This triggers widget update but soon after text is reset to original value + * This triggers a KAcceleratorManager update which again adds accelerator + * This happens in loop + * + * TODO: investigate proper solution + */ +void LXQtTaskButton::setTextExplicitly(const QString &str) +{ + if(str == mExplicitlySetText) + { + return; + } + + mExplicitlySetText = str; + setText(mExplicitlySetText); +} + /************************************************ ************************************************/ @@ -688,6 +712,7 @@ void LXQtTaskButton::contextMenuEvent(QContextMenuEvent* event) menu->addSeparator(); a = menu->addAction(XdgIcon::fromTheme(QStringLiteral("process-stop")), tr("&Close")); connect(a, &QAction::triggered, this, &LXQtTaskButton::closeApplication); + menu->setGeometry(mParentTaskBar->panel()->calculatePopupWindowPos(mapToGlobal(event->pos()), menu->sizeHint())); mPlugin->willShowWindow(menu); menu->show(); diff --git a/plugin-taskbar/lxqttaskbutton.h b/plugin-taskbar/lxqttaskbutton.h index 12e83a613..7a3deeb13 100644 --- a/plugin-taskbar/lxqttaskbutton.h +++ b/plugin-taskbar/lxqttaskbutton.h @@ -122,6 +122,8 @@ public slots: inline ILXQtPanelPlugin * plugin() const { return mPlugin; } + void setTextExplicitly(const QString& str); + protected: //TODO: public getter instead? ILXQtAbstractWMInterface *mBackend; @@ -138,6 +140,8 @@ public slots: int mIconSize; int mWheelDelta; + QString mExplicitlySetText; + // Timer for when draggind something into a button (the button's window // must be activated so that the use can continue dragging to the window QTimer * mDNDTimer; diff --git a/plugin-taskbar/lxqttaskgroup.cpp b/plugin-taskbar/lxqttaskgroup.cpp index 7f44bdc28..3aedc50f2 100644 --- a/plugin-taskbar/lxqttaskgroup.cpp +++ b/plugin-taskbar/lxqttaskgroup.cpp @@ -57,7 +57,7 @@ LXQtTaskGroup::LXQtTaskGroup(const QString &groupName, WId window, LXQtTaskBar * Q_ASSERT(parent); setObjectName(groupName); - setText(groupName); + setTextExplicitly(groupName); connect(this, &LXQtTaskGroup::clicked, this, &LXQtTaskGroup::onClicked); connect(parent, &LXQtTaskBar::buttonRotationRefreshed, this, &LXQtTaskGroup::setAutoRotation); @@ -336,7 +336,7 @@ void LXQtTaskGroup::regroup() if (button) { - setText(button->text()); + setTextExplicitly(button->text()); setToolTip(button->toolTip()); setWindowId(button->windowId()); } @@ -347,7 +347,7 @@ void LXQtTaskGroup::regroup() { mSingleButton = false; QString t = QString(QStringLiteral("%1 - %2 windows")).arg(mGroupName).arg(cont); - setText(t); + setTextExplicitly(t); setToolTip(parentTaskBar()->isShowGroupOnHover() ? QString() : t); } } From 9eaa6b0ac0e9b5c3667ca8cde6c279bd3c573a1a Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Mon, 26 Feb 2024 09:48:03 +0100 Subject: [PATCH 12/15] ColorPicker: use XDG Desktop Portal on Wayland TODO TODO: show error message when not supported --- plugin-colorpicker/colorpicker.cpp | 156 ++++++++++++++++++++++++++--- plugin-colorpicker/colorpicker.h | 8 ++ 2 files changed, 150 insertions(+), 14 deletions(-) diff --git a/plugin-colorpicker/colorpicker.cpp b/plugin-colorpicker/colorpicker.cpp index 004314557..0827686d5 100644 --- a/plugin-colorpicker/colorpicker.cpp +++ b/plugin-colorpicker/colorpicker.cpp @@ -36,6 +36,9 @@ #include #include +#include +#include + //NOTE: Xlib.h defines Bool which conflicts with QJsonValue::Type enum #include #undef Bool @@ -77,6 +80,33 @@ void ColorPicker::realign() mWidget.update(panel()->lineCount() <= 1 ? panel()->isHorizontal() : !panel()->isHorizontal()); } +void ColorPicker::queryXDGSupport() +{ + if (qEnvironmentVariableIntValue("QT_NO_XDG_DESKTOP_PORTAL") > 0) { + return; + } + QDBusMessage message = QDBusMessage::createMethodCall( + QLatin1String("org.freedesktop.portal.Desktop"), + QLatin1String("/org/freedesktop/portal/desktop"), + QLatin1String("org.freedesktop.DBus.Properties"), + QLatin1String("Get")); + message << QLatin1String("org.freedesktop.portal.Screenshot") + << QLatin1String("version"); + + QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message); + auto watcher = new QDBusPendingCallWatcher(pendingCall); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, + [this](QDBusPendingCallWatcher *watcher) { + watcher->deleteLater(); + QDBusPendingReply reply = *watcher; + if (!reply.isError() && reply.value().toUInt() >= 2) + m_hasScreenshotPortalWithColorPicking = true; + }); + + //TODO: show error tooltip if not supported + //NOTE: on Wayland we cannot pick color without it +} + ColorPickerWidget::ColorPickerWidget(QWidget *parent) : QWidget(parent) { @@ -108,7 +138,8 @@ ColorPickerWidget::ColorPickerWidget(QWidget *parent) : QWidget(parent) layout->addWidget(mColorButton); setLayout(layout); - connect(mPickerButton, &QToolButton::clicked, this, &ColorPickerWidget::captureMouse); + connect(mPickerButton, &QToolButton::clicked, this, &ColorPickerWidget::startCapturingColor); + connect(mColorButton, &QToolButton::clicked, this, [&]() { buildMenu(); @@ -162,29 +193,86 @@ void ColorPickerWidget::mouseReleaseEvent(QMouseEvent *event) qWarning() << "WAYLAND does not support grabbing windows"; } - mColorButton->setColor(col); - paste(col.name()); + setCapturedColor(col); - if (mColorsList.contains(col)) + mCapturing = false; + releaseMouse(); + + if (!mPickerButton->contentsRect().contains(mapFromGlobal(QCursor::pos()))) { - mColorsList.move(mColorsList.indexOf(col), 0); + QApplication::sendEvent(mPickerButton, new QEvent(QEvent::Leave)); } - else +} + +void ColorPickerWidget::startCapturingColor() +{ + //NOTE: see qt6 `src/gui/platform/unix/qgenericunixservices.cpp` + + // Make double sure that we are in a wayland environment. In particular check + // WAYLAND_DISPLAY so also XWayland apps benefit from portal-based color picking. + // Outside wayland we'll rather rely on other means than the XDG desktop portal. + if (!qEnvironmentVariableIsEmpty("WAYLAND_DISPLAY") + || QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) { - mColorsList.prepend(col); + // On Wayland use XDG Desktop Portal + + QString m_parentWindowId; //TODO + + QDBusMessage message = QDBusMessage::createMethodCall( + QLatin1String("org.freedesktop.portal.Desktop"), + QLatin1String("/org/freedesktop/portal/desktop"), + QLatin1String("org.freedesktop.portal.Screenshot"), + QLatin1String("PickColor")); + message << m_parentWindowId << QVariantMap(); + + QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message); + auto watcher = new QDBusPendingCallWatcher(pendingCall, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, + [this](QDBusPendingCallWatcher *watcher) { + watcher->deleteLater(); + QDBusPendingReply reply = *watcher; + if (reply.isError()) { + qWarning("DBus call to pick color failed: %s", + qPrintable(reply.error().message())); + setCapturedColor({}); + } else { + QDBusConnection::sessionBus().connect( + QLatin1String("org.freedesktop.portal.Desktop"), + reply.value().path(), + QLatin1String("org.freedesktop.portal.Request"), + QLatin1String("Response"), + this, + // clang-format off + SLOT(gotColorResponse(uint,QVariantMap)) + // clang-format on + ); + } + }); } - - if (mColorsList.size() > 10) + else if (qGuiApp->nativeInterface()) { - mColorsList.removeLast(); + // On X11 grab mouse and let `mouseReleaseEvent()` retrieve color + captureMouse(); } +} - mCapturing = false; - releaseMouse(); +void ColorPickerWidget::setCapturedColor(const QColor &color) +{ + mColorButton->setColor(color); + paste(color.name()); - if (!mPickerButton->contentsRect().contains(mapFromGlobal(QCursor::pos()))) + if (mColorsList.contains(color)) { - QApplication::sendEvent(mPickerButton, new QEvent(QEvent::Leave)); + mColorsList.move(mColorsList.indexOf(color), 0); + } + else + { + mColorsList.prepend(color); + } + + if (mColorsList.size() > 10) + { + mColorsList.removeLast(); } } @@ -195,6 +283,46 @@ void ColorPickerWidget::captureMouse() mCapturing = true; } +struct XDGDesktopColor +{ + double r = 0; + double g = 0; + double b = 0; + + QColor toQColor() const + { + constexpr auto rgbMax = 255; + return { static_cast(r * rgbMax), static_cast(g * rgbMax), + static_cast(b * rgbMax) }; + } +}; + +const QDBusArgument &operator>>(const QDBusArgument &argument, XDGDesktopColor &myStruct) +{ + argument.beginStructure(); + argument >> myStruct.r >> myStruct.g >> myStruct.b; + argument.endStructure(); + return argument; +} + +void ColorPickerWidget::gotColorResponse(uint result, const QVariantMap &map) +{ + auto colorProp = QStringLiteral("color"); + + if (result != 0) + return; + if (map.contains(colorProp)) + { + XDGDesktopColor color{}; + map.value(colorProp).value() >> color; + setCapturedColor(color.toQColor()); + } + else + { + setCapturedColor({}); + } +} + QIcon ColorPickerWidget::colorIcon(QColor color) { diff --git a/plugin-colorpicker/colorpicker.h b/plugin-colorpicker/colorpicker.h index 919f42490..ecf2a3b41 100644 --- a/plugin-colorpicker/colorpicker.h +++ b/plugin-colorpicker/colorpicker.h @@ -58,7 +58,10 @@ class ColorPickerWidget : public QWidget void mouseReleaseEvent(QMouseEvent *event); private slots: + void startCapturingColor(); + void setCapturedColor(const QColor& color); void captureMouse(); + void gotColorResponse(uint result, const QVariantMap& map); private: static const QString svgIcon; @@ -91,8 +94,13 @@ class ColorPicker : public QObject, public ILXQtPanelPlugin virtual void realign() override; +private: + void queryXDGSupport(); + private: ColorPickerWidget mWidget; + + bool m_hasScreenshotPortalWithColorPicking = false; }; class ColorPickerLibrary: public QObject, public ILXQtPanelPluginLibrary From 730597335b7df533c235e8ee8ca4c7b7167db220 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 9 Apr 2024 11:52:46 +0200 Subject: [PATCH 13/15] Hide lxqt-panel application from applications menu - Add NoDisplay=true to .desktop file CMake: rename autostart desktop variable --- autostart/CMakeLists.txt | 4 ++-- autostart/lxqt-panel_wayland.desktop.in | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/autostart/CMakeLists.txt b/autostart/CMakeLists.txt index 6d044738b..f56d282ff 100644 --- a/autostart/CMakeLists.txt +++ b/autostart/CMakeLists.txt @@ -1,9 +1,9 @@ -set(DESKTOP_FILES lxqt-panel.desktop.in) +set(AUTOSTART_DESKTOP_FILES_IN lxqt-panel.desktop.in) # Translations ********************************** lxqt_translate_desktop(DESKTOP_FILES SOURCES - ${DESKTOP_FILES_IN} + ${AUTOSTART_DESKTOP_FILES_IN} USE_YAML ) add_custom_target(lxqt_panel_autostart_desktop_files ALL DEPENDS ${DESKTOP_FILES}) diff --git a/autostart/lxqt-panel_wayland.desktop.in b/autostart/lxqt-panel_wayland.desktop.in index 089082aea..540955e18 100644 --- a/autostart/lxqt-panel_wayland.desktop.in +++ b/autostart/lxqt-panel_wayland.desktop.in @@ -1,6 +1,7 @@ [Desktop Entry] Type=Application TryExec=lxqt-panel +NoDisplay=true # NOTE: KWin wants absolute path here, get it from CMake install path Exec=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/lxqt-panel From 8f685166bd24eab3a08d3d6c1657e8667a9a0d6c Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Fri, 12 Jul 2024 11:53:16 +0200 Subject: [PATCH 14/15] LXQtWMBackend_KWinWayland: announce DesktopSwitch support --- .../wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp index 5eb83d688..de0d57bf4 100644 --- a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp +++ b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp @@ -87,6 +87,9 @@ LXQtWMBackend_KWinWayland::LXQtWMBackend_KWinWayland(QObject *parent) : bool LXQtWMBackend_KWinWayland::supportsAction(WId windowId, LXQtTaskBarBackendAction action) const { + if(action == LXQtTaskBarBackendAction::DesktopSwitch) + return true; + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); if(!window) return false; From 6ab96627ca6424df6426d53dc5e5edc381ba8a89 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 20 Aug 2024 17:30:02 +0200 Subject: [PATCH 15/15] LXQtWMBackend_KWinWayland: fix minimize on click not working --- .../kwin_wayland/lxqtwmbackend_kwinwayland.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp index de0d57bf4..1880a2060 100644 --- a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp +++ b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp @@ -598,11 +598,17 @@ void LXQtWMBackend_KWinWayland::addWindow(LXQtTaskBarPlasmaWindow *window) } else { - if (activeWindow == effectiveWindow) - { - activeWindow = nullptr; - emit activeWindowChanged(0); - } + // NOTE: LXQtTaskGroup does not handle well null active window + // This would break minimize on click functionality. + // Since window is deactivated because another window became active, + // we pretend to still be active until we receive signal from newly active + // window which will register itself and emit activeWindowChanged() as above + + // if (activeWindow == effectiveWindow) + // { + // activeWindow = nullptr; + // emit activeWindowChanged(0); + // } } });