From f34b6911d2aff50d6d4b7499cb26d567653c97bb Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 9 Jul 2024 12:45:35 +0200 Subject: [PATCH 01/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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); + // } } }); From ec7006205be90ac418dd4ca0218221819be54c0b Mon Sep 17 00:00:00 2001 From: Marcus Britanicus Date: Thu, 22 Aug 2024 12:45:50 +0530 Subject: [PATCH 16/42] Backend detection and Wlroots backend --- panel/backends/wayland/CMakeLists.txt | 1 + panel/backends/wayland/wlroots/CMakeLists.txt | 35 ++ .../wayland/wlroots/lxqttaskbarwlrwm.cpp | 545 ++++++++++++++++++ .../wayland/wlroots/lxqttaskbarwlrwm.h | 136 +++++ .../wayland/wlroots/lxqtwmbackend_wlr.cpp | 500 ++++++++++++++++ .../wayland/wlroots/lxqtwmbackend_wlr.h | 103 ++++ ...oreign-toplevel-management-unstable-v1.xml | 270 +++++++++ panel/lxqtpanelapplication.cpp | 256 ++++---- 8 files changed, 1741 insertions(+), 105 deletions(-) create mode 100644 panel/backends/wayland/wlroots/CMakeLists.txt create mode 100644 panel/backends/wayland/wlroots/lxqttaskbarwlrwm.cpp create mode 100644 panel/backends/wayland/wlroots/lxqttaskbarwlrwm.h create mode 100644 panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp create mode 100644 panel/backends/wayland/wlroots/lxqtwmbackend_wlr.h create mode 100644 panel/backends/wayland/wlroots/wlr-foreign-toplevel-management-unstable-v1.xml diff --git a/panel/backends/wayland/CMakeLists.txt b/panel/backends/wayland/CMakeLists.txt index 3f2c93189..19340698c 100644 --- a/panel/backends/wayland/CMakeLists.txt +++ b/panel/backends/wayland/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(kwin_wayland) +add_subdirectory(wlroots) diff --git a/panel/backends/wayland/wlroots/CMakeLists.txt b/panel/backends/wayland/wlroots/CMakeLists.txt new file mode 100644 index 000000000..aa53d5a99 --- /dev/null +++ b/panel/backends/wayland/wlroots/CMakeLists.txt @@ -0,0 +1,35 @@ +set(PLATFORM_NAME wlroots) + +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) +find_package(Qt6Xdg) + +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 Qt6::GuiPrivate) + +set( + SRC + lxqtwmbackend_wlr.cpp lxqtwmbackend_wlr.h + lxqttaskbarwlrwm.cpp lxqttaskbarwlrwm.h +) + +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 Qt6Xdg) + +qt6_generate_wayland_protocol_client_sources(${NAME} FILES + ${CMAKE_CURRENT_SOURCE_DIR}/wlr-foreign-toplevel-management-unstable-v1.xml +) diff --git a/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.cpp b/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.cpp new file mode 100644 index 000000000..7a84ab630 --- /dev/null +++ b/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.cpp @@ -0,0 +1,545 @@ +#include "lxqttaskbarwlrwm.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include + +QString U8Str( const char *str ) { + return QString::fromUtf8( str ); +} + +static inline QString getPixmapIcon(QString name) +{ + QStringList paths{ + U8Str("/usr/local/share/pixmaps/"), + U8Str("/usr/share/pixmaps/"), + }; + + QStringList sfxs{ + U8Str( ".svg" ), U8Str( ".png" ), U8Str( ".xpm" ) + }; + + for (QString path: paths) + { + for (QString sfx: sfxs) + { + if (QFile::exists(path + name + sfx)) + { + return path + name + sfx; + } + } + } + + return QString(); +} + + +QIcon getIconForAppId(QString mAppId) +{ + if (mAppId.isEmpty() or (mAppId == U8Str("Unknown"))) + { + return QIcon(); + } + + /** Wine apps */ + if (mAppId.endsWith(U8Str(".exe"))) + { + return QIcon::fromTheme(U8Str("wine")); + } + + /** Check if a theme icon exists called @mAppId */ + if (QIcon::hasThemeIcon(mAppId)) + { + return QIcon::fromTheme(mAppId); + } + + /** Check if the theme icon is @mAppId, but all lower-case letters */ + else if (QIcon::hasThemeIcon(mAppId.toLower())) + { + return QIcon::fromTheme(mAppId.toLower()); + } + + QStringList appDirs = { + QDir::home().filePath(U8Str(".local/share/applications/")), + U8Str("/usr/local/share/applications/"), + U8Str("/usr/share/applications/"), + }; + + /** + * Assume mAppId == desktop-file-name (ideal situation) + * or mAppId.toLower() == desktop-file-name (cheap fallback) + */ + QString iconName; + + for (QString path: appDirs) + { + /** Get the icon name from desktop (mAppId: as it is) */ + if (QFile::exists(path + mAppId + U8Str(".desktop"))) + { + QSettings desktop(path + mAppId + U8Str(".desktop"), QSettings::IniFormat); + iconName = desktop.value(U8Str("Desktop Entry/Icon")).toString(); + } + + /** Get the icon name from desktop (mAppId: all lower-case letters) */ + else if (QFile::exists(path + mAppId.toLower() + U8Str(".desktop"))) + { + QSettings desktop(path + mAppId.toLower() + U8Str(".desktop"), QSettings::IniFormat); + iconName = desktop.value(U8Str("Desktop Entry/Icon")).toString(); + } + + /** No icon specified: try else-where */ + if (iconName.isEmpty()) + { + continue; + } + + /** We got an iconName, and it's in the current theme */ + if (QIcon::hasThemeIcon(iconName)) + { + return QIcon::fromTheme(iconName); + } + + /** Not a theme icon, but an absolute path */ + else if (QFile::exists(iconName)) + { + return QIcon(iconName); + } + + /** Not theme icon or absolute path. So check /usr/share/pixmaps/ */ + else + { + iconName = getPixmapIcon(iconName); + + if (not iconName.isEmpty()) + { + return QIcon(iconName); + } + } + } + + /* Check all desktop files for @mAppId */ + for (QString path: appDirs) + { + QStringList desktops = QDir(path).entryList({ U8Str("*.desktop") }); + for (QString dskf: desktops) + { + QSettings desktop(path + dskf, QSettings::IniFormat); + + QString exec = desktop.value(U8Str("Desktop Entry/Exec"), U8Str("abcd1234/-")).toString(); + QString name = desktop.value(U8Str("Desktop Entry/Name"), U8Str("abcd1234/-")).toString(); + QString cls = desktop.value(U8Str("Desktop Entry/StartupWMClass"), U8Str("abcd1234/-")).toString(); + + QString execPath = U8Str(std::filesystem::path(exec.toStdString()).filename().c_str()); + + if (mAppId.compare(execPath, Qt::CaseInsensitive) == 0) + { + iconName = desktop.value(U8Str("Desktop Entry/Icon")).toString(); + } + + else if (mAppId.compare(name, Qt::CaseInsensitive) == 0) + { + iconName = desktop.value(U8Str("Desktop Entry/Icon")).toString(); + } + + else if (mAppId.compare(cls, Qt::CaseInsensitive) == 0) + { + iconName = desktop.value(U8Str("Desktop Entry/Icon")).toString(); + } + + if (not iconName.isEmpty()) + { + if (QIcon::hasThemeIcon(iconName)) + { + return QIcon::fromTheme(iconName); + } + + else if (QFile::exists(iconName)) + { + return QIcon(iconName); + } + + else + { + iconName = getPixmapIcon(iconName); + + if (not iconName.isEmpty()) + { + return QIcon(iconName); + } + } + } + } + } + + iconName = getPixmapIcon(iconName); + + if (not iconName.isEmpty()) + { + return QIcon(iconName); + } + + return QIcon(); +} + + +static inline wl_seat *get_seat() +{ + QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); + + if (!native) + { + return nullptr; + } + + struct wl_seat *seat = reinterpret_cast(native->nativeResourceForIntegration("wl_seat")); + + return seat; +} + + +/* + * LXQtTaskbarWlrootsWindowManagment + */ + +LXQtTaskbarWlrootsWindowManagment::LXQtTaskbarWlrootsWindowManagment() : QWaylandClientExtensionTemplate(version) +{ + /** Automatically destroy thie object */ + connect( + this, &QWaylandClientExtension::activeChanged, this, [ this ] { + if (!isActive()) + { + zwlr_foreign_toplevel_manager_v1_destroy(object()); + } + }); +} + + +LXQtTaskbarWlrootsWindowManagment::~LXQtTaskbarWlrootsWindowManagment() +{ + if (isActive()) + { + zwlr_foreign_toplevel_manager_v1_destroy(object()); + } +} + + +void LXQtTaskbarWlrootsWindowManagment::zwlr_foreign_toplevel_manager_v1_toplevel(struct ::zwlr_foreign_toplevel_handle_v1 *toplevel) +{ + /** + * A window was created. + * Wait for the window to become ready, i.e. wait for done() event to be sent by the compositor. + * Once we recieve done(), emit the windowReady() signal. + */ + + auto w = new LXQtTaskbarWlrootsWindow(toplevel); + + connect(w, &LXQtTaskbarWlrootsWindow::windowReady, [w, this] () { + emit windowCreated(w->getWindowId()); + }); +} + + +/* + * LXQtTaskbarWlrootsWindow + */ + +LXQtTaskbarWlrootsWindow::LXQtTaskbarWlrootsWindow(::zwlr_foreign_toplevel_handle_v1 *id) : zwlr_foreign_toplevel_handle_v1(id) +{ +} + + +LXQtTaskbarWlrootsWindow::~LXQtTaskbarWlrootsWindow() +{ + destroy(); +} + + +void LXQtTaskbarWlrootsWindow::activate() +{ + /** + * Activate on default seat. + * TODO: Worry about multi-seat setups, when we have no other worries :P + */ + zwlr_foreign_toplevel_handle_v1::activate(get_seat()); +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_title(const QString& title) +{ + /** Store the incoming title in pending */ + m_pendingState.title = title; + m_pendingState.titleChanged = true; +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_app_id(const QString& app_id) +{ + /** Store the incoming appId in pending */ + m_pendingState.appId = app_id; + m_pendingState.appIdChanged = true; + + /** Update the icon */ + this->icon = getIconForAppId(app_id); + + /** We did not get any icon from app-id. Let's use application-x-executable */ + if (this->icon.pixmap(64).width() == 0) + { + this->icon = XdgIcon::fromTheme(QString::fromUtf8("application-x-executable")); + } +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_output_enter(struct ::wl_output *output) +{ + /** This view was added to an output */ + m_pendingState.outputs << output; + m_pendingState.outputsChanged = true; +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_output_leave(struct ::wl_output *output) +{ + /** This view was removed from an output; store it in pending. */ + m_pendingState.outputsLeft << output; + + if (m_pendingState.outputs.contains(output)) + { + m_pendingState.outputs.removeAll(output); + } + + m_pendingState.outputsChanged = true; +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_state(wl_array *state) +{ + /** State of this window was changed; store it in pending. */ + auto *states = static_cast(state->data); + int numStates = static_cast(state->size / sizeof(uint32_t)); + + for (int i = 0; i < numStates; i++) + { + switch ((uint32_t)states[ i ]) + { + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED: { + m_pendingState.maximized = true; + m_pendingState.maximizedChanged = true; + break; + } + + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED: { + m_pendingState.minimized = true; + m_pendingState.minimizedChanged = true; + break; + } + + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED: { + m_pendingState.activated = true; + m_pendingState.activatedChanged = true; + break; + } + + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN: { + m_pendingState.fullscreen = true; + m_pendingState.fullscreenChanged = true; + break; + } + } + } +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_done() +{ + /** + * All the states/properties have been sent. + * We can now emit the signals and clear the pending state: + * 1. Update all the variables first. + * 2. Then clear the m_pendingState. + * 3. Emit the changed signals. + * 4. Finally, cleanr the m_pendingState.Changed flags. + */ + + // (1) title, if it changed + if (m_pendingState.titleChanged) + { + windowState.title = m_pendingState.title; + } + + // (2) appId, if it changed + if (m_pendingState.appIdChanged) + { + windowState.appId = m_pendingState.appId; + } + + // (3) outputs, if they changed + if (m_pendingState.outputsChanged) + { + for (::wl_output *op: m_pendingState.outputsLeft) + { + if (windowState.outputs.contains(op)) + { + windowState.outputs.removeAll(op); + } + } + + for (::wl_output *op: m_pendingState.outputs) + { + if (!windowState.outputs.contains(op)) + { + windowState.outputs << op; + } + } + } + + // (4) states, if they changed. Don't trust the changed flag. + if (m_pendingState.maximized != windowState.maximized) + { + windowState.maximized = m_pendingState.maximized; + m_pendingState.maximizedChanged = true; + } + + if (m_pendingState.minimized != windowState.minimized) + { + windowState.minimized = m_pendingState.minimized; + m_pendingState.minimizedChanged = true; + } + + if (m_pendingState.activated != windowState.activated) + { + windowState.activated = m_pendingState.activated; + m_pendingState.activatedChanged = true; + } + + if (m_pendingState.fullscreen != windowState.fullscreen) + { + windowState.fullscreen = m_pendingState.fullscreen; + m_pendingState.fullscreenChanged = true; + } + + // (5) parent, if it changed. + if (m_pendingState.parentChanged) + { + if (m_pendingState.parent) + { + setParentWindow(new LXQtTaskbarWlrootsWindow(m_pendingState.parent)); + } + + else + { + setParentWindow(nullptr); + } + } + + /** 2. Clear all m_pendingState. for next run */ + m_pendingState.title = QString(); + m_pendingState.appId = QString(); + m_pendingState.outputs.clear(); + m_pendingState.maximized = false; + m_pendingState.minimized = false; + m_pendingState.activated = false; + m_pendingState.fullscreen = false; + m_pendingState.parent = nullptr; + + /** + * 3. Emit signals + * (a) First time done was emitted after the window was created. + * (b) Other times. + */ + + /** (a) First time done was emitted */ + if (initDone == false) + { + /** + * All the states/properties are already set. + * Any query will give valid results. + */ + initDone = true; + emit windowReady(); + } + + /** (b) All the subsequent times */ + else + { + if (m_pendingState.titleChanged) + emit titleChanged(); + if (m_pendingState.appIdChanged) + emit appIdChanged(); + if (m_pendingState.outputsChanged) + emit outputsChanged(); + if (m_pendingState.maximizedChanged) + emit maximizedChanged(); + if (m_pendingState.minimizedChanged) + emit minimizedChanged(); + if (m_pendingState.activatedChanged) + emit activatedChanged(); + if (m_pendingState.fullscreenChanged) + emit fullscreenChanged(); + if (m_pendingState.parentChanged) + emit parentChanged(); + + emit stateChanged(); + } + + /** 4. Clear m+m_pendingState.Changed flags */ + m_pendingState.titleChanged = false; + m_pendingState.appIdChanged = false; + m_pendingState.outputsChanged = false; + m_pendingState.maximizedChanged = false; + m_pendingState.minimizedChanged = false; + m_pendingState.activatedChanged = false; + m_pendingState.fullscreenChanged = false; + m_pendingState.parentChanged = false; +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_closed() +{ + /** This window was closed */ + emit closed(); +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_parent(struct ::zwlr_foreign_toplevel_handle_v1 *parent) +{ + /** Parent of this window changed; store it in pending. */ + m_pendingState.parent = parent; + m_pendingState.parentChanged = true; +} + + +void LXQtTaskbarWlrootsWindow::setParentWindow(LXQtTaskbarWlrootsWindow *parent) +{ + QObject::disconnect(parentWindowUnmappedConnection); + + if (parent) + { + parentWindow = parent->getWindowId(); + parentWindowUnmappedConnection = QObject::connect( + parent, &LXQtTaskbarWlrootsWindow::closed, this, [ this ] { + setParentWindow(nullptr); + }); + } + else + { + parentWindow = 0; + parentWindowUnmappedConnection = QMetaObject::Connection(); + } +} diff --git a/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.h b/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.h new file mode 100644 index 000000000..c79f174da --- /dev/null +++ b/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.h @@ -0,0 +1,136 @@ +#pragma once + +#include +#include +#include + +#include "qwayland-wlr-foreign-toplevel-management-unstable-v1.h" +#include "wayland-wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" + +typedef quintptr WId; + +class LXQtTaskbarWlrootsWindow; + +class LXQtTaskbarWlrootsWindowManagment : public QWaylandClientExtensionTemplate, + public QtWayland::zwlr_foreign_toplevel_manager_v1 +{ + Q_OBJECT +public: + static constexpr int version = 16; + + LXQtTaskbarWlrootsWindowManagment(); + ~LXQtTaskbarWlrootsWindowManagment(); + + inline bool isShowingDesktop() const { return m_isShowingDesktop; } + +protected: + void zwlr_foreign_toplevel_manager_v1_toplevel(struct ::zwlr_foreign_toplevel_handle_v1 *toplevel); + void zwlr_foreign_toplevel_manager_v1_finished() {}; + +Q_SIGNALS: + void windowCreated(WId wid); + +private: + bool m_isShowingDesktop = false; +}; + +using WindowState = QtWayland::zwlr_foreign_toplevel_handle_v1::state; + +class WindowProperties { + public: + /** Title of the window */ + QString title = QString::fromUtf8( "untitled" ); + bool titleChanged = false; + + /** appId of the window */ + QString appId = QString::fromUtf8( "unidentified" ); + bool appIdChanged = false; + + /** List of outputs which the window is currently on */ + QList<::wl_output *> outputs; + bool outputsChanged = false; + + /** Is maximized */ + bool maximized = false; + bool maximizedChanged = false; + + /** Is minimized */ + bool minimized = false; + bool minimizedChanged = false; + + /** Is active */ + bool activated = false; + bool activatedChanged = false; + + /** Is fullscreen */ + bool fullscreen = false; + bool fullscreenChanged = false; + + /** Parent of this view, can be null */ + ::zwlr_foreign_toplevel_handle_v1 * parent = nullptr; + bool parentChanged = false; + + /** List of outputs from which window has left */ + QList<::wl_output *> outputsLeft; +}; + +class LXQtTaskbarWlrootsWindow : public QObject, + public QtWayland::zwlr_foreign_toplevel_handle_v1 +{ + Q_OBJECT +public: + LXQtTaskbarWlrootsWindow(::zwlr_foreign_toplevel_handle_v1 *id); + ~LXQtTaskbarWlrootsWindow(); + + inline WId getWindowId() const { return reinterpret_cast(this); } + + void activate(); + + QIcon icon; + WindowProperties windowState; + WId parentWindow = 0; + +Q_SIGNALS: + void titleChanged(); + void appIdChanged(); + void outputsChanged(); + + /** Individual state change signals */ + void maximizedChanged(); + void minimizedChanged(); + void activatedChanged(); + void fullscreenChanged(); + + void parentChanged(); + + /** Bulk state change signal */ + void stateChanged(); + + /** First state change signal: Before this, the window did not have a valid state */ + void windowReady(); + + /** All state changes have been sent. */ + void done(); + + /** Window closed signal */ + void closed(); + +protected: + void zwlr_foreign_toplevel_handle_v1_title(const QString &title); + void zwlr_foreign_toplevel_handle_v1_app_id(const QString &app_id); + void zwlr_foreign_toplevel_handle_v1_output_enter(struct ::wl_output *output); + void zwlr_foreign_toplevel_handle_v1_output_leave(struct ::wl_output *output); + void zwlr_foreign_toplevel_handle_v1_state(wl_array *state); + void zwlr_foreign_toplevel_handle_v1_done(); + void zwlr_foreign_toplevel_handle_v1_closed(); + void zwlr_foreign_toplevel_handle_v1_parent(struct ::zwlr_foreign_toplevel_handle_v1 *parent); + +private: + void setParentWindow(LXQtTaskbarWlrootsWindow *parent); + + QMetaObject::Connection parentWindowUnmappedConnection; + + WindowProperties m_pendingState; + + bool initDone = false; +}; diff --git a/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp new file mode 100644 index 000000000..a69520098 --- /dev/null +++ b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp @@ -0,0 +1,500 @@ +#include "lxqttaskbarwlrwm.h" +#include "lxqtwmbackend_wlr.h" + +#include +#include +#include +#include + +// Function to search for a window in the vector +WId findWindow(const std::vector& windows, WId tgt) { + // Use std::find to locate the target window + auto it = std::find(windows.begin(), windows.end(), tgt); + + // Check if the window was found (iterator points to windows.end() if not found) + if (it != windows.end()) { + // If found, return the window ID by dereferencing the iterator + return *it; + } + + return 0; +} + +// Function to erase a window from the vector +void eraseWindow(std::vector& windows, WId tgt) { + // Use std::vector::iterator to find the window + auto it = std::find(windows.begin(), windows.end(), tgt); + + // Check if the window was found + if (it != windows.end()) { + // If found, erase the element pointed to by the iterator + windows.erase(it); + } +} + +LXQtTaskbarWlrootsBackend::LXQtTaskbarWlrootsBackend(QObject *parent) : + ILXQtAbstractWMInterface(parent) +{ + m_managment.reset(new LXQtTaskbarWlrootsWindowManagment); + + connect(m_managment.get(), &LXQtTaskbarWlrootsWindowManagment::windowCreated, this, &LXQtTaskbarWlrootsBackend::addWindow); +} + +bool LXQtTaskbarWlrootsBackend::supportsAction(WId, LXQtTaskBarBackendAction action) const +{ + switch (action) + { + case LXQtTaskBarBackendAction::Maximize: + return true; + + case LXQtTaskBarBackendAction::Minimize: + return true; + + case LXQtTaskBarBackendAction::FullScreen: + return true; + + default: + return false; + } + + return false; +} + +bool LXQtTaskbarWlrootsBackend::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 LXQtTaskbarWlrootsBackend::getCurrentWindows() const +{ + QVector wids; + + for( WId wid: windows ){ + wids << wid; + } + + return wids; +} + +QString LXQtTaskbarWlrootsBackend::getWindowTitle(WId windowId) const +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return QString(); + + return window->windowState.title; +} + +bool LXQtTaskbarWlrootsBackend::applicationDemandsAttention(WId) const +{ + return false; +} + +QIcon LXQtTaskbarWlrootsBackend::getApplicationIcon(WId windowId, int devicePixels) const +{ + Q_UNUSED(devicePixels) + + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return QIcon(); + + return window->icon; +} + +QString LXQtTaskbarWlrootsBackend::getWindowClass(WId windowId) const +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return QString(); + return window->windowState.appId; +} + +LXQtTaskBarWindowLayer LXQtTaskbarWlrootsBackend::getWindowLayer(WId) const +{ + return LXQtTaskBarWindowLayer::Normal; +} + +bool LXQtTaskbarWlrootsBackend::setWindowLayer(WId, LXQtTaskBarWindowLayer) +{ + return false; +} + +LXQtTaskBarWindowState LXQtTaskbarWlrootsBackend::getWindowState(WId windowId) const +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return LXQtTaskBarWindowState::Normal; + + if(window->windowState.minimized) + return LXQtTaskBarWindowState::Minimized; + + if(window->windowState.maximized) + return LXQtTaskBarWindowState::Maximized; + + if(window->windowState.fullscreen) + return LXQtTaskBarWindowState::FullScreen; + + return LXQtTaskBarWindowState::Normal; +} + +bool LXQtTaskbarWlrootsBackend::setWindowState(WId windowId, LXQtTaskBarWindowState state, bool set) +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return false; + + switch (state) + { + case LXQtTaskBarWindowState::Minimized: + { + if ( set ) { + window->set_minimized(); + } + + else { + window->unset_minimized(); + } + + break; + } + case LXQtTaskBarWindowState::Maximized: + case LXQtTaskBarWindowState::MaximizedVertically: + case LXQtTaskBarWindowState::MaximizedHorizontally: + { + if ( set ) { + window->set_maximized(); + } + + else { + window->unset_maximized(); + } + + break; + } + case LXQtTaskBarWindowState::Normal: + { + if (set) + { + if ( window->windowState.maximized) { + window->unset_maximized(); + } + } + + break; + } + + case LXQtTaskBarWindowState::FullScreen: + { + if ( set ) { + window->set_fullscreen(nullptr); + } + + else { + window->unset_fullscreen(); + } + break; + } + + default: + return false; + } + + return true; +} + +bool LXQtTaskbarWlrootsBackend::isWindowActive(WId windowId) const +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return false; + + return activeWindow == windowId || window->windowState.activated; +} + +bool LXQtTaskbarWlrootsBackend::raiseWindow(WId windowId, bool onCurrentWorkSpace) +{ + Q_UNUSED(onCurrentWorkSpace) // Cannot be done on a generic wlroots-based compositor! + + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return false; + + window->activate(); + return true; +} + +bool LXQtTaskbarWlrootsBackend::closeWindow(WId windowId) +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return false; + + window->close(); + return true; +} + +WId LXQtTaskbarWlrootsBackend::getActiveWindow() const +{ + return activeWindow; +} + +int LXQtTaskbarWlrootsBackend::getWorkspacesCount() const +{ + return 1; +} + +QString LXQtTaskbarWlrootsBackend::getWorkspaceName(int) const +{ + return QStringLiteral("Desktop 1"); +} + +int LXQtTaskbarWlrootsBackend::getCurrentWorkspace() const +{ + return 1; +} + +bool LXQtTaskbarWlrootsBackend::setCurrentWorkspace(int) +{ + return false; +} + +int LXQtTaskbarWlrootsBackend::getWindowWorkspace(WId) const +{ + return 1; +} + +bool LXQtTaskbarWlrootsBackend::setWindowOnWorkspace(WId, int) +{ + return true; +} + +void LXQtTaskbarWlrootsBackend::moveApplicationToPrevNextMonitor(WId, bool, bool) +{ +} + +bool LXQtTaskbarWlrootsBackend::isWindowOnScreen(QScreen *, WId) const +{ + // TODO: Manage based on output-enter/output-leave + return true; +} + +bool LXQtTaskbarWlrootsBackend::setDesktopLayout(Qt::Orientation, int, int, bool) { + // Wlroots has no support for workspace as of 2024-August-20 + return false; +} + +void LXQtTaskbarWlrootsBackend::moveApplication(WId) +{ +} + +void LXQtTaskbarWlrootsBackend::resizeApplication(WId) +{ +} + +void LXQtTaskbarWlrootsBackend::refreshIconGeometry(WId, const QRect &) +{ + +} + +bool LXQtTaskbarWlrootsBackend::isAreaOverlapped(const QRect &) const +{ + return false; +} + +bool LXQtTaskbarWlrootsBackend::isShowingDesktop() const +{ + return m_managment->isShowingDesktop(); +} + +bool LXQtTaskbarWlrootsBackend::showDesktop(bool) +{ + return false; +} + +void LXQtTaskbarWlrootsBackend::addWindow(WId winId) +{ + if (findWindow(windows, winId) != 0 || transients.contains(winId)) + { + return; + } + + auto removeWindow = [winId, this] + { + eraseWindow(windows, winId); + lastActivated.remove(winId); + + if (activeWindow == winId) + { + activeWindow = 0; + emit activeWindowChanged(0); + } + + emit windowRemoved(winId); + }; + + LXQtTaskbarWlrootsWindow *window = getWindow( winId ); + if ( window == nullptr ) { + return; + } + + /** The window was closed. Remove from our lists */ + connect(window, &LXQtTaskbarWlrootsWindow::closed, this, removeWindow); + + /** */ + connect(window, &LXQtTaskbarWlrootsWindow::titleChanged, this, [winId, this] { + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::Title)); + }); + + connect(window, &LXQtTaskbarWlrootsWindow::appIdChanged, this, [winId, this] { + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::WindowClass)); + }); + + if (window->windowState.activated) { + LXQtTaskbarWlrootsWindow *effectiveActive = window; + while (effectiveActive->parentWindow) { + effectiveActive = getWindow(effectiveActive->parentWindow); + } + + lastActivated[effectiveActive->getWindowId()] = QTime::currentTime(); + activeWindow = effectiveActive->getWindowId(); + } + + connect(window, &LXQtTaskbarWlrootsWindow::activatedChanged, this, [window, this] { + WId effectiveWindow = window->getWindowId(); + + while (getWindow(effectiveWindow)->parentWindow) + { + effectiveWindow = getWindow(effectiveWindow)->parentWindow; + } + + if (window->windowState.activated) + { + lastActivated[effectiveWindow] = QTime::currentTime(); + + if (activeWindow != effectiveWindow) + { + activeWindow = effectiveWindow; + emit activeWindowChanged(activeWindow); + } + } + else + { + if (activeWindow == effectiveWindow) + { + activeWindow = 0; + emit activeWindowChanged(0); + } + } + }); + + connect(window, &LXQtTaskbarWlrootsWindow::parentChanged, this, [window, this] { + WId leader = window->parentWindow; + + /** Basically, check if this window is a transient */ + if (transients.remove(leader)) + { + if (leader) + { + // leader change. + transients.insert(window->getWindowId(), leader); + } + else + { + // lost a leader, add to regular windows list. + Q_ASSERT(findWindow(windows, leader) == 0); + + windows.push_back(leader); + } + } + + else if (leader) + { + eraseWindow(windows, window->getWindowId()); + lastActivated.remove(window->getWindowId()); + } + }); + + auto stateChanged = [window, this] { + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::State)); + }; + + connect(window, &LXQtTaskbarWlrootsWindow::fullscreenChanged, this, stateChanged); + + connect(window, &LXQtTaskbarWlrootsWindow::maximizedChanged, this, stateChanged); + + connect(window, &LXQtTaskbarWlrootsWindow::minimizedChanged, this, stateChanged); + + // Handle transient. + if (WId leader = window->parentWindow) + { + transients.insert(winId, leader); + } + else + { + windows.push_back(winId); + } + + emit windowAdded( winId ); + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::WindowClass)); + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::Title)); + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::Icon)); + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::State)); +} + +bool LXQtTaskbarWlrootsBackend::acceptWindow(WId window) const +{ + if(transients.contains(window)) + return false; + + return true; +} + +LXQtTaskbarWlrootsWindow *LXQtTaskbarWlrootsBackend::getWindow(WId windowId) const +{ + /** Easiest way is to convert the quintptr to the actual pointer */ + LXQtTaskbarWlrootsWindow *win = reinterpret_cast( windowId ); + if ( win ) { + return win; + } + + return nullptr; +} + + +int LXQtWMBackendWlrootsLibrary::getBackendScore(const QString& key) const +{ + if (key == QStringLiteral("wlroots")) + return 50; + + else if (key == QStringLiteral("wayfire")) + return 30; + + else if (key == QStringLiteral("sway")) + return 30; + + else if (key == QStringLiteral("hyprland")) + return 30; + + else if (key == QStringLiteral("labwc")) + return 30; + + // Unsupported + return 0; +} + +ILXQtAbstractWMInterface *LXQtWMBackendWlrootsLibrary::instance() const +{ + return new LXQtTaskbarWlrootsBackend(nullptr); +} diff --git a/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.h b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.h new file mode 100644 index 000000000..b354b3cdb --- /dev/null +++ b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.h @@ -0,0 +1,103 @@ +#pragma once + +#include "../../ilxqtabstractwmiface.h" + +#include +#include +#include + +class LXQtTaskbarWlrootsWindow; +class LXQtTaskbarWlrootsWindowManagment; +class LXQtWlrootsWaylandWorkspaceInfo; + + +class LXQtTaskbarWlrootsBackend : public ILXQtAbstractWMInterface +{ + Q_OBJECT + +public: + explicit LXQtTaskbarWlrootsBackend(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); + + // 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(WId wid); + bool acceptWindow(WId wid) const; + +private: + /** Convert WId (i.e. quintptr into LXQtTaskbarWlrootsWindow*) */ + LXQtTaskbarWlrootsWindow *getWindow(WId windowId) const; + + std::unique_ptr m_managment; + + QHash lastActivated; + WId activeWindow = 0; + std::vector windows; + + // key=transient child, value=leader + QHash transients; +}; + + +class LXQtWMBackendWlrootsLibrary: 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; +}; diff --git a/panel/backends/wayland/wlroots/wlr-foreign-toplevel-management-unstable-v1.xml b/panel/backends/wayland/wlroots/wlr-foreign-toplevel-management-unstable-v1.xml new file mode 100644 index 000000000..108133715 --- /dev/null +++ b/panel/backends/wayland/wlroots/wlr-foreign-toplevel-management-unstable-v1.xml @@ -0,0 +1,270 @@ + + + + Copyright © 2018 Ilia Bozhinov + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + The purpose of this protocol is to enable the creation of taskbars + and docks by providing them with a list of opened applications and + letting them request certain actions on them, like maximizing, etc. + + After a client binds the zwlr_foreign_toplevel_manager_v1, each opened + toplevel window will be sent via the toplevel event + + + + + This event is emitted whenever a new toplevel window is created. It + is emitted for all toplevels, regardless of the app that has created + them. + + All initial details of the toplevel(title, app_id, states, etc.) will + be sent immediately after this event via the corresponding events in + zwlr_foreign_toplevel_handle_v1. + + + + + + + Indicates the client no longer wishes to receive events for new toplevels. + However the compositor may emit further toplevel_created events, until + the finished event is emitted. + + The client must not send any more requests after this one. + + + + + + This event indicates that the compositor is done sending events to the + zwlr_foreign_toplevel_manager_v1. The server will destroy the object + immediately after sending this request, so it will become invalid and + the client should free any resources associated with it. + + + + + + + A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel + window. Each app may have multiple opened toplevels. + + Each toplevel has a list of outputs it is visible on, conveyed to the + client with the output_enter and output_leave events. + + + + + This event is emitted whenever the title of the toplevel changes. + + + + + + + This event is emitted whenever the app-id of the toplevel changes. + + + + + + + This event is emitted whenever the toplevel becomes visible on + the given output. A toplevel may be visible on multiple outputs. + + + + + + + This event is emitted whenever the toplevel stops being visible on + the given output. It is guaranteed that an entered-output event + with the same output has been emitted before this event. + + + + + + + Requests that the toplevel be maximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unmaximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be minimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unminimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Request that this toplevel be activated on the given seat. + There is no guarantee the toplevel will be actually activated. + + + + + + + The different states that a toplevel can have. These have the same meaning + as the states with the same names defined in xdg-toplevel + + + + + + + + + + + This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 + is created and each time the toplevel state changes, either because of a + compositor action or because of a request in this protocol. + + + + + + + + This event is sent after all changes in the toplevel state have been + sent. + + This allows changes to the zwlr_foreign_toplevel_handle_v1 properties + to be seen as atomic, even if they happen via multiple events. + + + + + + Send a request to the toplevel to close itself. The compositor would + typically use a shell-specific method to carry out this request, for + example by sending the xdg_toplevel.close event. However, this gives + no guarantees the toplevel will actually be destroyed. If and when + this happens, the zwlr_foreign_toplevel_handle_v1.closed event will + be emitted. + + + + + + The rectangle of the surface specified in this request corresponds to + the place where the app using this protocol represents the given toplevel. + It can be used by the compositor as a hint for some operations, e.g + minimizing. The client is however not required to set this, in which + case the compositor is free to decide some default value. + + If the client specifies more than one rectangle, only the last one is + considered. + + The dimensions are given in surface-local coordinates. + Setting width=height=0 removes the already-set rectangle. + + + + + + + + + + + + + + + + This event means the toplevel has been destroyed. It is guaranteed there + won't be any more events for this zwlr_foreign_toplevel_handle_v1. The + toplevel itself becomes inert so any requests will be ignored except the + destroy request. + + + + + + Destroys the zwlr_foreign_toplevel_handle_v1 object. + + This request should be called either when the client does not want to + use the toplevel anymore or after the closed event to finalize the + destruction of the object. + + + + + + + + Requests that the toplevel be fullscreened on the given output. If the + fullscreen state and/or the outputs the toplevel is visible on actually + change, this will be indicated by the state and output_enter/leave + events. + + The output parameter is only a hint to the compositor. Also, if output + is NULL, the compositor should decide which output the toplevel will be + fullscreened on, if at all. + + + + + + + Requests that the toplevel be unfullscreened. If the fullscreen state + actually changes, this will be indicated by the state event. + + + + + + + + This event is emitted whenever the parent of the toplevel changes. + + No event is emitted when the parent handle is destroyed by the client. + + + + + diff --git a/panel/lxqtpanelapplication.cpp b/panel/lxqtpanelapplication.cpp index 59dd09eb0..dba4d3c9e 100644 --- a/panel/lxqtpanelapplication.cpp +++ b/panel/lxqtpanelapplication.cpp @@ -44,111 +44,112 @@ #include "backends/lxqtdummywmbackend.h" - -static inline QList detectDesktopEnvironment() +static inline QMap getBackendScoreMap( QString compositor ) { - const QByteArray xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP"); - if (!xdgCurrentDesktop.isEmpty()) + QStringList dirs; + dirs << QProcessEnvironment::systemEnvironment().value(QStringLiteral("LXQTPANEL_PLUGIN_PATH")).split(QStringLiteral(":")); + dirs << QStringLiteral(PLUGIN_DIR); + + QMap backendScoreMap; + + for(const QString& dir : std::as_const(dirs)) { - // 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; + QDir backendsDir(dir); + if ( QFile::exists( dir + QStringLiteral("/backend") ) ) { + backendsDir.cd(QLatin1String("backend")); } - } - // Classic fallbacks - if (!qEnvironmentVariableIsEmpty("KDE_FULL_SESSION")) - return {QByteArrayLiteral("KDE")}; - - // Fallback to checking $DESKTOP_SESSION (unreliable) - QByteArray desktopSession = qgetenv("DESKTOP_SESSION"); + const auto entryList = backendsDir.entryInfoList(QStringList() << QStringLiteral("*.so"), QDir::Files|QDir::System|QDir::Readable); + for(QFileInfo info: entryList) + { + QPluginLoader loader(info.absoluteFilePath()); + if(!loader.load()) + { + QString err = loader.errorString(); + qWarning() << "Backend error:" << err; + } - // This can be a path in /usr/share/xsessions - int slash = desktopSession.lastIndexOf('/'); - // try decoding just the basename - desktopSession = desktopSession.mid(slash + 1); + QObject *plugin = loader.instance(); + if(!plugin) + continue; - if (desktopSession == "kde" || desktopSession == "plasma") - return {QByteArrayLiteral("KDE")}; + ILXQtWMBackendLibrary *backend = qobject_cast(plugin); + if(backend) + { + backendScoreMap[ info.fileName() ] = backend->getBackendScore( compositor ); + } + loader.unload(); + } + } - return {}; + return backendScoreMap; } -QString findBestBackend() +static inline QString getBackendFilePath( QString name ) { - QStringList dirs; - - // 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; + // If we do not have a full library name, line lib_labwc_backend.so, + // then build a name based on default heuristic: libwmbackend_.so + if (!name.startsWith(QStringLiteral("lib")) || !name.endsWith(QStringLiteral(".so"))) + { + if ( !name.startsWith( QStringLiteral("libwmbackend_") ) ) + { + name = QString( QStringLiteral("libwmbackend_%1") ).arg( name ); + } + if ( !name.endsWith( QStringLiteral(".so") ) ) + { + name = QString( QStringLiteral("%1.so") ).arg( name ); + } + } + QStringList dirs; + dirs << QProcessEnvironment::systemEnvironment().value(QStringLiteral("LXQTPANEL_PLUGIN_PATH")).split(QStringLiteral(":")); dirs << QStringLiteral(PLUGIN_DIR); - QString lastBackendFile; - int lastBackendScore = 0; + QMap backendScoreMap; - QList desktops = detectDesktopEnvironment(); - for(const QByteArray& desktop : desktops) + for(const QString& dir : std::as_const(dirs)) { - QString key = QString::fromUtf8(desktop); + QDir backendsDir(dir); + if ( QFile::exists( dir + QStringLiteral("/backend") ) ) { + backendsDir.cd(QLatin1String("backend")); + } - for(const QString& dir : std::as_const(dirs)) + if ( backendsDir.exists( name ) ) { - QDir backendsDir(dir); + return backendsDir.absoluteFilePath( name ); + } + } - if ( QFile::exists( dir + QStringLiteral("/backend") ) ) - { - backendsDir.cd(QLatin1String("backend")); - } + return QString(); +} - backendsDir.setNameFilters({QLatin1String("libwmbackend_*.so")}); - 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; - } +static inline bool testBackend( QString backendName ) +{ + QString backendPath = getBackendFilePath( backendName ); - QObject *plugin = loader.instance(); - if(!plugin) - continue; - - ILXQtWMBackendLibrary *backend = qobject_cast(plugin); - if(backend) - { - int score = backend->getBackendScore(key); - if(score > lastBackendScore) - { - lastBackendFile = absPath; - lastBackendScore = score; - } - } - loader.unload(); - } - } + QPluginLoader loader(backendPath); + if(!loader.load()) + { + qWarning() << "Backend error:" << loader.errorString(); + return false; + } - // Double the score before going to next key - lastBackendScore *= 2; + QObject *plugin = loader.instance(); + if(!plugin) { + qWarning() << "Failed to create the plugin instance"; + return false; } - if(lastBackendScore == 0) - return QString(); // No available backend is good for this environment + ILXQtWMBackendLibrary *backend = qobject_cast(plugin); + bool okay = false; + if(backend) + { + okay = true; + } - return lastBackendFile; + loader.unload(); + + return okay; } LXQtPanelApplicationPrivate::LXQtPanelApplicationPrivate(LXQtPanelApplication *q) @@ -189,44 +190,85 @@ ILXQtPanel::Position LXQtPanelApplicationPrivate::computeNewPanelPosition(const void LXQtPanelApplicationPrivate::loadBackend() { - QPluginLoader loader; + /** + * 1. Get the XDG_CURRENT_DESKTOP. It's a colon separate list. + * 2. Get the preferredBackend. It's a comma separated list. + * 3. First attempt to match some value in XDG_CURRENT_DESKTOP with any value in preferredBackend. + * 4. If it matches, end of story. Else, we attempt to deduce the backend based on XDG_CURRENT_DESKTOP: + * a. X11 -> xcb + * b. kwin_wayland -> plasma + * c. wayfire -> wayfire + * d. wayland -> wlroots + * e. other -> dummy + */ + + // Get and split XDG_CURRENT_DESKTOP. + QStringList xdgCurrentDesktops = qEnvironmentVariable( "XDG_CURRENT_DESKTOP" ).split( QStringLiteral(":") ); + + // Get and split XDG_SESSION_TYPE. + QString xdgSessionType = qEnvironmentVariable( "XDG_SESSION_TYPE" ); + + // Get the preferred backends + QStringList preferredBackends = mSettings->value(QStringLiteral("preferred_backend")).toStringList(); + + // The preferred backend + QString preferredBackend; + + for( QString backend: preferredBackends ) { + QStringList parts = backend.split(QStringLiteral(":")); + if (( parts[0] == xdgCurrentDesktops[ 1 ] ) && testBackend(parts[1])) { + preferredBackend = parts[1]; + break; + } + } - // First try to load user preferred backend - QString preferredBackend = mSettings->value(QStringLiteral("preferred_backend")).toString(); - if(!preferredBackend.isEmpty()) - { - loader.setFileName(preferredBackend); - loader.load(); + /** No special considerations. Attempt auto-detection of the platform */ + if ( preferredBackend.isEmpty() ) { + qDebug() << "No user preferences available. Attempting auto-detection."; - QObject *plugin = loader.instance(); - ILXQtWMBackendLibrary *backend = qobject_cast(plugin); - if(backend) - { - mWMBackend = backend->instance(); + // It's XCB/X11 + if ( xdgSessionType == QStringLiteral("x11") ) { + preferredBackend = QStringLiteral("xcb"); } - else - { - // Plugin not valid - loader.unload(); + + // It's wayland + else { + QMap backendScoreMap = getBackendScoreMap( xdgCurrentDesktops[ 1 ] ); + + int bestScore = 0; + for( QString backend: backendScoreMap.keys() ) { + if ( backendScoreMap[ backend ] > bestScore ) { + bestScore = backendScoreMap[ backend ]; + // No need to call testBackend(). + // We can be sure the plugin can be loaded. + // Because we have a score. + preferredBackend = backend; + } + } } } - if(!mWMBackend) + if ( preferredBackend.isEmpty() && xdgCurrentDesktops.contains( QStringLiteral("wlroots") ) ) { - // If user prefferred is not valid, find best available backend - QString fileName = findBestBackend(); + qDebug() << "Specialized backend unavailable. Falling back to generic wlroots"; + preferredBackend = QStringLiteral("wlroots"); + } - if(!fileName.isEmpty()) - { - loader.setFileName(fileName); - loader.load(); + QPluginLoader loader; + // We now have the preferred backend. + // We have taken into consideration, the user's choice. + // In case it was unavailable, a default one has been chosen. + if(!preferredBackend.isEmpty()) + { + loader.setFileName(getBackendFilePath(preferredBackend)); + if (loader.load()) + { QObject *plugin = loader.instance(); ILXQtWMBackendLibrary *backend = qobject_cast(plugin); if(backend) { mWMBackend = backend->instance(); - preferredBackend = fileName; } else { @@ -234,6 +276,10 @@ void LXQtPanelApplicationPrivate::loadBackend() loader.unload(); } } + else + { + qWarning() << loader.errorString(); + } } if(mWMBackend) From 4f095650eb2d6915d328014190e3b1a039c81096 Mon Sep 17 00:00:00 2001 From: Marcus Britanicus Date: Thu, 22 Aug 2024 12:51:09 +0530 Subject: [PATCH 17/42] Fix autodetection on user choice failure From deaf9d50cb294dc8d47c995042d86a88852a6db1 Mon Sep 17 00:00:00 2001 From: Marcus Britanicus Date: Thu, 22 Aug 2024 14:43:08 +0530 Subject: [PATCH 18/42] wmbackend_kwin_wayland: Compare against kwin_wayland. --- .../wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp index 1880a2060..6e5eab1c8 100644 --- a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp +++ b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp @@ -773,7 +773,8 @@ int LXQtWMBackendKWinWaylandLibrary::getBackendScore(const QString &key) const return 0; // Detect KWin Plasma - if(key == QLatin1String("KDE") || key == QLatin1String("KWIN")) + // The key will be kwin_wayland, not anything else. + if(key == QLatin1String("kwin_wayland")) return 100; // It's not useful for other wayland compositors From c464c96c585b48a2861fde3877a0d64d4123d6f6 Mon Sep 17 00:00:00 2001 From: Marcus Britanicus Date: Mon, 26 Aug 2024 16:41:32 +0530 Subject: [PATCH 19/42] Iterate through all parts of the XDG_CURRENT_DESKTOP. Add river to wlroots --- .../lxqtwmbackend_kwinwayland.cpp | 13 +++-- .../wayland/wlroots/lxqtwmbackend_wlr.cpp | 40 +++++++++++----- panel/lxqtpanelapplication.cpp | 47 ++++++++++++------- panel/resources/panel.conf | 3 ++ 4 files changed, 72 insertions(+), 31 deletions(-) diff --git a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp index 6e5eab1c8..0d39f4e17 100644 --- a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp +++ b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp @@ -772,10 +772,17 @@ int LXQtWMBackendKWinWaylandLibrary::getBackendScore(const QString &key) const if(!waylandApplication) return 0; - // Detect KWin Plasma - // The key will be kwin_wayland, not anything else. - if(key == QLatin1String("kwin_wayland")) + // Detect KWin Plasma (Wayland) + if(key == QLatin1String("KDE") || key == QLatin1String("KWIN")) + { + return 100; + } + + // kwin_wayland compositor, but not full-blown DE + else if (key == QLatin1String("kwin_wayland")) + { return 100; + } // It's not useful for other wayland compositors return 0; diff --git a/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp index a69520098..241efd2b8 100644 --- a/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp +++ b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp @@ -475,25 +475,41 @@ LXQtTaskbarWlrootsWindow *LXQtTaskbarWlrootsBackend::getWindow(WId windowId) con int LXQtWMBackendWlrootsLibrary::getBackendScore(const QString& key) const { - if (key == QStringLiteral("wlroots")) - return 50; + if (key == QStringLiteral("wlroots")) + { + return 50; + } - else if (key == QStringLiteral("wayfire")) - return 30; + else if (key == QStringLiteral("wayfire")) + { + return 30; + } - else if (key == QStringLiteral("sway")) - return 30; + else if (key == QStringLiteral("sway")) + { + return 30; + } - else if (key == QStringLiteral("hyprland")) - return 30; + else if (key == QStringLiteral("hyprland")) + { + return 30; + } - else if (key == QStringLiteral("labwc")) - return 30; + else if (key == QStringLiteral("labwc")) + { + return 30; + } - // Unsupported - return 0; + else if (key == QStringLiteral("river")) + { + return 30; + } + + // Unsupported + return 0; } + ILXQtAbstractWMInterface *LXQtWMBackendWlrootsLibrary::instance() const { return new LXQtTaskbarWlrootsBackend(nullptr); diff --git a/panel/lxqtpanelapplication.cpp b/panel/lxqtpanelapplication.cpp index dba4d3c9e..1970204ac 100644 --- a/panel/lxqtpanelapplication.cpp +++ b/panel/lxqtpanelapplication.cpp @@ -214,13 +214,24 @@ void LXQtPanelApplicationPrivate::loadBackend() // The preferred backend QString preferredBackend; - for( QString backend: preferredBackends ) { - QStringList parts = backend.split(QStringLiteral(":")); - if (( parts[0] == xdgCurrentDesktops[ 1 ] ) && testBackend(parts[1])) { - preferredBackend = parts[1]; - break; - } - } + for ( QString xdgCurrentDesktop: xdgCurrentDesktops ) + { + for ( QString backend: preferredBackends ) + { + QStringList parts = backend.split(QStringLiteral(":")); + // Invalid format + if (parts.count() != 2) + { + continue; + } + + if ((parts[0] == xdgCurrentDesktop) && testBackend(parts[1])) + { + preferredBackend = parts[1]; + break; + } + } + } /** No special considerations. Attempt auto-detection of the platform */ if ( preferredBackend.isEmpty() ) { @@ -233,16 +244,20 @@ void LXQtPanelApplicationPrivate::loadBackend() // It's wayland else { - QMap backendScoreMap = getBackendScoreMap( xdgCurrentDesktops[ 1 ] ); - int bestScore = 0; - for( QString backend: backendScoreMap.keys() ) { - if ( backendScoreMap[ backend ] > bestScore ) { - bestScore = backendScoreMap[ backend ]; - // No need to call testBackend(). - // We can be sure the plugin can be loaded. - // Because we have a score. - preferredBackend = backend; + for ( QString xdgCurrentDesktop: xdgCurrentDesktops ) + { + QMap backendScoreMap = getBackendScoreMap( xdgCurrentDesktop ); + for( QString backend: backendScoreMap.keys() ) + { + if ( backendScoreMap[ backend ] > bestScore ) + { + bestScore = backendScoreMap[ backend ]; + // No need to call testBackend(). + // We can be sure the plugin can be loaded. + // Because we have a score. + preferredBackend = backend; + } } } } diff --git a/panel/resources/panel.conf b/panel/resources/panel.conf index 2c0799cd4..fe2e9d990 100644 --- a/panel/resources/panel.conf +++ b/panel/resources/panel.conf @@ -1,5 +1,8 @@ panels=panel1 +[General] +preferred_backend=KDE:kwin_wayland,KWIN:kwin_wayland + [panel1] plugins=fancymenu,desktopswitch,quicklaunch,taskbar,statusnotifier,tray,mount,volume,worldclock,showdesktop position=Bottom From 626fede4409471a61598263ccfd832b1bde234c9 Mon Sep 17 00:00:00 2001 From: isf63 <121320947+isf63@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:36:42 -0400 Subject: [PATCH 20/42] Set size policy in plugin-backlight and plugin-colorpicker (#2049) Fixes an inconsistency in themes when they expect an expanding size policy, which is what most plugins have set. Some themes that do this: KDE-Plasma, Ambiance, Kvantum. Further stylesheet changes in some themes will be needed to match themes exactly. --- plugin-backlight/backlight.cpp | 1 + plugin-colorpicker/colorpicker.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/plugin-backlight/backlight.cpp b/plugin-backlight/backlight.cpp index 31247f7b1..d6c7086f9 100644 --- a/plugin-backlight/backlight.cpp +++ b/plugin-backlight/backlight.cpp @@ -34,6 +34,7 @@ LXQtBacklight::LXQtBacklight(const ILXQtPanelPluginStartupInfo &startupInfo): m_backlightButton = new QToolButton(); // use our own icon m_backlightButton->setIcon(QIcon::fromTheme(QStringLiteral("brightnesssettings"))); + m_backlightButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); connect(m_backlightButton, &QToolButton::clicked, this, &LXQtBacklight::showSlider); diff --git a/plugin-colorpicker/colorpicker.cpp b/plugin-colorpicker/colorpicker.cpp index 004314557..e6f541580 100644 --- a/plugin-colorpicker/colorpicker.cpp +++ b/plugin-colorpicker/colorpicker.cpp @@ -80,7 +80,7 @@ void ColorPicker::realign() ColorPickerWidget::ColorPickerWidget(QWidget *parent) : QWidget(parent) { - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); mSeparator = new QFrame(); mSeparator->setFrameShape(QFrame::VLine); @@ -93,12 +93,14 @@ ColorPickerWidget::ColorPickerWidget(QWidget *parent) : QWidget(parent) mPickerButton->setAccessibleName(mPickerButton->objectName()); mPickerButton->setAutoRaise(true); mPickerButton->setIcon(QIcon::fromTheme(QLatin1String("color-picker"), QIcon::fromTheme(QLatin1String("color-select-symbolic")))); + mPickerButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); mColorButton = new ColorButton(); mColorButton->setObjectName(QStringLiteral("ColorPickerColorButton")); mColorButton->setAccessibleName(mColorButton->objectName()); mColorButton->setAutoRaise(true); mColorButton->setStyleSheet(QStringLiteral("::menu-indicator{ image: none; }")); + mColorButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QBoxLayout *layout = new QBoxLayout(QBoxLayout::LeftToRight); layout->setContentsMargins(0, 0, 0, 0); From 9bd9b5f96fe18b7eabd002e81f3c79f392dcd34f Mon Sep 17 00:00:00 2001 From: LXQtBot <72348767+LXQtBot@users.noreply.github.com> Date: Tue, 27 Aug 2024 07:29:49 +0200 Subject: [PATCH 21/42] Weblate commit (#2085) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translate-URL: https://translate.lxqt-project.org/projects/lxqt-panel/plugin-fancymenu/da/ Translate-URL: https://translate.lxqt-project.org/projects/lxqt-panel/plugin-kbindindicator/ko/ Translation: LXQt Panel/plugin-fancymenu Translation: LXQt Panel/plugin-kbindicator Co-authored-by: Peter Jespersen Co-authored-by: 이정희 --- plugin-fancymenu/translations/fancymenu_da.ts | 2 +- plugin-kbindicator/translations/kbindicator_ko.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin-fancymenu/translations/fancymenu_da.ts b/plugin-fancymenu/translations/fancymenu_da.ts index 4855e1ecc..98cc2fc44 100644 --- a/plugin-fancymenu/translations/fancymenu_da.ts +++ b/plugin-fancymenu/translations/fancymenu_da.ts @@ -97,7 +97,7 @@ Layout - Layout + Udformning diff --git a/plugin-kbindicator/translations/kbindicator_ko.ts b/plugin-kbindicator/translations/kbindicator_ko.ts index a73c09baf..c96c607dc 100644 --- a/plugin-kbindicator/translations/kbindicator_ko.ts +++ b/plugin-kbindicator/translations/kbindicator_ko.ts @@ -80,7 +80,7 @@ Keyboard Layout Indicator - 키보드 레이아웃 표시기 + 키보드 자판 표시기 From d5876feb35fa9d5eec295205f2f5c4d22641d3ba Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 9 Jul 2024 12:45:35 +0200 Subject: [PATCH 22/42] 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 545617bb4..a0839eb42 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 c80576f25d9eb75c7a4c737b38379422f435a749 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 9 Jul 2024 16:35:19 +0200 Subject: [PATCH 23/42] 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 4ae0f1ff4e3cbd54fa70a1e189dd34da7d189c15 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 9 Jul 2024 16:40:24 +0200 Subject: [PATCH 24/42] 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 7fa109f356d68f5f352febe84d767103187be4ed Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Fri, 12 Jul 2024 11:27:04 +0200 Subject: [PATCH 25/42] 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 e5c7c6070cc8f98736a4e6f8b6cdc772d700eae1 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Sat, 17 Aug 2024 13:26:06 +0200 Subject: [PATCH 26/42] 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 ff84dd374cb74f4c822cbfc9e55b95170f01afa7 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 20 Aug 2024 13:15:14 +0200 Subject: [PATCH 27/42] 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 2133a12bedf60987ff1fa73beaa16274e391763d Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 20 Aug 2024 13:22:20 +0200 Subject: [PATCH 28/42] 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 76284835dfc2d38b693bda00aec91f9e1df88f37 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 20 Aug 2024 13:38:38 +0200 Subject: [PATCH 29/42] 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 7dd8aacaee767555ad82e75fdb6eae427b58035f Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 20 Aug 2024 13:58:17 +0200 Subject: [PATCH 30/42] 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 7874b2d2306d787591a2008e9788a4cd3d09071d Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Sat, 17 Feb 2024 19:18:09 +0100 Subject: [PATCH 31/42] 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 cc4af7d6e504d726c3ffafd35e61c7bf34efae33 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Sat, 17 Feb 2024 23:32:46 +0100 Subject: [PATCH 32/42] 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 decff276db6c2686d55ce7840387fcff59ad4d36 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Mon, 26 Feb 2024 09:48:03 +0100 Subject: [PATCH 33/42] 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 e6f541580..a862609b9 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) { @@ -110,7 +140,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(); @@ -164,29 +195,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(); } } @@ -197,6 +285,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 00ebd6f13f029f314717fba99095d7c249b28a6a Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 9 Apr 2024 11:52:46 +0200 Subject: [PATCH 34/42] 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 09af6ade059f71b10f156a3f5667a3a0d662a6e6 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Fri, 12 Jul 2024 11:53:16 +0200 Subject: [PATCH 35/42] 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 1ea3b73b7860b715fe14fd73e8074e14f6066448 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 20 Aug 2024 17:30:02 +0200 Subject: [PATCH 36/42] 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); + // } } }); From 22ad160cf746ab0b19c2b35dba4cd233aac10e9a Mon Sep 17 00:00:00 2001 From: Marcus Britanicus Date: Thu, 22 Aug 2024 12:45:50 +0530 Subject: [PATCH 37/42] Backend detection and Wlroots backend --- panel/backends/wayland/CMakeLists.txt | 1 + panel/backends/wayland/wlroots/CMakeLists.txt | 35 ++ .../wayland/wlroots/lxqttaskbarwlrwm.cpp | 545 ++++++++++++++++++ .../wayland/wlroots/lxqttaskbarwlrwm.h | 136 +++++ .../wayland/wlroots/lxqtwmbackend_wlr.cpp | 500 ++++++++++++++++ .../wayland/wlroots/lxqtwmbackend_wlr.h | 103 ++++ ...oreign-toplevel-management-unstable-v1.xml | 270 +++++++++ panel/lxqtpanelapplication.cpp | 256 ++++---- 8 files changed, 1741 insertions(+), 105 deletions(-) create mode 100644 panel/backends/wayland/wlroots/CMakeLists.txt create mode 100644 panel/backends/wayland/wlroots/lxqttaskbarwlrwm.cpp create mode 100644 panel/backends/wayland/wlroots/lxqttaskbarwlrwm.h create mode 100644 panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp create mode 100644 panel/backends/wayland/wlroots/lxqtwmbackend_wlr.h create mode 100644 panel/backends/wayland/wlroots/wlr-foreign-toplevel-management-unstable-v1.xml diff --git a/panel/backends/wayland/CMakeLists.txt b/panel/backends/wayland/CMakeLists.txt index 3f2c93189..19340698c 100644 --- a/panel/backends/wayland/CMakeLists.txt +++ b/panel/backends/wayland/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(kwin_wayland) +add_subdirectory(wlroots) diff --git a/panel/backends/wayland/wlroots/CMakeLists.txt b/panel/backends/wayland/wlroots/CMakeLists.txt new file mode 100644 index 000000000..aa53d5a99 --- /dev/null +++ b/panel/backends/wayland/wlroots/CMakeLists.txt @@ -0,0 +1,35 @@ +set(PLATFORM_NAME wlroots) + +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) +find_package(Qt6Xdg) + +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 Qt6::GuiPrivate) + +set( + SRC + lxqtwmbackend_wlr.cpp lxqtwmbackend_wlr.h + lxqttaskbarwlrwm.cpp lxqttaskbarwlrwm.h +) + +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 Qt6Xdg) + +qt6_generate_wayland_protocol_client_sources(${NAME} FILES + ${CMAKE_CURRENT_SOURCE_DIR}/wlr-foreign-toplevel-management-unstable-v1.xml +) diff --git a/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.cpp b/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.cpp new file mode 100644 index 000000000..7a84ab630 --- /dev/null +++ b/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.cpp @@ -0,0 +1,545 @@ +#include "lxqttaskbarwlrwm.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include + +QString U8Str( const char *str ) { + return QString::fromUtf8( str ); +} + +static inline QString getPixmapIcon(QString name) +{ + QStringList paths{ + U8Str("/usr/local/share/pixmaps/"), + U8Str("/usr/share/pixmaps/"), + }; + + QStringList sfxs{ + U8Str( ".svg" ), U8Str( ".png" ), U8Str( ".xpm" ) + }; + + for (QString path: paths) + { + for (QString sfx: sfxs) + { + if (QFile::exists(path + name + sfx)) + { + return path + name + sfx; + } + } + } + + return QString(); +} + + +QIcon getIconForAppId(QString mAppId) +{ + if (mAppId.isEmpty() or (mAppId == U8Str("Unknown"))) + { + return QIcon(); + } + + /** Wine apps */ + if (mAppId.endsWith(U8Str(".exe"))) + { + return QIcon::fromTheme(U8Str("wine")); + } + + /** Check if a theme icon exists called @mAppId */ + if (QIcon::hasThemeIcon(mAppId)) + { + return QIcon::fromTheme(mAppId); + } + + /** Check if the theme icon is @mAppId, but all lower-case letters */ + else if (QIcon::hasThemeIcon(mAppId.toLower())) + { + return QIcon::fromTheme(mAppId.toLower()); + } + + QStringList appDirs = { + QDir::home().filePath(U8Str(".local/share/applications/")), + U8Str("/usr/local/share/applications/"), + U8Str("/usr/share/applications/"), + }; + + /** + * Assume mAppId == desktop-file-name (ideal situation) + * or mAppId.toLower() == desktop-file-name (cheap fallback) + */ + QString iconName; + + for (QString path: appDirs) + { + /** Get the icon name from desktop (mAppId: as it is) */ + if (QFile::exists(path + mAppId + U8Str(".desktop"))) + { + QSettings desktop(path + mAppId + U8Str(".desktop"), QSettings::IniFormat); + iconName = desktop.value(U8Str("Desktop Entry/Icon")).toString(); + } + + /** Get the icon name from desktop (mAppId: all lower-case letters) */ + else if (QFile::exists(path + mAppId.toLower() + U8Str(".desktop"))) + { + QSettings desktop(path + mAppId.toLower() + U8Str(".desktop"), QSettings::IniFormat); + iconName = desktop.value(U8Str("Desktop Entry/Icon")).toString(); + } + + /** No icon specified: try else-where */ + if (iconName.isEmpty()) + { + continue; + } + + /** We got an iconName, and it's in the current theme */ + if (QIcon::hasThemeIcon(iconName)) + { + return QIcon::fromTheme(iconName); + } + + /** Not a theme icon, but an absolute path */ + else if (QFile::exists(iconName)) + { + return QIcon(iconName); + } + + /** Not theme icon or absolute path. So check /usr/share/pixmaps/ */ + else + { + iconName = getPixmapIcon(iconName); + + if (not iconName.isEmpty()) + { + return QIcon(iconName); + } + } + } + + /* Check all desktop files for @mAppId */ + for (QString path: appDirs) + { + QStringList desktops = QDir(path).entryList({ U8Str("*.desktop") }); + for (QString dskf: desktops) + { + QSettings desktop(path + dskf, QSettings::IniFormat); + + QString exec = desktop.value(U8Str("Desktop Entry/Exec"), U8Str("abcd1234/-")).toString(); + QString name = desktop.value(U8Str("Desktop Entry/Name"), U8Str("abcd1234/-")).toString(); + QString cls = desktop.value(U8Str("Desktop Entry/StartupWMClass"), U8Str("abcd1234/-")).toString(); + + QString execPath = U8Str(std::filesystem::path(exec.toStdString()).filename().c_str()); + + if (mAppId.compare(execPath, Qt::CaseInsensitive) == 0) + { + iconName = desktop.value(U8Str("Desktop Entry/Icon")).toString(); + } + + else if (mAppId.compare(name, Qt::CaseInsensitive) == 0) + { + iconName = desktop.value(U8Str("Desktop Entry/Icon")).toString(); + } + + else if (mAppId.compare(cls, Qt::CaseInsensitive) == 0) + { + iconName = desktop.value(U8Str("Desktop Entry/Icon")).toString(); + } + + if (not iconName.isEmpty()) + { + if (QIcon::hasThemeIcon(iconName)) + { + return QIcon::fromTheme(iconName); + } + + else if (QFile::exists(iconName)) + { + return QIcon(iconName); + } + + else + { + iconName = getPixmapIcon(iconName); + + if (not iconName.isEmpty()) + { + return QIcon(iconName); + } + } + } + } + } + + iconName = getPixmapIcon(iconName); + + if (not iconName.isEmpty()) + { + return QIcon(iconName); + } + + return QIcon(); +} + + +static inline wl_seat *get_seat() +{ + QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); + + if (!native) + { + return nullptr; + } + + struct wl_seat *seat = reinterpret_cast(native->nativeResourceForIntegration("wl_seat")); + + return seat; +} + + +/* + * LXQtTaskbarWlrootsWindowManagment + */ + +LXQtTaskbarWlrootsWindowManagment::LXQtTaskbarWlrootsWindowManagment() : QWaylandClientExtensionTemplate(version) +{ + /** Automatically destroy thie object */ + connect( + this, &QWaylandClientExtension::activeChanged, this, [ this ] { + if (!isActive()) + { + zwlr_foreign_toplevel_manager_v1_destroy(object()); + } + }); +} + + +LXQtTaskbarWlrootsWindowManagment::~LXQtTaskbarWlrootsWindowManagment() +{ + if (isActive()) + { + zwlr_foreign_toplevel_manager_v1_destroy(object()); + } +} + + +void LXQtTaskbarWlrootsWindowManagment::zwlr_foreign_toplevel_manager_v1_toplevel(struct ::zwlr_foreign_toplevel_handle_v1 *toplevel) +{ + /** + * A window was created. + * Wait for the window to become ready, i.e. wait for done() event to be sent by the compositor. + * Once we recieve done(), emit the windowReady() signal. + */ + + auto w = new LXQtTaskbarWlrootsWindow(toplevel); + + connect(w, &LXQtTaskbarWlrootsWindow::windowReady, [w, this] () { + emit windowCreated(w->getWindowId()); + }); +} + + +/* + * LXQtTaskbarWlrootsWindow + */ + +LXQtTaskbarWlrootsWindow::LXQtTaskbarWlrootsWindow(::zwlr_foreign_toplevel_handle_v1 *id) : zwlr_foreign_toplevel_handle_v1(id) +{ +} + + +LXQtTaskbarWlrootsWindow::~LXQtTaskbarWlrootsWindow() +{ + destroy(); +} + + +void LXQtTaskbarWlrootsWindow::activate() +{ + /** + * Activate on default seat. + * TODO: Worry about multi-seat setups, when we have no other worries :P + */ + zwlr_foreign_toplevel_handle_v1::activate(get_seat()); +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_title(const QString& title) +{ + /** Store the incoming title in pending */ + m_pendingState.title = title; + m_pendingState.titleChanged = true; +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_app_id(const QString& app_id) +{ + /** Store the incoming appId in pending */ + m_pendingState.appId = app_id; + m_pendingState.appIdChanged = true; + + /** Update the icon */ + this->icon = getIconForAppId(app_id); + + /** We did not get any icon from app-id. Let's use application-x-executable */ + if (this->icon.pixmap(64).width() == 0) + { + this->icon = XdgIcon::fromTheme(QString::fromUtf8("application-x-executable")); + } +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_output_enter(struct ::wl_output *output) +{ + /** This view was added to an output */ + m_pendingState.outputs << output; + m_pendingState.outputsChanged = true; +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_output_leave(struct ::wl_output *output) +{ + /** This view was removed from an output; store it in pending. */ + m_pendingState.outputsLeft << output; + + if (m_pendingState.outputs.contains(output)) + { + m_pendingState.outputs.removeAll(output); + } + + m_pendingState.outputsChanged = true; +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_state(wl_array *state) +{ + /** State of this window was changed; store it in pending. */ + auto *states = static_cast(state->data); + int numStates = static_cast(state->size / sizeof(uint32_t)); + + for (int i = 0; i < numStates; i++) + { + switch ((uint32_t)states[ i ]) + { + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED: { + m_pendingState.maximized = true; + m_pendingState.maximizedChanged = true; + break; + } + + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED: { + m_pendingState.minimized = true; + m_pendingState.minimizedChanged = true; + break; + } + + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED: { + m_pendingState.activated = true; + m_pendingState.activatedChanged = true; + break; + } + + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN: { + m_pendingState.fullscreen = true; + m_pendingState.fullscreenChanged = true; + break; + } + } + } +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_done() +{ + /** + * All the states/properties have been sent. + * We can now emit the signals and clear the pending state: + * 1. Update all the variables first. + * 2. Then clear the m_pendingState. + * 3. Emit the changed signals. + * 4. Finally, cleanr the m_pendingState.Changed flags. + */ + + // (1) title, if it changed + if (m_pendingState.titleChanged) + { + windowState.title = m_pendingState.title; + } + + // (2) appId, if it changed + if (m_pendingState.appIdChanged) + { + windowState.appId = m_pendingState.appId; + } + + // (3) outputs, if they changed + if (m_pendingState.outputsChanged) + { + for (::wl_output *op: m_pendingState.outputsLeft) + { + if (windowState.outputs.contains(op)) + { + windowState.outputs.removeAll(op); + } + } + + for (::wl_output *op: m_pendingState.outputs) + { + if (!windowState.outputs.contains(op)) + { + windowState.outputs << op; + } + } + } + + // (4) states, if they changed. Don't trust the changed flag. + if (m_pendingState.maximized != windowState.maximized) + { + windowState.maximized = m_pendingState.maximized; + m_pendingState.maximizedChanged = true; + } + + if (m_pendingState.minimized != windowState.minimized) + { + windowState.minimized = m_pendingState.minimized; + m_pendingState.minimizedChanged = true; + } + + if (m_pendingState.activated != windowState.activated) + { + windowState.activated = m_pendingState.activated; + m_pendingState.activatedChanged = true; + } + + if (m_pendingState.fullscreen != windowState.fullscreen) + { + windowState.fullscreen = m_pendingState.fullscreen; + m_pendingState.fullscreenChanged = true; + } + + // (5) parent, if it changed. + if (m_pendingState.parentChanged) + { + if (m_pendingState.parent) + { + setParentWindow(new LXQtTaskbarWlrootsWindow(m_pendingState.parent)); + } + + else + { + setParentWindow(nullptr); + } + } + + /** 2. Clear all m_pendingState. for next run */ + m_pendingState.title = QString(); + m_pendingState.appId = QString(); + m_pendingState.outputs.clear(); + m_pendingState.maximized = false; + m_pendingState.minimized = false; + m_pendingState.activated = false; + m_pendingState.fullscreen = false; + m_pendingState.parent = nullptr; + + /** + * 3. Emit signals + * (a) First time done was emitted after the window was created. + * (b) Other times. + */ + + /** (a) First time done was emitted */ + if (initDone == false) + { + /** + * All the states/properties are already set. + * Any query will give valid results. + */ + initDone = true; + emit windowReady(); + } + + /** (b) All the subsequent times */ + else + { + if (m_pendingState.titleChanged) + emit titleChanged(); + if (m_pendingState.appIdChanged) + emit appIdChanged(); + if (m_pendingState.outputsChanged) + emit outputsChanged(); + if (m_pendingState.maximizedChanged) + emit maximizedChanged(); + if (m_pendingState.minimizedChanged) + emit minimizedChanged(); + if (m_pendingState.activatedChanged) + emit activatedChanged(); + if (m_pendingState.fullscreenChanged) + emit fullscreenChanged(); + if (m_pendingState.parentChanged) + emit parentChanged(); + + emit stateChanged(); + } + + /** 4. Clear m+m_pendingState.Changed flags */ + m_pendingState.titleChanged = false; + m_pendingState.appIdChanged = false; + m_pendingState.outputsChanged = false; + m_pendingState.maximizedChanged = false; + m_pendingState.minimizedChanged = false; + m_pendingState.activatedChanged = false; + m_pendingState.fullscreenChanged = false; + m_pendingState.parentChanged = false; +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_closed() +{ + /** This window was closed */ + emit closed(); +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_parent(struct ::zwlr_foreign_toplevel_handle_v1 *parent) +{ + /** Parent of this window changed; store it in pending. */ + m_pendingState.parent = parent; + m_pendingState.parentChanged = true; +} + + +void LXQtTaskbarWlrootsWindow::setParentWindow(LXQtTaskbarWlrootsWindow *parent) +{ + QObject::disconnect(parentWindowUnmappedConnection); + + if (parent) + { + parentWindow = parent->getWindowId(); + parentWindowUnmappedConnection = QObject::connect( + parent, &LXQtTaskbarWlrootsWindow::closed, this, [ this ] { + setParentWindow(nullptr); + }); + } + else + { + parentWindow = 0; + parentWindowUnmappedConnection = QMetaObject::Connection(); + } +} diff --git a/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.h b/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.h new file mode 100644 index 000000000..c79f174da --- /dev/null +++ b/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.h @@ -0,0 +1,136 @@ +#pragma once + +#include +#include +#include + +#include "qwayland-wlr-foreign-toplevel-management-unstable-v1.h" +#include "wayland-wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" + +typedef quintptr WId; + +class LXQtTaskbarWlrootsWindow; + +class LXQtTaskbarWlrootsWindowManagment : public QWaylandClientExtensionTemplate, + public QtWayland::zwlr_foreign_toplevel_manager_v1 +{ + Q_OBJECT +public: + static constexpr int version = 16; + + LXQtTaskbarWlrootsWindowManagment(); + ~LXQtTaskbarWlrootsWindowManagment(); + + inline bool isShowingDesktop() const { return m_isShowingDesktop; } + +protected: + void zwlr_foreign_toplevel_manager_v1_toplevel(struct ::zwlr_foreign_toplevel_handle_v1 *toplevel); + void zwlr_foreign_toplevel_manager_v1_finished() {}; + +Q_SIGNALS: + void windowCreated(WId wid); + +private: + bool m_isShowingDesktop = false; +}; + +using WindowState = QtWayland::zwlr_foreign_toplevel_handle_v1::state; + +class WindowProperties { + public: + /** Title of the window */ + QString title = QString::fromUtf8( "untitled" ); + bool titleChanged = false; + + /** appId of the window */ + QString appId = QString::fromUtf8( "unidentified" ); + bool appIdChanged = false; + + /** List of outputs which the window is currently on */ + QList<::wl_output *> outputs; + bool outputsChanged = false; + + /** Is maximized */ + bool maximized = false; + bool maximizedChanged = false; + + /** Is minimized */ + bool minimized = false; + bool minimizedChanged = false; + + /** Is active */ + bool activated = false; + bool activatedChanged = false; + + /** Is fullscreen */ + bool fullscreen = false; + bool fullscreenChanged = false; + + /** Parent of this view, can be null */ + ::zwlr_foreign_toplevel_handle_v1 * parent = nullptr; + bool parentChanged = false; + + /** List of outputs from which window has left */ + QList<::wl_output *> outputsLeft; +}; + +class LXQtTaskbarWlrootsWindow : public QObject, + public QtWayland::zwlr_foreign_toplevel_handle_v1 +{ + Q_OBJECT +public: + LXQtTaskbarWlrootsWindow(::zwlr_foreign_toplevel_handle_v1 *id); + ~LXQtTaskbarWlrootsWindow(); + + inline WId getWindowId() const { return reinterpret_cast(this); } + + void activate(); + + QIcon icon; + WindowProperties windowState; + WId parentWindow = 0; + +Q_SIGNALS: + void titleChanged(); + void appIdChanged(); + void outputsChanged(); + + /** Individual state change signals */ + void maximizedChanged(); + void minimizedChanged(); + void activatedChanged(); + void fullscreenChanged(); + + void parentChanged(); + + /** Bulk state change signal */ + void stateChanged(); + + /** First state change signal: Before this, the window did not have a valid state */ + void windowReady(); + + /** All state changes have been sent. */ + void done(); + + /** Window closed signal */ + void closed(); + +protected: + void zwlr_foreign_toplevel_handle_v1_title(const QString &title); + void zwlr_foreign_toplevel_handle_v1_app_id(const QString &app_id); + void zwlr_foreign_toplevel_handle_v1_output_enter(struct ::wl_output *output); + void zwlr_foreign_toplevel_handle_v1_output_leave(struct ::wl_output *output); + void zwlr_foreign_toplevel_handle_v1_state(wl_array *state); + void zwlr_foreign_toplevel_handle_v1_done(); + void zwlr_foreign_toplevel_handle_v1_closed(); + void zwlr_foreign_toplevel_handle_v1_parent(struct ::zwlr_foreign_toplevel_handle_v1 *parent); + +private: + void setParentWindow(LXQtTaskbarWlrootsWindow *parent); + + QMetaObject::Connection parentWindowUnmappedConnection; + + WindowProperties m_pendingState; + + bool initDone = false; +}; diff --git a/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp new file mode 100644 index 000000000..a69520098 --- /dev/null +++ b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp @@ -0,0 +1,500 @@ +#include "lxqttaskbarwlrwm.h" +#include "lxqtwmbackend_wlr.h" + +#include +#include +#include +#include + +// Function to search for a window in the vector +WId findWindow(const std::vector& windows, WId tgt) { + // Use std::find to locate the target window + auto it = std::find(windows.begin(), windows.end(), tgt); + + // Check if the window was found (iterator points to windows.end() if not found) + if (it != windows.end()) { + // If found, return the window ID by dereferencing the iterator + return *it; + } + + return 0; +} + +// Function to erase a window from the vector +void eraseWindow(std::vector& windows, WId tgt) { + // Use std::vector::iterator to find the window + auto it = std::find(windows.begin(), windows.end(), tgt); + + // Check if the window was found + if (it != windows.end()) { + // If found, erase the element pointed to by the iterator + windows.erase(it); + } +} + +LXQtTaskbarWlrootsBackend::LXQtTaskbarWlrootsBackend(QObject *parent) : + ILXQtAbstractWMInterface(parent) +{ + m_managment.reset(new LXQtTaskbarWlrootsWindowManagment); + + connect(m_managment.get(), &LXQtTaskbarWlrootsWindowManagment::windowCreated, this, &LXQtTaskbarWlrootsBackend::addWindow); +} + +bool LXQtTaskbarWlrootsBackend::supportsAction(WId, LXQtTaskBarBackendAction action) const +{ + switch (action) + { + case LXQtTaskBarBackendAction::Maximize: + return true; + + case LXQtTaskBarBackendAction::Minimize: + return true; + + case LXQtTaskBarBackendAction::FullScreen: + return true; + + default: + return false; + } + + return false; +} + +bool LXQtTaskbarWlrootsBackend::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 LXQtTaskbarWlrootsBackend::getCurrentWindows() const +{ + QVector wids; + + for( WId wid: windows ){ + wids << wid; + } + + return wids; +} + +QString LXQtTaskbarWlrootsBackend::getWindowTitle(WId windowId) const +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return QString(); + + return window->windowState.title; +} + +bool LXQtTaskbarWlrootsBackend::applicationDemandsAttention(WId) const +{ + return false; +} + +QIcon LXQtTaskbarWlrootsBackend::getApplicationIcon(WId windowId, int devicePixels) const +{ + Q_UNUSED(devicePixels) + + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return QIcon(); + + return window->icon; +} + +QString LXQtTaskbarWlrootsBackend::getWindowClass(WId windowId) const +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return QString(); + return window->windowState.appId; +} + +LXQtTaskBarWindowLayer LXQtTaskbarWlrootsBackend::getWindowLayer(WId) const +{ + return LXQtTaskBarWindowLayer::Normal; +} + +bool LXQtTaskbarWlrootsBackend::setWindowLayer(WId, LXQtTaskBarWindowLayer) +{ + return false; +} + +LXQtTaskBarWindowState LXQtTaskbarWlrootsBackend::getWindowState(WId windowId) const +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return LXQtTaskBarWindowState::Normal; + + if(window->windowState.minimized) + return LXQtTaskBarWindowState::Minimized; + + if(window->windowState.maximized) + return LXQtTaskBarWindowState::Maximized; + + if(window->windowState.fullscreen) + return LXQtTaskBarWindowState::FullScreen; + + return LXQtTaskBarWindowState::Normal; +} + +bool LXQtTaskbarWlrootsBackend::setWindowState(WId windowId, LXQtTaskBarWindowState state, bool set) +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return false; + + switch (state) + { + case LXQtTaskBarWindowState::Minimized: + { + if ( set ) { + window->set_minimized(); + } + + else { + window->unset_minimized(); + } + + break; + } + case LXQtTaskBarWindowState::Maximized: + case LXQtTaskBarWindowState::MaximizedVertically: + case LXQtTaskBarWindowState::MaximizedHorizontally: + { + if ( set ) { + window->set_maximized(); + } + + else { + window->unset_maximized(); + } + + break; + } + case LXQtTaskBarWindowState::Normal: + { + if (set) + { + if ( window->windowState.maximized) { + window->unset_maximized(); + } + } + + break; + } + + case LXQtTaskBarWindowState::FullScreen: + { + if ( set ) { + window->set_fullscreen(nullptr); + } + + else { + window->unset_fullscreen(); + } + break; + } + + default: + return false; + } + + return true; +} + +bool LXQtTaskbarWlrootsBackend::isWindowActive(WId windowId) const +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return false; + + return activeWindow == windowId || window->windowState.activated; +} + +bool LXQtTaskbarWlrootsBackend::raiseWindow(WId windowId, bool onCurrentWorkSpace) +{ + Q_UNUSED(onCurrentWorkSpace) // Cannot be done on a generic wlroots-based compositor! + + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return false; + + window->activate(); + return true; +} + +bool LXQtTaskbarWlrootsBackend::closeWindow(WId windowId) +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return false; + + window->close(); + return true; +} + +WId LXQtTaskbarWlrootsBackend::getActiveWindow() const +{ + return activeWindow; +} + +int LXQtTaskbarWlrootsBackend::getWorkspacesCount() const +{ + return 1; +} + +QString LXQtTaskbarWlrootsBackend::getWorkspaceName(int) const +{ + return QStringLiteral("Desktop 1"); +} + +int LXQtTaskbarWlrootsBackend::getCurrentWorkspace() const +{ + return 1; +} + +bool LXQtTaskbarWlrootsBackend::setCurrentWorkspace(int) +{ + return false; +} + +int LXQtTaskbarWlrootsBackend::getWindowWorkspace(WId) const +{ + return 1; +} + +bool LXQtTaskbarWlrootsBackend::setWindowOnWorkspace(WId, int) +{ + return true; +} + +void LXQtTaskbarWlrootsBackend::moveApplicationToPrevNextMonitor(WId, bool, bool) +{ +} + +bool LXQtTaskbarWlrootsBackend::isWindowOnScreen(QScreen *, WId) const +{ + // TODO: Manage based on output-enter/output-leave + return true; +} + +bool LXQtTaskbarWlrootsBackend::setDesktopLayout(Qt::Orientation, int, int, bool) { + // Wlroots has no support for workspace as of 2024-August-20 + return false; +} + +void LXQtTaskbarWlrootsBackend::moveApplication(WId) +{ +} + +void LXQtTaskbarWlrootsBackend::resizeApplication(WId) +{ +} + +void LXQtTaskbarWlrootsBackend::refreshIconGeometry(WId, const QRect &) +{ + +} + +bool LXQtTaskbarWlrootsBackend::isAreaOverlapped(const QRect &) const +{ + return false; +} + +bool LXQtTaskbarWlrootsBackend::isShowingDesktop() const +{ + return m_managment->isShowingDesktop(); +} + +bool LXQtTaskbarWlrootsBackend::showDesktop(bool) +{ + return false; +} + +void LXQtTaskbarWlrootsBackend::addWindow(WId winId) +{ + if (findWindow(windows, winId) != 0 || transients.contains(winId)) + { + return; + } + + auto removeWindow = [winId, this] + { + eraseWindow(windows, winId); + lastActivated.remove(winId); + + if (activeWindow == winId) + { + activeWindow = 0; + emit activeWindowChanged(0); + } + + emit windowRemoved(winId); + }; + + LXQtTaskbarWlrootsWindow *window = getWindow( winId ); + if ( window == nullptr ) { + return; + } + + /** The window was closed. Remove from our lists */ + connect(window, &LXQtTaskbarWlrootsWindow::closed, this, removeWindow); + + /** */ + connect(window, &LXQtTaskbarWlrootsWindow::titleChanged, this, [winId, this] { + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::Title)); + }); + + connect(window, &LXQtTaskbarWlrootsWindow::appIdChanged, this, [winId, this] { + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::WindowClass)); + }); + + if (window->windowState.activated) { + LXQtTaskbarWlrootsWindow *effectiveActive = window; + while (effectiveActive->parentWindow) { + effectiveActive = getWindow(effectiveActive->parentWindow); + } + + lastActivated[effectiveActive->getWindowId()] = QTime::currentTime(); + activeWindow = effectiveActive->getWindowId(); + } + + connect(window, &LXQtTaskbarWlrootsWindow::activatedChanged, this, [window, this] { + WId effectiveWindow = window->getWindowId(); + + while (getWindow(effectiveWindow)->parentWindow) + { + effectiveWindow = getWindow(effectiveWindow)->parentWindow; + } + + if (window->windowState.activated) + { + lastActivated[effectiveWindow] = QTime::currentTime(); + + if (activeWindow != effectiveWindow) + { + activeWindow = effectiveWindow; + emit activeWindowChanged(activeWindow); + } + } + else + { + if (activeWindow == effectiveWindow) + { + activeWindow = 0; + emit activeWindowChanged(0); + } + } + }); + + connect(window, &LXQtTaskbarWlrootsWindow::parentChanged, this, [window, this] { + WId leader = window->parentWindow; + + /** Basically, check if this window is a transient */ + if (transients.remove(leader)) + { + if (leader) + { + // leader change. + transients.insert(window->getWindowId(), leader); + } + else + { + // lost a leader, add to regular windows list. + Q_ASSERT(findWindow(windows, leader) == 0); + + windows.push_back(leader); + } + } + + else if (leader) + { + eraseWindow(windows, window->getWindowId()); + lastActivated.remove(window->getWindowId()); + } + }); + + auto stateChanged = [window, this] { + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::State)); + }; + + connect(window, &LXQtTaskbarWlrootsWindow::fullscreenChanged, this, stateChanged); + + connect(window, &LXQtTaskbarWlrootsWindow::maximizedChanged, this, stateChanged); + + connect(window, &LXQtTaskbarWlrootsWindow::minimizedChanged, this, stateChanged); + + // Handle transient. + if (WId leader = window->parentWindow) + { + transients.insert(winId, leader); + } + else + { + windows.push_back(winId); + } + + emit windowAdded( winId ); + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::WindowClass)); + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::Title)); + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::Icon)); + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::State)); +} + +bool LXQtTaskbarWlrootsBackend::acceptWindow(WId window) const +{ + if(transients.contains(window)) + return false; + + return true; +} + +LXQtTaskbarWlrootsWindow *LXQtTaskbarWlrootsBackend::getWindow(WId windowId) const +{ + /** Easiest way is to convert the quintptr to the actual pointer */ + LXQtTaskbarWlrootsWindow *win = reinterpret_cast( windowId ); + if ( win ) { + return win; + } + + return nullptr; +} + + +int LXQtWMBackendWlrootsLibrary::getBackendScore(const QString& key) const +{ + if (key == QStringLiteral("wlroots")) + return 50; + + else if (key == QStringLiteral("wayfire")) + return 30; + + else if (key == QStringLiteral("sway")) + return 30; + + else if (key == QStringLiteral("hyprland")) + return 30; + + else if (key == QStringLiteral("labwc")) + return 30; + + // Unsupported + return 0; +} + +ILXQtAbstractWMInterface *LXQtWMBackendWlrootsLibrary::instance() const +{ + return new LXQtTaskbarWlrootsBackend(nullptr); +} diff --git a/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.h b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.h new file mode 100644 index 000000000..b354b3cdb --- /dev/null +++ b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.h @@ -0,0 +1,103 @@ +#pragma once + +#include "../../ilxqtabstractwmiface.h" + +#include +#include +#include + +class LXQtTaskbarWlrootsWindow; +class LXQtTaskbarWlrootsWindowManagment; +class LXQtWlrootsWaylandWorkspaceInfo; + + +class LXQtTaskbarWlrootsBackend : public ILXQtAbstractWMInterface +{ + Q_OBJECT + +public: + explicit LXQtTaskbarWlrootsBackend(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); + + // 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(WId wid); + bool acceptWindow(WId wid) const; + +private: + /** Convert WId (i.e. quintptr into LXQtTaskbarWlrootsWindow*) */ + LXQtTaskbarWlrootsWindow *getWindow(WId windowId) const; + + std::unique_ptr m_managment; + + QHash lastActivated; + WId activeWindow = 0; + std::vector windows; + + // key=transient child, value=leader + QHash transients; +}; + + +class LXQtWMBackendWlrootsLibrary: 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; +}; diff --git a/panel/backends/wayland/wlroots/wlr-foreign-toplevel-management-unstable-v1.xml b/panel/backends/wayland/wlroots/wlr-foreign-toplevel-management-unstable-v1.xml new file mode 100644 index 000000000..108133715 --- /dev/null +++ b/panel/backends/wayland/wlroots/wlr-foreign-toplevel-management-unstable-v1.xml @@ -0,0 +1,270 @@ + + + + Copyright © 2018 Ilia Bozhinov + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + The purpose of this protocol is to enable the creation of taskbars + and docks by providing them with a list of opened applications and + letting them request certain actions on them, like maximizing, etc. + + After a client binds the zwlr_foreign_toplevel_manager_v1, each opened + toplevel window will be sent via the toplevel event + + + + + This event is emitted whenever a new toplevel window is created. It + is emitted for all toplevels, regardless of the app that has created + them. + + All initial details of the toplevel(title, app_id, states, etc.) will + be sent immediately after this event via the corresponding events in + zwlr_foreign_toplevel_handle_v1. + + + + + + + Indicates the client no longer wishes to receive events for new toplevels. + However the compositor may emit further toplevel_created events, until + the finished event is emitted. + + The client must not send any more requests after this one. + + + + + + This event indicates that the compositor is done sending events to the + zwlr_foreign_toplevel_manager_v1. The server will destroy the object + immediately after sending this request, so it will become invalid and + the client should free any resources associated with it. + + + + + + + A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel + window. Each app may have multiple opened toplevels. + + Each toplevel has a list of outputs it is visible on, conveyed to the + client with the output_enter and output_leave events. + + + + + This event is emitted whenever the title of the toplevel changes. + + + + + + + This event is emitted whenever the app-id of the toplevel changes. + + + + + + + This event is emitted whenever the toplevel becomes visible on + the given output. A toplevel may be visible on multiple outputs. + + + + + + + This event is emitted whenever the toplevel stops being visible on + the given output. It is guaranteed that an entered-output event + with the same output has been emitted before this event. + + + + + + + Requests that the toplevel be maximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unmaximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be minimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unminimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Request that this toplevel be activated on the given seat. + There is no guarantee the toplevel will be actually activated. + + + + + + + The different states that a toplevel can have. These have the same meaning + as the states with the same names defined in xdg-toplevel + + + + + + + + + + + This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 + is created and each time the toplevel state changes, either because of a + compositor action or because of a request in this protocol. + + + + + + + + This event is sent after all changes in the toplevel state have been + sent. + + This allows changes to the zwlr_foreign_toplevel_handle_v1 properties + to be seen as atomic, even if they happen via multiple events. + + + + + + Send a request to the toplevel to close itself. The compositor would + typically use a shell-specific method to carry out this request, for + example by sending the xdg_toplevel.close event. However, this gives + no guarantees the toplevel will actually be destroyed. If and when + this happens, the zwlr_foreign_toplevel_handle_v1.closed event will + be emitted. + + + + + + The rectangle of the surface specified in this request corresponds to + the place where the app using this protocol represents the given toplevel. + It can be used by the compositor as a hint for some operations, e.g + minimizing. The client is however not required to set this, in which + case the compositor is free to decide some default value. + + If the client specifies more than one rectangle, only the last one is + considered. + + The dimensions are given in surface-local coordinates. + Setting width=height=0 removes the already-set rectangle. + + + + + + + + + + + + + + + + This event means the toplevel has been destroyed. It is guaranteed there + won't be any more events for this zwlr_foreign_toplevel_handle_v1. The + toplevel itself becomes inert so any requests will be ignored except the + destroy request. + + + + + + Destroys the zwlr_foreign_toplevel_handle_v1 object. + + This request should be called either when the client does not want to + use the toplevel anymore or after the closed event to finalize the + destruction of the object. + + + + + + + + Requests that the toplevel be fullscreened on the given output. If the + fullscreen state and/or the outputs the toplevel is visible on actually + change, this will be indicated by the state and output_enter/leave + events. + + The output parameter is only a hint to the compositor. Also, if output + is NULL, the compositor should decide which output the toplevel will be + fullscreened on, if at all. + + + + + + + Requests that the toplevel be unfullscreened. If the fullscreen state + actually changes, this will be indicated by the state event. + + + + + + + + This event is emitted whenever the parent of the toplevel changes. + + No event is emitted when the parent handle is destroyed by the client. + + + + + diff --git a/panel/lxqtpanelapplication.cpp b/panel/lxqtpanelapplication.cpp index 59dd09eb0..dba4d3c9e 100644 --- a/panel/lxqtpanelapplication.cpp +++ b/panel/lxqtpanelapplication.cpp @@ -44,111 +44,112 @@ #include "backends/lxqtdummywmbackend.h" - -static inline QList detectDesktopEnvironment() +static inline QMap getBackendScoreMap( QString compositor ) { - const QByteArray xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP"); - if (!xdgCurrentDesktop.isEmpty()) + QStringList dirs; + dirs << QProcessEnvironment::systemEnvironment().value(QStringLiteral("LXQTPANEL_PLUGIN_PATH")).split(QStringLiteral(":")); + dirs << QStringLiteral(PLUGIN_DIR); + + QMap backendScoreMap; + + for(const QString& dir : std::as_const(dirs)) { - // 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; + QDir backendsDir(dir); + if ( QFile::exists( dir + QStringLiteral("/backend") ) ) { + backendsDir.cd(QLatin1String("backend")); } - } - // Classic fallbacks - if (!qEnvironmentVariableIsEmpty("KDE_FULL_SESSION")) - return {QByteArrayLiteral("KDE")}; - - // Fallback to checking $DESKTOP_SESSION (unreliable) - QByteArray desktopSession = qgetenv("DESKTOP_SESSION"); + const auto entryList = backendsDir.entryInfoList(QStringList() << QStringLiteral("*.so"), QDir::Files|QDir::System|QDir::Readable); + for(QFileInfo info: entryList) + { + QPluginLoader loader(info.absoluteFilePath()); + if(!loader.load()) + { + QString err = loader.errorString(); + qWarning() << "Backend error:" << err; + } - // This can be a path in /usr/share/xsessions - int slash = desktopSession.lastIndexOf('/'); - // try decoding just the basename - desktopSession = desktopSession.mid(slash + 1); + QObject *plugin = loader.instance(); + if(!plugin) + continue; - if (desktopSession == "kde" || desktopSession == "plasma") - return {QByteArrayLiteral("KDE")}; + ILXQtWMBackendLibrary *backend = qobject_cast(plugin); + if(backend) + { + backendScoreMap[ info.fileName() ] = backend->getBackendScore( compositor ); + } + loader.unload(); + } + } - return {}; + return backendScoreMap; } -QString findBestBackend() +static inline QString getBackendFilePath( QString name ) { - QStringList dirs; - - // 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; + // If we do not have a full library name, line lib_labwc_backend.so, + // then build a name based on default heuristic: libwmbackend_.so + if (!name.startsWith(QStringLiteral("lib")) || !name.endsWith(QStringLiteral(".so"))) + { + if ( !name.startsWith( QStringLiteral("libwmbackend_") ) ) + { + name = QString( QStringLiteral("libwmbackend_%1") ).arg( name ); + } + if ( !name.endsWith( QStringLiteral(".so") ) ) + { + name = QString( QStringLiteral("%1.so") ).arg( name ); + } + } + QStringList dirs; + dirs << QProcessEnvironment::systemEnvironment().value(QStringLiteral("LXQTPANEL_PLUGIN_PATH")).split(QStringLiteral(":")); dirs << QStringLiteral(PLUGIN_DIR); - QString lastBackendFile; - int lastBackendScore = 0; + QMap backendScoreMap; - QList desktops = detectDesktopEnvironment(); - for(const QByteArray& desktop : desktops) + for(const QString& dir : std::as_const(dirs)) { - QString key = QString::fromUtf8(desktop); + QDir backendsDir(dir); + if ( QFile::exists( dir + QStringLiteral("/backend") ) ) { + backendsDir.cd(QLatin1String("backend")); + } - for(const QString& dir : std::as_const(dirs)) + if ( backendsDir.exists( name ) ) { - QDir backendsDir(dir); + return backendsDir.absoluteFilePath( name ); + } + } - if ( QFile::exists( dir + QStringLiteral("/backend") ) ) - { - backendsDir.cd(QLatin1String("backend")); - } + return QString(); +} - backendsDir.setNameFilters({QLatin1String("libwmbackend_*.so")}); - 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; - } +static inline bool testBackend( QString backendName ) +{ + QString backendPath = getBackendFilePath( backendName ); - QObject *plugin = loader.instance(); - if(!plugin) - continue; - - ILXQtWMBackendLibrary *backend = qobject_cast(plugin); - if(backend) - { - int score = backend->getBackendScore(key); - if(score > lastBackendScore) - { - lastBackendFile = absPath; - lastBackendScore = score; - } - } - loader.unload(); - } - } + QPluginLoader loader(backendPath); + if(!loader.load()) + { + qWarning() << "Backend error:" << loader.errorString(); + return false; + } - // Double the score before going to next key - lastBackendScore *= 2; + QObject *plugin = loader.instance(); + if(!plugin) { + qWarning() << "Failed to create the plugin instance"; + return false; } - if(lastBackendScore == 0) - return QString(); // No available backend is good for this environment + ILXQtWMBackendLibrary *backend = qobject_cast(plugin); + bool okay = false; + if(backend) + { + okay = true; + } - return lastBackendFile; + loader.unload(); + + return okay; } LXQtPanelApplicationPrivate::LXQtPanelApplicationPrivate(LXQtPanelApplication *q) @@ -189,44 +190,85 @@ ILXQtPanel::Position LXQtPanelApplicationPrivate::computeNewPanelPosition(const void LXQtPanelApplicationPrivate::loadBackend() { - QPluginLoader loader; + /** + * 1. Get the XDG_CURRENT_DESKTOP. It's a colon separate list. + * 2. Get the preferredBackend. It's a comma separated list. + * 3. First attempt to match some value in XDG_CURRENT_DESKTOP with any value in preferredBackend. + * 4. If it matches, end of story. Else, we attempt to deduce the backend based on XDG_CURRENT_DESKTOP: + * a. X11 -> xcb + * b. kwin_wayland -> plasma + * c. wayfire -> wayfire + * d. wayland -> wlroots + * e. other -> dummy + */ + + // Get and split XDG_CURRENT_DESKTOP. + QStringList xdgCurrentDesktops = qEnvironmentVariable( "XDG_CURRENT_DESKTOP" ).split( QStringLiteral(":") ); + + // Get and split XDG_SESSION_TYPE. + QString xdgSessionType = qEnvironmentVariable( "XDG_SESSION_TYPE" ); + + // Get the preferred backends + QStringList preferredBackends = mSettings->value(QStringLiteral("preferred_backend")).toStringList(); + + // The preferred backend + QString preferredBackend; + + for( QString backend: preferredBackends ) { + QStringList parts = backend.split(QStringLiteral(":")); + if (( parts[0] == xdgCurrentDesktops[ 1 ] ) && testBackend(parts[1])) { + preferredBackend = parts[1]; + break; + } + } - // First try to load user preferred backend - QString preferredBackend = mSettings->value(QStringLiteral("preferred_backend")).toString(); - if(!preferredBackend.isEmpty()) - { - loader.setFileName(preferredBackend); - loader.load(); + /** No special considerations. Attempt auto-detection of the platform */ + if ( preferredBackend.isEmpty() ) { + qDebug() << "No user preferences available. Attempting auto-detection."; - QObject *plugin = loader.instance(); - ILXQtWMBackendLibrary *backend = qobject_cast(plugin); - if(backend) - { - mWMBackend = backend->instance(); + // It's XCB/X11 + if ( xdgSessionType == QStringLiteral("x11") ) { + preferredBackend = QStringLiteral("xcb"); } - else - { - // Plugin not valid - loader.unload(); + + // It's wayland + else { + QMap backendScoreMap = getBackendScoreMap( xdgCurrentDesktops[ 1 ] ); + + int bestScore = 0; + for( QString backend: backendScoreMap.keys() ) { + if ( backendScoreMap[ backend ] > bestScore ) { + bestScore = backendScoreMap[ backend ]; + // No need to call testBackend(). + // We can be sure the plugin can be loaded. + // Because we have a score. + preferredBackend = backend; + } + } } } - if(!mWMBackend) + if ( preferredBackend.isEmpty() && xdgCurrentDesktops.contains( QStringLiteral("wlroots") ) ) { - // If user prefferred is not valid, find best available backend - QString fileName = findBestBackend(); + qDebug() << "Specialized backend unavailable. Falling back to generic wlroots"; + preferredBackend = QStringLiteral("wlroots"); + } - if(!fileName.isEmpty()) - { - loader.setFileName(fileName); - loader.load(); + QPluginLoader loader; + // We now have the preferred backend. + // We have taken into consideration, the user's choice. + // In case it was unavailable, a default one has been chosen. + if(!preferredBackend.isEmpty()) + { + loader.setFileName(getBackendFilePath(preferredBackend)); + if (loader.load()) + { QObject *plugin = loader.instance(); ILXQtWMBackendLibrary *backend = qobject_cast(plugin); if(backend) { mWMBackend = backend->instance(); - preferredBackend = fileName; } else { @@ -234,6 +276,10 @@ void LXQtPanelApplicationPrivate::loadBackend() loader.unload(); } } + else + { + qWarning() << loader.errorString(); + } } if(mWMBackend) From 2afd630f4d03ef6e7448d4774027ef9340ae4fdc Mon Sep 17 00:00:00 2001 From: Marcus Britanicus Date: Thu, 22 Aug 2024 12:51:09 +0530 Subject: [PATCH 38/42] Fix autodetection on user choice failure From dac7ce3fa7b7dbd6ccba043bc4d2d2f454864389 Mon Sep 17 00:00:00 2001 From: Marcus Britanicus Date: Thu, 22 Aug 2024 14:43:08 +0530 Subject: [PATCH 39/42] wmbackend_kwin_wayland: Compare against kwin_wayland. --- .../wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp index 1880a2060..6e5eab1c8 100644 --- a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp +++ b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp @@ -773,7 +773,8 @@ int LXQtWMBackendKWinWaylandLibrary::getBackendScore(const QString &key) const return 0; // Detect KWin Plasma - if(key == QLatin1String("KDE") || key == QLatin1String("KWIN")) + // The key will be kwin_wayland, not anything else. + if(key == QLatin1String("kwin_wayland")) return 100; // It's not useful for other wayland compositors From c5da9bf6b0313159fdcd65f06570b92a6345bc6e Mon Sep 17 00:00:00 2001 From: Marcus Britanicus Date: Mon, 26 Aug 2024 16:41:32 +0530 Subject: [PATCH 40/42] Iterate through all parts of the XDG_CURRENT_DESKTOP. Add river to wlroots --- .../lxqtwmbackend_kwinwayland.cpp | 13 +++-- .../wayland/wlroots/lxqtwmbackend_wlr.cpp | 40 +++++++++++----- panel/lxqtpanelapplication.cpp | 47 ++++++++++++------- panel/resources/panel.conf | 3 ++ 4 files changed, 72 insertions(+), 31 deletions(-) diff --git a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp index 6e5eab1c8..0d39f4e17 100644 --- a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp +++ b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp @@ -772,10 +772,17 @@ int LXQtWMBackendKWinWaylandLibrary::getBackendScore(const QString &key) const if(!waylandApplication) return 0; - // Detect KWin Plasma - // The key will be kwin_wayland, not anything else. - if(key == QLatin1String("kwin_wayland")) + // Detect KWin Plasma (Wayland) + if(key == QLatin1String("KDE") || key == QLatin1String("KWIN")) + { + return 100; + } + + // kwin_wayland compositor, but not full-blown DE + else if (key == QLatin1String("kwin_wayland")) + { return 100; + } // It's not useful for other wayland compositors return 0; diff --git a/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp index a69520098..241efd2b8 100644 --- a/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp +++ b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp @@ -475,25 +475,41 @@ LXQtTaskbarWlrootsWindow *LXQtTaskbarWlrootsBackend::getWindow(WId windowId) con int LXQtWMBackendWlrootsLibrary::getBackendScore(const QString& key) const { - if (key == QStringLiteral("wlroots")) - return 50; + if (key == QStringLiteral("wlroots")) + { + return 50; + } - else if (key == QStringLiteral("wayfire")) - return 30; + else if (key == QStringLiteral("wayfire")) + { + return 30; + } - else if (key == QStringLiteral("sway")) - return 30; + else if (key == QStringLiteral("sway")) + { + return 30; + } - else if (key == QStringLiteral("hyprland")) - return 30; + else if (key == QStringLiteral("hyprland")) + { + return 30; + } - else if (key == QStringLiteral("labwc")) - return 30; + else if (key == QStringLiteral("labwc")) + { + return 30; + } - // Unsupported - return 0; + else if (key == QStringLiteral("river")) + { + return 30; + } + + // Unsupported + return 0; } + ILXQtAbstractWMInterface *LXQtWMBackendWlrootsLibrary::instance() const { return new LXQtTaskbarWlrootsBackend(nullptr); diff --git a/panel/lxqtpanelapplication.cpp b/panel/lxqtpanelapplication.cpp index dba4d3c9e..1970204ac 100644 --- a/panel/lxqtpanelapplication.cpp +++ b/panel/lxqtpanelapplication.cpp @@ -214,13 +214,24 @@ void LXQtPanelApplicationPrivate::loadBackend() // The preferred backend QString preferredBackend; - for( QString backend: preferredBackends ) { - QStringList parts = backend.split(QStringLiteral(":")); - if (( parts[0] == xdgCurrentDesktops[ 1 ] ) && testBackend(parts[1])) { - preferredBackend = parts[1]; - break; - } - } + for ( QString xdgCurrentDesktop: xdgCurrentDesktops ) + { + for ( QString backend: preferredBackends ) + { + QStringList parts = backend.split(QStringLiteral(":")); + // Invalid format + if (parts.count() != 2) + { + continue; + } + + if ((parts[0] == xdgCurrentDesktop) && testBackend(parts[1])) + { + preferredBackend = parts[1]; + break; + } + } + } /** No special considerations. Attempt auto-detection of the platform */ if ( preferredBackend.isEmpty() ) { @@ -233,16 +244,20 @@ void LXQtPanelApplicationPrivate::loadBackend() // It's wayland else { - QMap backendScoreMap = getBackendScoreMap( xdgCurrentDesktops[ 1 ] ); - int bestScore = 0; - for( QString backend: backendScoreMap.keys() ) { - if ( backendScoreMap[ backend ] > bestScore ) { - bestScore = backendScoreMap[ backend ]; - // No need to call testBackend(). - // We can be sure the plugin can be loaded. - // Because we have a score. - preferredBackend = backend; + for ( QString xdgCurrentDesktop: xdgCurrentDesktops ) + { + QMap backendScoreMap = getBackendScoreMap( xdgCurrentDesktop ); + for( QString backend: backendScoreMap.keys() ) + { + if ( backendScoreMap[ backend ] > bestScore ) + { + bestScore = backendScoreMap[ backend ]; + // No need to call testBackend(). + // We can be sure the plugin can be loaded. + // Because we have a score. + preferredBackend = backend; + } } } } diff --git a/panel/resources/panel.conf b/panel/resources/panel.conf index 2c0799cd4..fe2e9d990 100644 --- a/panel/resources/panel.conf +++ b/panel/resources/panel.conf @@ -1,5 +1,8 @@ panels=panel1 +[General] +preferred_backend=KDE:kwin_wayland,KWIN:kwin_wayland + [panel1] plugins=fancymenu,desktopswitch,quicklaunch,taskbar,statusnotifier,tray,mount,volume,worldclock,showdesktop position=Bottom From cd2eedcd90cfd749acb642c30710c763d3668e71 Mon Sep 17 00:00:00 2001 From: Marcus Britanicus Date: Tue, 27 Aug 2024 14:54:50 +0530 Subject: [PATCH 41/42] Rever changes to color-picker --- plugin-colorpicker/colorpicker.cpp | 156 +++-------------------------- plugin-colorpicker/colorpicker.h | 8 -- 2 files changed, 14 insertions(+), 150 deletions(-) diff --git a/plugin-colorpicker/colorpicker.cpp b/plugin-colorpicker/colorpicker.cpp index a862609b9..e6f541580 100644 --- a/plugin-colorpicker/colorpicker.cpp +++ b/plugin-colorpicker/colorpicker.cpp @@ -36,9 +36,6 @@ #include #include -#include -#include - //NOTE: Xlib.h defines Bool which conflicts with QJsonValue::Type enum #include #undef Bool @@ -80,33 +77,6 @@ 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) { @@ -140,8 +110,7 @@ ColorPickerWidget::ColorPickerWidget(QWidget *parent) : QWidget(parent) layout->addWidget(mColorButton); setLayout(layout); - connect(mPickerButton, &QToolButton::clicked, this, &ColorPickerWidget::startCapturingColor); - + connect(mPickerButton, &QToolButton::clicked, this, &ColorPickerWidget::captureMouse); connect(mColorButton, &QToolButton::clicked, this, [&]() { buildMenu(); @@ -195,134 +164,37 @@ void ColorPickerWidget::mouseReleaseEvent(QMouseEvent *event) qWarning() << "WAYLAND does not support grabbing windows"; } - setCapturedColor(col); - - mCapturing = false; - releaseMouse(); - - if (!mPickerButton->contentsRect().contains(mapFromGlobal(QCursor::pos()))) - { - QApplication::sendEvent(mPickerButton, new QEvent(QEvent::Leave)); - } -} - -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"))) - { - // 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 - ); - } - }); - } - else if (qGuiApp->nativeInterface()) - { - // On X11 grab mouse and let `mouseReleaseEvent()` retrieve color - captureMouse(); - } -} - -void ColorPickerWidget::setCapturedColor(const QColor &color) -{ - mColorButton->setColor(color); - paste(color.name()); + mColorButton->setColor(col); + paste(col.name()); - if (mColorsList.contains(color)) + if (mColorsList.contains(col)) { - mColorsList.move(mColorsList.indexOf(color), 0); + mColorsList.move(mColorsList.indexOf(col), 0); } else { - mColorsList.prepend(color); + mColorsList.prepend(col); } if (mColorsList.size() > 10) { mColorsList.removeLast(); } -} - -void ColorPickerWidget::captureMouse() -{ - grabMouse(Qt::CrossCursor); - mCapturing = true; -} - -struct XDGDesktopColor -{ - double r = 0; - double g = 0; - double b = 0; + mCapturing = false; + releaseMouse(); - QColor toQColor() const + if (!mPickerButton->contentsRect().contains(mapFromGlobal(QCursor::pos()))) { - constexpr auto rgbMax = 255; - return { static_cast(r * rgbMax), static_cast(g * rgbMax), - static_cast(b * rgbMax) }; + QApplication::sendEvent(mPickerButton, new QEvent(QEvent::Leave)); } -}; - -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({}); - } +void ColorPickerWidget::captureMouse() +{ + grabMouse(Qt::CrossCursor); + mCapturing = true; } diff --git a/plugin-colorpicker/colorpicker.h b/plugin-colorpicker/colorpicker.h index ecf2a3b41..919f42490 100644 --- a/plugin-colorpicker/colorpicker.h +++ b/plugin-colorpicker/colorpicker.h @@ -58,10 +58,7 @@ 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; @@ -94,13 +91,8 @@ 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 9ae8005cde25acaa2ed7ceac76e8db8cb955bfd5 Mon Sep 17 00:00:00 2001 From: Marcus Britanicus Date: Tue, 27 Aug 2024 14:59:17 +0530 Subject: [PATCH 42/42] Revert changes to color-picker, attempt 2 --- plugin-colorpicker/colorpicker.cpp | 156 +++-------------------------- plugin-colorpicker/colorpicker.h | 8 -- 2 files changed, 14 insertions(+), 150 deletions(-) diff --git a/plugin-colorpicker/colorpicker.cpp b/plugin-colorpicker/colorpicker.cpp index a862609b9..e6f541580 100644 --- a/plugin-colorpicker/colorpicker.cpp +++ b/plugin-colorpicker/colorpicker.cpp @@ -36,9 +36,6 @@ #include #include -#include -#include - //NOTE: Xlib.h defines Bool which conflicts with QJsonValue::Type enum #include #undef Bool @@ -80,33 +77,6 @@ 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) { @@ -140,8 +110,7 @@ ColorPickerWidget::ColorPickerWidget(QWidget *parent) : QWidget(parent) layout->addWidget(mColorButton); setLayout(layout); - connect(mPickerButton, &QToolButton::clicked, this, &ColorPickerWidget::startCapturingColor); - + connect(mPickerButton, &QToolButton::clicked, this, &ColorPickerWidget::captureMouse); connect(mColorButton, &QToolButton::clicked, this, [&]() { buildMenu(); @@ -195,134 +164,37 @@ void ColorPickerWidget::mouseReleaseEvent(QMouseEvent *event) qWarning() << "WAYLAND does not support grabbing windows"; } - setCapturedColor(col); - - mCapturing = false; - releaseMouse(); - - if (!mPickerButton->contentsRect().contains(mapFromGlobal(QCursor::pos()))) - { - QApplication::sendEvent(mPickerButton, new QEvent(QEvent::Leave)); - } -} - -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"))) - { - // 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 - ); - } - }); - } - else if (qGuiApp->nativeInterface()) - { - // On X11 grab mouse and let `mouseReleaseEvent()` retrieve color - captureMouse(); - } -} - -void ColorPickerWidget::setCapturedColor(const QColor &color) -{ - mColorButton->setColor(color); - paste(color.name()); + mColorButton->setColor(col); + paste(col.name()); - if (mColorsList.contains(color)) + if (mColorsList.contains(col)) { - mColorsList.move(mColorsList.indexOf(color), 0); + mColorsList.move(mColorsList.indexOf(col), 0); } else { - mColorsList.prepend(color); + mColorsList.prepend(col); } if (mColorsList.size() > 10) { mColorsList.removeLast(); } -} - -void ColorPickerWidget::captureMouse() -{ - grabMouse(Qt::CrossCursor); - mCapturing = true; -} - -struct XDGDesktopColor -{ - double r = 0; - double g = 0; - double b = 0; + mCapturing = false; + releaseMouse(); - QColor toQColor() const + if (!mPickerButton->contentsRect().contains(mapFromGlobal(QCursor::pos()))) { - constexpr auto rgbMax = 255; - return { static_cast(r * rgbMax), static_cast(g * rgbMax), - static_cast(b * rgbMax) }; + QApplication::sendEvent(mPickerButton, new QEvent(QEvent::Leave)); } -}; - -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({}); - } +void ColorPickerWidget::captureMouse() +{ + grabMouse(Qt::CrossCursor); + mCapturing = true; } diff --git a/plugin-colorpicker/colorpicker.h b/plugin-colorpicker/colorpicker.h index ecf2a3b41..919f42490 100644 --- a/plugin-colorpicker/colorpicker.h +++ b/plugin-colorpicker/colorpicker.h @@ -58,10 +58,7 @@ 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; @@ -94,13 +91,8 @@ 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