From 5def5ac7d8d624867290481aa51c2c0ecb5b5c03 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Thu, 6 Jun 2024 18:08:13 +0200 Subject: [PATCH] Refactor Window Manager interaction (#2041) * TaskBar: forward declare * TaskBar: new ILXQtTaskbarAbstractBackend This is an abstract interface to operate windows and workspaces * ILXQtTaskbarAbstractBackend: add supportsAction() method - Move WindowProperty enum to lxqttaskbartypes.h * TaskBar: add X11 backend * TaskBar: new LXQtTaskBarProxyModel This model will manage the tasks shown * TaskBar: use backend in LXQtTaskBar * TaskBar: initial use of backend in LXQtTaskButton * TaskBar: use backend for urgency hint * TaskBar: use backend to set window layer * TaskBar: use backend to close and raise window Also use it to get window icon * TaskBar: use backend to roll up (shade) windows * TaskBar: use backend to minimize window * TaskBar: use backend to de-maximize window * TaskBar: use backend to get window state * TaskBar: use backend to set window state * TaskBar: port workspace usage to backend * TaskBar: remove X11 specific includes in lxqttaskbutton.cpp * TaskBar: remove X11 code from LXQtTaskBar and LXQtTaskGroup * TaskBar: LXQtTaskButton remove X11 specific code * TaskBar: set context menu parent * LXQtPanel: rework calculatePopupWindowPos() - Don't rely on global screen coordinates - This will be needed for future Wayland port, Where we don't have global screen coordinates - Keep compatible behavior on X11 * LXQtPanel: avoid QCursor::pos() usage * ILXQtTaskbarAbstractBackend: new Geometry window property This new window propery flag is needed to notify geometry changes * Move ILXQtTaskbarAbstractBackend to panel directory - It is now a global instance * ILXQtTaskbarAbstractBackend: new isAreaOverlapped() method * LXQtPanel: use less KX11Extras and more ILXQtTaskbarAbstractBackend * ILXQtTaskbarAbstractBackend: add dummy implementation This will be used to avoid crashing panel in case no backend could be created. A warning message will be printed in this case. * LXQtMainMenu: indent header include * ILXQtTaskbarAbstractBackend: new showDesktop() function * ShowDesktop: use ILXQtTaskbarAbstractBackend * DesktopSwitch: port to ILXQtTaskbarAbstractBackend - Clarify desktop index range * LXQtTaskbarConfiguration: port to ILXQtTaskBarAbstractBackend * DesktopSwitchConfiguration: port to ILXQtTaskBarAbstractBackend TODO TODO: this will disable changing desktop names * TaskBar: consider initial windows * DesktopSwitch: remove unused isWindowHighlightable() * DesktopSwitch: show unsupported widget if backend does not have switch capability * ILXQtTaskbarAbstractBackend: add setDesktopLayout() function - Use it in DesktopSwitch::settingsChanged() * LXQtTaskbarX11Backend: fix re-add window after property changes * LXQtTaskBarDummyBackend: fix compilation * LXQtTaskBarPlugin: fix headers path * Revert "LXQtPanel: rework calculatePopupWindowPos()" This reverts commit b0e54b87c7a9800cf9786b97916b48e9eb0c4c23. * Removed `mDesktops` after rebasing * Reverted X11 codes of `DesktopSwitchConfiguration::loadDesktopsNames` --------- Co-authored-by: Tsu Jan --- panel/CMakeLists.txt | 17 + panel/backends/CMakeLists.txt | 1 + .../backends/ilxqttaskbarabstractbackend.cpp | 25 + panel/backends/ilxqttaskbarabstractbackend.h | 99 +++ panel/backends/lxqttaskbardummybackend.cpp | 171 +++++ panel/backends/lxqttaskbardummybackend.h | 88 +++ panel/backends/lxqttaskbartypes.h | 56 ++ panel/backends/xcb/CMakeLists.txt | 1 + panel/backends/xcb/lxqttaskbarbackend_x11.cpp | 660 ++++++++++++++++++ panel/backends/xcb/lxqttaskbarbackend_x11.h | 89 +++ panel/lxqtpanel.cpp | 89 +-- panel/lxqtpanel.h | 3 +- panel/lxqtpanelapplication.cpp | 37 +- panel/lxqtpanelapplication.h | 4 + panel/lxqtpanelapplication_p.h | 3 + panel/plugin.cpp | 4 +- plugin-desktopswitch/desktopswitch.cpp | 148 ++-- plugin-desktopswitch/desktopswitch.h | 35 +- .../desktopswitchconfiguration.cpp | 32 +- plugin-mainmenu/lxqtmainmenu.cpp | 2 +- plugin-showdesktop/showdesktop.cpp | 9 +- plugin-taskbar/CMakeLists.txt | 4 + plugin-taskbar/lxqttaskbar.cpp | 129 +--- plugin-taskbar/lxqttaskbar.h | 33 +- plugin-taskbar/lxqttaskbarconfiguration.cpp | 11 +- plugin-taskbar/lxqttaskbarconfiguration.h | 4 +- plugin-taskbar/lxqttaskbarplugin.cpp | 10 +- plugin-taskbar/lxqttaskbarplugin.h | 8 +- plugin-taskbar/lxqttaskbarproxymodel.cpp | 257 +++++++ plugin-taskbar/lxqttaskbarproxymodel.h | 100 +++ plugin-taskbar/lxqttaskbutton.cpp | 365 +++------- plugin-taskbar/lxqttaskbutton.h | 9 +- plugin-taskbar/lxqttaskgroup.cpp | 83 +-- plugin-taskbar/lxqttaskgroup.h | 9 +- 34 files changed, 1961 insertions(+), 634 deletions(-) create mode 100644 panel/backends/CMakeLists.txt create mode 100644 panel/backends/ilxqttaskbarabstractbackend.cpp create mode 100644 panel/backends/ilxqttaskbarabstractbackend.h create mode 100644 panel/backends/lxqttaskbardummybackend.cpp create mode 100644 panel/backends/lxqttaskbardummybackend.h create mode 100644 panel/backends/lxqttaskbartypes.h create mode 100644 panel/backends/xcb/CMakeLists.txt create mode 100644 panel/backends/xcb/lxqttaskbarbackend_x11.cpp create mode 100644 panel/backends/xcb/lxqttaskbarbackend_x11.h create mode 100644 plugin-taskbar/lxqttaskbarproxymodel.cpp create mode 100644 plugin-taskbar/lxqttaskbarproxymodel.h diff --git a/panel/CMakeLists.txt b/panel/CMakeLists.txt index fb5651f8b..074c62af3 100644 --- a/panel/CMakeLists.txt +++ b/panel/CMakeLists.txt @@ -1,5 +1,8 @@ set(PROJECT lxqt-panel) +# TODO +add_subdirectory(backends) + set(PRIV_HEADERS panelpluginsmodel.h windownotifier.h @@ -18,6 +21,12 @@ 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. @@ -26,6 +35,9 @@ set(PUB_HEADERS pluginsettings.h ilxqtpanelplugin.h ilxqtpanel.h + + backends/ilxqttaskbarabstractbackend.h + backends/lxqttaskbartypes.h ) set(SOURCES @@ -45,6 +57,11 @@ set(SOURCES config/configstyling.cpp config/configpluginswidget.cpp config/addplugindialog.cpp + + backends/ilxqttaskbarabstractbackend.cpp + + backends/lxqttaskbardummybackend.cpp + backends/xcb/lxqttaskbarbackend_x11.cpp ) set(UI diff --git a/panel/backends/CMakeLists.txt b/panel/backends/CMakeLists.txt new file mode 100644 index 000000000..8f34a3c67 --- /dev/null +++ b/panel/backends/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(xcb) diff --git a/panel/backends/ilxqttaskbarabstractbackend.cpp b/panel/backends/ilxqttaskbarabstractbackend.cpp new file mode 100644 index 000000000..137728263 --- /dev/null +++ b/panel/backends/ilxqttaskbarabstractbackend.cpp @@ -0,0 +1,25 @@ +#include "../panel/backends/ilxqttaskbarabstractbackend.h" + + +ILXQtTaskbarAbstractBackend::ILXQtTaskbarAbstractBackend(QObject *parent) + : QObject(parent) +{ + +} + +void ILXQtTaskbarAbstractBackend::moveApplicationToPrevNextDesktop(WId windowId, bool next) +{ + int count = getWorkspacesCount(); + if (count <= 1) + return; + + int targetWorkspace = getWindowWorkspace(windowId) + (next ? 1 : -1); + + // Wrap around + if (targetWorkspace > count) + targetWorkspace = 1; //Ids are 1-based + else if (targetWorkspace < 1) + targetWorkspace = count; + + setWindowOnWorkspace(windowId, targetWorkspace); +} diff --git a/panel/backends/ilxqttaskbarabstractbackend.h b/panel/backends/ilxqttaskbarabstractbackend.h new file mode 100644 index 000000000..44840671a --- /dev/null +++ b/panel/backends/ilxqttaskbarabstractbackend.h @@ -0,0 +1,99 @@ +#ifndef ILXQTTASKBARABSTRACTBACKEND_H +#define ILXQTTASKBARABSTRACTBACKEND_H + +#include + +#include "lxqttaskbartypes.h" + +class QIcon; +class QScreen; + +class ILXQtTaskbarAbstractBackend : public QObject +{ + Q_OBJECT + +public: + explicit ILXQtTaskbarAbstractBackend(QObject *parent = nullptr); + + // Backend + virtual bool supportsAction(WId windowId, LXQtTaskBarBackendAction action) const = 0; + + // Windows + virtual bool reloadWindows() = 0; + + virtual QVector getCurrentWindows() const = 0; + + virtual QString getWindowTitle(WId windowId) const = 0; + + virtual bool applicationDemandsAttention(WId windowId) const = 0; + + virtual QIcon getApplicationIcon(WId windowId, int fallbackDevicePixels) const = 0; + + virtual QString getWindowClass(WId windowId) const = 0; + + virtual LXQtTaskBarWindowLayer getWindowLayer(WId windowId) const = 0; + virtual bool setWindowLayer(WId windowId, LXQtTaskBarWindowLayer layer) = 0; + + virtual LXQtTaskBarWindowState getWindowState(WId windowId) const = 0; + virtual bool setWindowState(WId windowId, LXQtTaskBarWindowState state, bool set = true) = 0; + + virtual bool isWindowActive(WId windowId) const = 0; + virtual bool raiseWindow(WId windowId, bool onCurrentWorkSpace) = 0; + + virtual bool closeWindow(WId windowId) = 0; + + virtual WId getActiveWindow() const = 0; + + // Workspaces + // NOTE: indexes are 1-based, 0 means "Show on All desktops" + virtual int getWorkspacesCount() const = 0; + virtual QString getWorkspaceName(int idx) const = 0; + + virtual int getCurrentWorkspace() const = 0; + virtual bool setCurrentWorkspace(int idx) = 0; + + virtual int getWindowWorkspace(WId windowId) const = 0; + virtual bool setWindowOnWorkspace(WId windowId, int idx) = 0; + + virtual void moveApplicationToPrevNextDesktop(WId windowId, bool next); // Default implementation + virtual void moveApplicationToPrevNextMonitor(WId windowId, bool next, bool raiseOnCurrentDesktop) = 0; + + virtual bool isWindowOnScreen(QScreen *screen, WId windowId) const = 0; + + virtual bool setDesktopLayout(Qt::Orientation orientation, int rows, int columns, bool rightToLeft) = 0; + + // X11 Specific + virtual void moveApplication(WId windowId) = 0; + virtual void resizeApplication(WId windowId) = 0; + + virtual void refreshIconGeometry(WId windowId, const QRect &geom) = 0; + + // Panel internal + virtual bool isAreaOverlapped(const QRect& area) const = 0; + + // Show Destop TODO: split in multiple interfeces, this is becoming big + // NOTE: KWindowSystem already has these functions + // However on Wayland they are only compatible with KWin + // because internally it uses org_kde_plasma_window_management protocol + // We make this virtual so it can be implemented also for other compositors + virtual bool isShowingDesktop() const = 0; + virtual bool showDesktop(bool value) = 0; + +signals: + void reloaded(); + + // Windows + void windowAdded(WId windowId); + void windowRemoved(WId windowId); + void windowPropertyChanged(WId windowId, int prop); + + // Workspaces + void workspacesCountChanged(); + void workspaceNameChanged(int idx); + void currentWorkspaceChanged(int idx); + + // TODO: needed? + void activeWindowChanged(WId windowId); +}; + +#endif // ILXQTTASKBARABSTRACTBACKEND_H diff --git a/panel/backends/lxqttaskbardummybackend.cpp b/panel/backends/lxqttaskbardummybackend.cpp new file mode 100644 index 000000000..15e7e1149 --- /dev/null +++ b/panel/backends/lxqttaskbardummybackend.cpp @@ -0,0 +1,171 @@ +#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/lxqttaskbardummybackend.h b/panel/backends/lxqttaskbardummybackend.h new file mode 100644 index 000000000..15506838f --- /dev/null +++ b/panel/backends/lxqttaskbardummybackend.h @@ -0,0 +1,88 @@ +#ifndef LXQTTASKBARDUMMYBACKEND_H +#define LXQTTASKBARDUMMYBACKEND_H + +#include "ilxqttaskbarabstractbackend.h" + +class LXQtTaskBarDummyBackend : public ILXQtTaskbarAbstractBackend +{ + Q_OBJECT + +public: + explicit LXQtTaskBarDummyBackend(QObject *parent = nullptr); + + // Backend + bool supportsAction(WId windowId, LXQtTaskBarBackendAction action) const override; + + // Windows + bool reloadWindows() override; + + QVector getCurrentWindows() const override; + + QString getWindowTitle(WId windowId) const override; + + bool applicationDemandsAttention(WId windowId) const override; + + QIcon getApplicationIcon(WId windowId, int fallbackDevicePixels) const override; + + QString getWindowClass(WId windowId) const override; + + LXQtTaskBarWindowLayer getWindowLayer(WId windowId) const override; + bool setWindowLayer(WId windowId, LXQtTaskBarWindowLayer layer) override; + + LXQtTaskBarWindowState getWindowState(WId windowId) const override; + bool setWindowState(WId windowId, LXQtTaskBarWindowState state, bool set = true) override; + + bool isWindowActive(WId windowId) const override; + bool raiseWindow(WId windowId, bool onCurrentWorkSpace) override; + + bool closeWindow(WId windowId) override; + + WId getActiveWindow() const override; + + // Workspaces + int getWorkspacesCount() const override; + QString getWorkspaceName(int idx) const override; + + int getCurrentWorkspace() const override; + bool setCurrentWorkspace(int idx) override; + + int getWindowWorkspace(WId windowId) const override; + bool setWindowOnWorkspace(WId windowId, int idx) override; + + void moveApplicationToPrevNextMonitor(WId windowId, bool next, bool raiseOnCurrentDesktop) override; + + bool isWindowOnScreen(QScreen *screen, WId windowId) const override; + + virtual bool setDesktopLayout(Qt::Orientation orientation, int rows, int columns, bool rightToLeft) override; + + // X11 Specific + void moveApplication(WId windowId) override; + void resizeApplication(WId windowId) override; + + void refreshIconGeometry(WId windowId, const QRect &geom) override; + + // Panel internal + bool isAreaOverlapped(const QRect& area) const override; + + // Show Destop + bool isShowingDesktop() const override; + bool showDesktop(bool value) override; + +signals: + void reloaded(); + + // Windows + void windowAdded(WId windowId); + void windowRemoved(WId windowId); + void windowPropertyChanged(WId windowId, int prop); + + // Workspaces + void workspacesCountChanged(); + void workspaceNameChanged(int idx); + void currentWorkspaceChanged(int idx); + + // TODO: needed? + void activeWindowChanged(WId windowId); +}; + +#endif // LXQTTASKBARDUMMYBACKEND_H diff --git a/panel/backends/lxqttaskbartypes.h b/panel/backends/lxqttaskbartypes.h new file mode 100644 index 000000000..656591fbc --- /dev/null +++ b/panel/backends/lxqttaskbartypes.h @@ -0,0 +1,56 @@ +#ifndef LXQTTASKBARTYPES_H +#define LXQTTASKBARTYPES_H + +#include + +typedef quintptr WId; + +enum class LXQtTaskBarBackendAction +{ + Move = 0, + Resize, + Maximize, + MaximizeVertically, + MaximizeHorizontally, + Minimize, + RollUp, + FullScreen, + DesktopSwitch +}; + +enum class LXQtTaskBarWindowProperty +{ + Title = 0, + Icon, + State, + Geometry, + Urgency, + WindowClass, + Workspace +}; + +enum class LXQtTaskBarWindowState +{ + Hidden = 0, + FullScreen, + Minimized, + Maximized, + MaximizedVertically, + MaximizedHorizontally, + Normal, + RolledUp //Shaded +}; + +enum class LXQtTaskBarWindowLayer +{ + KeepBelow = 0, + Normal, + KeepAbove +}; + +enum class LXQtTaskBarWorkspace +{ + ShowOnAll = -1 +}; + +#endif // LXQTTASKBARTYPES_H diff --git a/panel/backends/xcb/CMakeLists.txt b/panel/backends/xcb/CMakeLists.txt new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/panel/backends/xcb/CMakeLists.txt @@ -0,0 +1 @@ + diff --git a/panel/backends/xcb/lxqttaskbarbackend_x11.cpp b/panel/backends/xcb/lxqttaskbarbackend_x11.cpp new file mode 100644 index 000000000..fe0452776 --- /dev/null +++ b/panel/backends/xcb/lxqttaskbarbackend_x11.cpp @@ -0,0 +1,660 @@ +#include "lxqttaskbarbackend_x11.h" + +#include +#include +#include + +// Necessary for closeApplication() +#include + +#include +#include + +#include + +//NOTE: Xlib.h defines Bool which conflicts with QJsonValue::Type enum +#include +#undef Bool + +LXQtTaskbarX11Backend::LXQtTaskbarX11Backend(QObject *parent) + : ILXQtTaskbarAbstractBackend(parent) +{ + auto *x11Application = qGuiApp->nativeInterface(); + Q_ASSERT_X(x11Application, "LXQtTaskbarX11Backend", "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::numberOfDesktopsChanged, this, &ILXQtTaskbarAbstractBackend::workspacesCountChanged); + connect(KX11Extras::self(), &KX11Extras::currentDesktopChanged, this, &ILXQtTaskbarAbstractBackend::currentWorkspaceChanged); + + connect(KX11Extras::self(), &KX11Extras::activeWindowChanged, this, &ILXQtTaskbarAbstractBackend::activeWindowChanged); +} + +/************************************************ + * Model slots + ************************************************/ +void LXQtTaskbarX11Backend::onWindowChanged(WId windowId, NET::Properties prop, NET::Properties2 prop2) +{ + if(!m_windows.contains(windowId)) + { + // If already known window changes its property in a way + // it's now acceptable, add it again to taskbar + if(acceptWindow(windowId)) + onWindowAdded(windowId); + return; + } + + if(!acceptWindow(windowId)) + { + // If already known window changes its property in a way + // it's not anymore accepted, remove it from taskbar + onWindowRemoved(windowId); + return; + } + + if (prop.testFlag(NET::WMGeometry)) + { + emit windowPropertyChanged(windowId, int(LXQtTaskBarWindowProperty::Geometry)); + } + + if (prop2.testFlag(NET::WM2WindowClass)) + { + emit windowPropertyChanged(windowId, int(LXQtTaskBarWindowProperty::WindowClass)); + } + + // window changed virtual desktop + if (prop.testFlag(NET::WMDesktop) || prop.testFlag(NET::WMGeometry)) + { + emit windowPropertyChanged(windowId, int(LXQtTaskBarWindowProperty::Workspace)); + } + + if (prop.testFlag(NET::WMVisibleName) || prop.testFlag(NET::WMName)) + emit windowPropertyChanged(windowId, int(LXQtTaskBarWindowProperty::Title)); + + // XXX: we are setting window icon geometry -> don't need to handle NET::WMIconGeometry + // Icon of the button can be based on windowClass + if (prop.testFlag(NET::WMIcon) || prop2.testFlag(NET::WM2WindowClass)) + emit windowPropertyChanged(windowId, int(LXQtTaskBarWindowProperty::Icon)); + + bool update_urgency = false; + if (prop2.testFlag(NET::WM2Urgency)) + { + update_urgency = true; + } + + if (prop.testFlag(NET::WMState)) + { + update_urgency = true; + + emit windowPropertyChanged(windowId, int(LXQtTaskBarWindowProperty::State)); + } + + if (update_urgency) + emit windowPropertyChanged(windowId, int(LXQtTaskBarWindowProperty::Urgency)); +} + +void LXQtTaskbarX11Backend::onWindowAdded(WId windowId) +{ + if(m_windows.contains(windowId)) + return; + + if (!acceptWindow(windowId)) + return; + + addWindow_internal(windowId); +} + +void LXQtTaskbarX11Backend::onWindowRemoved(WId windowId) +{ + const int row = m_windows.indexOf(windowId); + if(row == -1) + return; + + m_windows.removeAt(row); + + emit windowRemoved(windowId); +} + +/************************************************ + * Model private functions + ************************************************/ +bool LXQtTaskbarX11Backend::acceptWindow(WId windowId) const +{ + QFlags ignoreList; + ignoreList |= NET::DesktopMask; + ignoreList |= NET::DockMask; + ignoreList |= NET::SplashMask; + ignoreList |= NET::ToolbarMask; + ignoreList |= NET::MenuMask; + ignoreList |= NET::PopupMenuMask; + ignoreList |= NET::NotificationMask; + + KWindowInfo info(windowId, NET::WMWindowType | NET::WMState, NET::WM2TransientFor); + if (!info.valid()) + return false; + + if (NET::typeMatchesMask(info.windowType(NET::AllTypesMask), ignoreList)) + return false; + + if (info.state() & NET::SkipTaskbar) + return false; + + // WM_TRANSIENT_FOR hint not set - normal window + WId transFor = info.transientFor(); + + WId appRootWindow = XDefaultRootWindow(m_X11Display); + + if (transFor == 0 || transFor == windowId || transFor == appRootWindow) + return true; + + info = KWindowInfo(transFor, NET::WMWindowType); + + QFlags normalFlag; + normalFlag |= NET::NormalMask; + normalFlag |= NET::DialogMask; + normalFlag |= NET::UtilityMask; + + return !NET::typeMatchesMask(info.windowType(NET::AllTypesMask), normalFlag); +} + +void LXQtTaskbarX11Backend::addWindow_internal(WId windowId, bool emitAdded) +{ + m_windows.append(windowId); + if(emitAdded) + emit windowAdded(windowId); +} + + +/************************************************ + * Windows function + ************************************************/ +bool LXQtTaskbarX11Backend::supportsAction(WId windowId, LXQtTaskBarBackendAction action) const +{ + NET::Action x11Action; + + switch (action) + { + case LXQtTaskBarBackendAction::Move: + x11Action = NET::ActionMove; + break; + + case LXQtTaskBarBackendAction::Resize: + x11Action = NET::ActionResize; + break; + + case LXQtTaskBarBackendAction::Maximize: + x11Action = NET::ActionMax; + break; + + case LXQtTaskBarBackendAction::MaximizeVertically: + x11Action = NET::ActionMaxVert; + break; + + case LXQtTaskBarBackendAction::MaximizeHorizontally: + x11Action = NET::ActionMaxHoriz; + break; + + case LXQtTaskBarBackendAction::Minimize: + x11Action = NET::ActionMinimize; + break; + + case LXQtTaskBarBackendAction::RollUp: + x11Action = NET::ActionShade; + break; + + case LXQtTaskBarBackendAction::FullScreen: + x11Action = NET::ActionFullScreen; + break; + + case LXQtTaskBarBackendAction::DesktopSwitch: + return true; + + default: + return false; + } + + KWindowInfo info(windowId, NET::Properties(), NET::WM2AllowedActions); + return info.actionSupported(x11Action); +} + +bool LXQtTaskbarX11Backend::reloadWindows() +{ + QVector oldWindows; + qSwap(oldWindows, m_windows); + + // Just add new windows to groups, deleting is up to the groups + const auto x11windows = KX11Extras::stackingOrder(); + for (auto const windowId: x11windows) + { + if (acceptWindow(windowId)) + { + bool emitAdded = !oldWindows.contains(windowId); + addWindow_internal(windowId, emitAdded); + } + } + + //emulate windowRemoved if known window not reported by KWindowSystem + for (auto i = oldWindows.begin(), i_e = oldWindows.end(); i != i_e; i++) + { + WId windowId = *i; + if (!m_windows.contains(windowId)) + { + //TODO: more efficient method? + emit windowRemoved(windowId); + } + } + + //TODO: refreshPlaceholderVisibility() + emit reloaded(); + + return true; +} + +QVector LXQtTaskbarX11Backend::getCurrentWindows() const +{ + return m_windows; +} + +QString LXQtTaskbarX11Backend::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 +{ + 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 +{ + return KX11Extras::icon(windowId, devicePixels, devicePixels); +} + +QString LXQtTaskbarX11Backend::getWindowClass(WId windowId) const +{ + KWindowInfo info(windowId, NET::Properties(), NET::WM2WindowClass); + return QString::fromUtf8(info.windowClassClass()); +} + +LXQtTaskBarWindowLayer LXQtTaskbarX11Backend::getWindowLayer(WId windowId) const +{ + NET::States state = KWindowInfo(windowId, NET::WMState).state(); + if(state.testFlag(NET::KeepAbove)) + return LXQtTaskBarWindowLayer::KeepAbove; + else if(state.testFlag(NET::KeepBelow)) + return LXQtTaskBarWindowLayer::KeepBelow; + return LXQtTaskBarWindowLayer::Normal; +} + +bool LXQtTaskbarX11Backend::setWindowLayer(WId windowId, LXQtTaskBarWindowLayer layer) +{ + switch(layer) + { + case LXQtTaskBarWindowLayer::KeepAbove: + KX11Extras::clearState(windowId, NET::KeepBelow); + KX11Extras::setState(windowId, NET::KeepAbove); + break; + + case LXQtTaskBarWindowLayer::KeepBelow: + KX11Extras::clearState(windowId, NET::KeepAbove); + KX11Extras::setState(windowId, NET::KeepBelow); + break; + + default: + KX11Extras::clearState(windowId, NET::KeepBelow); + KX11Extras::clearState(windowId, NET::KeepAbove); + break; + } + + return true; +} + +LXQtTaskBarWindowState LXQtTaskbarX11Backend::getWindowState(WId windowId) const +{ + KWindowInfo info(windowId,NET::WMState | NET::XAWMState); + if(info.isMinimized()) + return LXQtTaskBarWindowState::Minimized; + + NET::States state = info.state(); + if(state.testFlag(NET::Hidden)) + return LXQtTaskBarWindowState::Hidden; + if(state.testFlag(NET::Max)) + return LXQtTaskBarWindowState::Maximized; + if(state.testFlag(NET::MaxHoriz)) + return LXQtTaskBarWindowState::MaximizedHorizontally; + if(state.testFlag(NET::MaxVert)) + return LXQtTaskBarWindowState::MaximizedVertically; + if(state.testFlag(NET::Shaded)) + return LXQtTaskBarWindowState::RolledUp; + if(state.testFlag(NET::FullScreen)) + return LXQtTaskBarWindowState::FullScreen; + + return LXQtTaskBarWindowState::Normal; +} + +bool LXQtTaskbarX11Backend::setWindowState(WId windowId, LXQtTaskBarWindowState state, bool set) +{ + // NOTE: window activation is left to the caller + + NET::State x11State; + + switch (state) + { + case LXQtTaskBarWindowState::Minimized: + { + if(set) + KX11Extras::minimizeWindow(windowId); + else + KX11Extras::unminimizeWindow(windowId); + return true; + } + case LXQtTaskBarWindowState::Maximized: + { + x11State = NET::Max; + break; + } + case LXQtTaskBarWindowState::MaximizedVertically: + { + x11State = NET::MaxVert; + break; + } + case LXQtTaskBarWindowState::MaximizedHorizontally: + { + x11State = NET::MaxHoriz; + break; + } + case LXQtTaskBarWindowState::Normal: + { + x11State = NET::Max; //TODO: correct? + break; + } + case LXQtTaskBarWindowState::RolledUp: + { + x11State = NET::Shaded; + break; + } + default: + return false; + } + + if(set) + KX11Extras::setState(windowId, x11State); + else + KX11Extras::clearState(windowId, x11State); + + return true; +} + +bool LXQtTaskbarX11Backend::isWindowActive(WId windowId) const +{ + return KX11Extras::activeWindow() == windowId; +} + +bool LXQtTaskbarX11Backend::raiseWindow(WId windowId, bool onCurrentWorkSpace) +{ + if (onCurrentWorkSpace && getWindowState(windowId) == LXQtTaskBarWindowState::Minimized) + { + setWindowOnWorkspace(windowId, getCurrentWorkspace()); + } + else + { + setCurrentWorkspace(getWindowWorkspace(windowId)); + } + + // bypass focus stealing prevention + KX11Extras::forceActiveWindow(windowId); + + // Clear urgency flag + emit windowPropertyChanged(windowId, int(LXQtTaskBarWindowProperty::Urgency)); + + return true; +} + +bool LXQtTaskbarX11Backend::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 +{ + return KX11Extras::activeWindow(); +} + + +/************************************************ + * Workspaces + ************************************************/ +int LXQtTaskbarX11Backend::getWorkspacesCount() const +{ + return KX11Extras::numberOfDesktops(); +} + +QString LXQtTaskbarX11Backend::getWorkspaceName(int idx) const +{ + return KX11Extras::desktopName(idx); +} + +int LXQtTaskbarX11Backend::getCurrentWorkspace() const +{ + return KX11Extras::currentDesktop(); +} + +bool LXQtTaskbarX11Backend::setCurrentWorkspace(int idx) +{ + if(KX11Extras::currentDesktop() == idx) + return true; + + KX11Extras::setCurrentDesktop(idx); + return true; +} + +int LXQtTaskbarX11Backend::getWindowWorkspace(WId windowId) const +{ + KWindowInfo info(windowId, NET::WMDesktop); + return info.desktop(); +} + +bool LXQtTaskbarX11Backend::setWindowOnWorkspace(WId windowId, int idx) +{ + KX11Extras::setOnDesktop(windowId, idx); + return true; +} + +void LXQtTaskbarX11Backend::moveApplicationToPrevNextMonitor(WId windowId, bool next, bool raiseOnCurrentDesktop) +{ + KWindowInfo info(windowId, NET::WMDesktop); + if (!info.isOnCurrentDesktop()) + KX11Extras::setCurrentDesktop(info.desktop()); + + if (getWindowState(windowId) == LXQtTaskBarWindowState::Minimized) + KX11Extras::unminimizeWindow(windowId); + + KX11Extras::forceActiveWindow(windowId); + + const QRect& windowGeometry = KWindowInfo(windowId, NET::WMFrameExtents).frameGeometry(); + QList screens = QGuiApplication::screens(); + if (screens.size() > 1) + { + for (int i = 0; i < screens.size(); ++i) + { + QRect screenGeometry = screens[i]->geometry(); + if (screenGeometry.intersects(windowGeometry)) + { + int targetScreen = i + (next ? 1 : -1); + if (targetScreen < 0) + targetScreen += screens.size(); + else if (targetScreen >= screens.size()) + targetScreen -= screens.size(); + + QRect targetScreenGeometry = screens[targetScreen]->geometry(); + int X = windowGeometry.x() - screenGeometry.x() + targetScreenGeometry.x(); + int Y = windowGeometry.y() - screenGeometry.y() + targetScreenGeometry.y(); + NET::States state = KWindowInfo(windowId, NET::WMState).state(); + + // NW geometry | y/x | from panel + const int flags = 1 | (0b011 << 8) | (0b010 << 12); + KX11Extras::clearState(windowId, NET::MaxHoriz | NET::MaxVert | NET::Max | NET::FullScreen); + NETRootInfo(m_xcbConnection, NET::Properties(), NET::WM2MoveResizeWindow).moveResizeWindowRequest(windowId, flags, X, Y, 0, 0); + QTimer::singleShot(200, this, [this, windowId, state, raiseOnCurrentDesktop] + { + KX11Extras::setState(windowId, state); + raiseWindow(windowId, raiseOnCurrentDesktop); + }); + break; + } + } + } +} + +bool LXQtTaskbarX11Backend::isWindowOnScreen(QScreen *screen, WId windowId) const +{ + //TODO: old code was: + //return QApplication::desktop()->screenGeometry(parentTaskBar()).intersects(KWindowInfo(mWindow, NET::WMFrameExtents).frameGeometry()); + + if(!screen) + return true; + + QRect r = KWindowInfo(windowId, NET::WMFrameExtents).frameGeometry(); + return screen->geometry().intersects(r); +} + +bool LXQtTaskbarX11Backend::setDesktopLayout(Qt::Orientation orientation, int rows, int columns, bool rightToLeft) +{ + NETRootInfo mDesktops(m_xcbConnection, NET::NumberOfDesktops | NET::CurrentDesktop | NET::DesktopNames, NET::WM2DesktopLayout); + + if (orientation == Qt::Horizontal) + { + mDesktops.setDesktopLayout(NET::OrientationHorizontal, + columns, rows, + rightToLeft ? NET::DesktopLayoutCornerTopRight : NET::DesktopLayoutCornerTopLeft); + } + else + { + mDesktops.setDesktopLayout(NET::OrientationHorizontal, + rows, columns, + rightToLeft ? NET::DesktopLayoutCornerTopRight : NET::DesktopLayoutCornerTopLeft); + } + return true; +} + +/************************************************ + * X11 Specific + ************************************************/ +void LXQtTaskbarX11Backend::moveApplication(WId windowId) +{ + KWindowInfo info(windowId, NET::WMDesktop); + if (!info.isOnCurrentDesktop()) + KX11Extras::setCurrentDesktop(info.desktop()); + + if (getWindowState(windowId) == LXQtTaskBarWindowState::Minimized) + KX11Extras::unminimizeWindow(windowId); + + KX11Extras::forceActiveWindow(windowId); + + const QRect& g = KWindowInfo(windowId, NET::WMGeometry).geometry(); + int X = g.center().x(); + int Y = g.center().y(); + QCursor::setPos(X, Y); + NETRootInfo(m_xcbConnection, NET::WMMoveResize).moveResizeRequest(windowId, X, Y, NET::Move); +} + +void LXQtTaskbarX11Backend::resizeApplication(WId windowId) +{ + KWindowInfo info(windowId, NET::WMDesktop); + if (!info.isOnCurrentDesktop()) + KX11Extras::setCurrentDesktop(info.desktop()); + + if (getWindowState(windowId) == LXQtTaskBarWindowState::Minimized) + KX11Extras::unminimizeWindow(windowId); + + KX11Extras::forceActiveWindow(windowId); + + const QRect& g = KWindowInfo(windowId, NET::WMGeometry).geometry(); + int X = g.bottomRight().x(); + int Y = g.bottomRight().y(); + QCursor::setPos(X, Y); + NETRootInfo(m_xcbConnection, NET::WMMoveResize).moveResizeRequest(windowId, X, Y, NET::BottomRight); +} + +void LXQtTaskbarX11Backend::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. + + WId appRootWindow = XDefaultRootWindow(m_X11Display); + + NETWinInfo info(m_xcbConnection, + windowId, + appRootWindow, + NET::WMIconGeometry, + NET::Properties2()); + NETRect const curr = info.iconGeometry(); + + // see kwindowsystem -> NETWinInfo::setIconGeometry for the scale factor + const qreal scaleFactor = qApp->devicePixelRatio(); + int xPos = geom.x() * scaleFactor; + int yPos = geom.y() * scaleFactor; + int w = geom.width() * scaleFactor; + int h = geom.height() * scaleFactor; + if (xPos == curr.pos.x && yPos == curr.pos.y && w == curr.size.width && h == curr.size.height) + return; + NETRect nrect; + nrect.pos.x = geom.x(); + nrect.pos.y = geom.y(); + nrect.size.height = geom.height(); + nrect.size.width = geom.width(); + info.setIconGeometry(nrect); +} + +bool LXQtTaskbarX11Backend::isAreaOverlapped(const QRect &area) const +{ + //TODO: reuse our m_windows cache? + QFlags ignoreList; + ignoreList |= NET::DesktopMask; + ignoreList |= NET::DockMask; + ignoreList |= NET::SplashMask; + ignoreList |= NET::MenuMask; + ignoreList |= NET::PopupMenuMask; + ignoreList |= NET::DropdownMenuMask; + ignoreList |= NET::TopMenuMask; + ignoreList |= NET::NotificationMask; + + const auto wIds = KX11Extras::stackingOrder(); + for (auto const wId : wIds) + { + KWindowInfo info(wId, NET::WMWindowType | NET::WMState | NET::WMFrameExtents | NET::WMDesktop); + if (info.valid() + // skip windows that are on other desktops + && info.isOnCurrentDesktop() + // skip shaded, minimized or hidden windows + && !(info.state() & (NET::Shaded | NET::Hidden)) + // check against the list of ignored types + && !NET::typeMatchesMask(info.windowType(NET::AllTypesMask), ignoreList)) + { + if (info.frameGeometry().intersects(area)) + return true; + } + } + return false; +} + +bool LXQtTaskbarX11Backend::isShowingDesktop() const +{ + return KWindowSystem::showingDesktop(); +} + +bool LXQtTaskbarX11Backend::showDesktop(bool value) +{ + KWindowSystem::setShowingDesktop(value); + return true; +} diff --git a/panel/backends/xcb/lxqttaskbarbackend_x11.h b/panel/backends/xcb/lxqttaskbarbackend_x11.h new file mode 100644 index 000000000..2478b3fff --- /dev/null +++ b/panel/backends/xcb/lxqttaskbarbackend_x11.h @@ -0,0 +1,89 @@ +#ifndef LXQTTASKBARBACKEND_X11_H +#define LXQTTASKBARBACKEND_X11_H + +#include "../ilxqttaskbarabstractbackend.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 +{ + Q_OBJECT + +public: + explicit LXQtTaskbarX11Backend(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 slots: + void onWindowChanged(WId windowId, NET::Properties prop, NET::Properties2 prop2); + void onWindowAdded(WId windowId); + void onWindowRemoved(WId windowId); + +private: + bool acceptWindow(WId windowId) const; + void addWindow_internal(WId windowId, bool emitAdded = true); + +private: + Display *m_X11Display; + xcb_connection_t *m_xcbConnection; + + QVector m_windows; +}; + +#endif // LXQTTASKBARBACKEND_X11_H diff --git a/panel/lxqtpanel.cpp b/panel/lxqtpanel.cpp index a1fb68f35..f4144ff41 100644 --- a/panel/lxqtpanel.cpp +++ b/panel/lxqtpanel.cpp @@ -51,7 +51,9 @@ #include #include #include -#include + +#include "backends/ilxqttaskbarabstractbackend.h" + #include @@ -276,18 +278,21 @@ LXQtPanel::LXQtPanel(const QString &configGroup, LXQt::Settings *settings, QWidg QTimer::singleShot(PANEL_HIDE_FIRST_TIME, this, SLOT(hidePanel())); } - connect(KX11Extras::self(), &KX11Extras::windowAdded, this, [this] { + LXQtPanelApplication *a = reinterpret_cast(qApp); + auto wmBackend = a->getWMBackend(); + + connect(wmBackend, &ILXQtTaskbarAbstractBackend::windowAdded, this, [this] { if (mHidable && mHideOnOverlap && !mHidden) { mShowDelayTimer.stop(); hidePanel(); } }); - connect(KX11Extras::self(), &KX11Extras::windowRemoved, this, [this] { + connect(wmBackend, &ILXQtTaskbarAbstractBackend::windowRemoved, this, [this] { if (mHidable && mHideOnOverlap && mHidden && !isPanelOverlapped()) mShowDelayTimer.start(); }); - connect(KX11Extras::self(), &KX11Extras::currentDesktopChanged, this, [this] { + connect(wmBackend, &ILXQtTaskbarAbstractBackend::currentWorkspaceChanged, this, [this] { if (mHidable && mHideOnOverlap) { if (!mHidden) @@ -299,12 +304,12 @@ LXQtPanel::LXQtPanel(const QString &configGroup, LXQt::Settings *settings, QWidg mShowDelayTimer.start(); } }); - connect(KX11Extras::self(), - static_cast(&KX11Extras::windowChanged), - this, [this] (WId /* id */, NET::Properties prop, NET::Properties2) { + connect(wmBackend, &ILXQtTaskbarAbstractBackend::windowPropertyChanged, + this, [this] (WId /* id */, int prop) + { if (mHidable && mHideOnOverlap // when a window is moved, resized, shaded, or minimized - && (prop.testFlag(NET::WMGeometry) || prop.testFlag(NET::WMState))) + && (prop == int(LXQtTaskBarWindowProperty::Geometry) || prop == int(LXQtTaskBarWindowProperty::State))) { if (!mHidden) { @@ -453,7 +458,8 @@ LXQtPanel::~LXQtPanel() void LXQtPanel::show() { QWidget::show(); - KX11Extras::setOnDesktop(effectiveWinId(), NET::OnAllDesktops); + if(qGuiApp->nativeInterface()) //TODO: cache in bool isPlatformX11 + KX11Extras::setOnDesktop(effectiveWinId(), NET::OnAllDesktops); } @@ -1255,7 +1261,7 @@ bool LXQtPanel::event(QEvent *event) switch (event->type()) { case QEvent::ContextMenu: - showPopupMenu(); + showPopupMenu(static_cast(event)->globalPos()); break; case QEvent::LayoutRequest: @@ -1264,22 +1270,25 @@ bool LXQtPanel::event(QEvent *event) case QEvent::WinIdChange: { - // qDebug() << "WinIdChange" << hex << effectiveWinId(); - if(effectiveWinId() == 0) - break; + if(qGuiApp->nativeInterface()) + { + // qDebug() << "WinIdChange" << hex << effectiveWinId(); + if(effectiveWinId() == 0) + break; - // Sometimes Qt needs to re-create the underlying window of the widget and - // the winId() may be changed at runtime. So we need to reset all X11 properties - // when this happens. + // Sometimes Qt needs to re-create the underlying window of the widget and + // the winId() may be changed at runtime. So we need to reset all X11 properties + // when this happens. qDebug() << "WinIdChange" << Qt::hex << effectiveWinId() << "handle" << windowHandle() << windowHandle()->screen(); - // Qt::WA_X11NetWmWindowTypeDock becomes ineffective in Qt 5 - // See QTBUG-39887: https://bugreports.qt-project.org/browse/QTBUG-39887 - // Let's use KWindowSystem for that - KX11Extras::setType(effectiveWinId(), NET::Dock); + // Qt::WA_X11NetWmWindowTypeDock becomes ineffective in Qt 5 + // See QTBUG-39887: https://bugreports.qt-project.org/browse/QTBUG-39887 + // Let's use KWindowSystem for that + KX11Extras::setType(effectiveWinId(), NET::Dock); - updateWmStrut(); // reserve screen space for the panel - KX11Extras::setOnAllDesktops(effectiveWinId(), true); + updateWmStrut(); // reserve screen space for the panel + KX11Extras::setOnAllDesktops(effectiveWinId(), true); + } break; } case QEvent::DragEnter: @@ -1320,7 +1329,7 @@ void LXQtPanel::showEvent(QShowEvent *event) /************************************************ ************************************************/ -void LXQtPanel::showPopupMenu(Plugin *plugin) +void LXQtPanel::showPopupMenu(const QPoint& cursorPos, Plugin *plugin) { PopupMenu * menu = new PopupMenu(tr("Panel"), this); menu->setAttribute(Qt::WA_DeleteOnClose); @@ -1388,7 +1397,7 @@ void LXQtPanel::showPopupMenu(Plugin *plugin) * sometimes wrongly (it seems that this bug is somehow connected to misinterpretation * of QDesktopWidget::availableGeometry) */ - menu->setGeometry(calculatePopupWindowPos(QCursor::pos(), menu->sizeHint())); + menu->setGeometry(calculatePopupWindowPos(cursorPos, menu->sizeHint())); willShowWindow(menu); menu->show(); } @@ -1553,33 +1562,11 @@ void LXQtPanel::userRequestForDeletion() bool LXQtPanel::isPanelOverlapped() const { - QFlags ignoreList; - ignoreList |= NET::DesktopMask; - ignoreList |= NET::DockMask; - ignoreList |= NET::SplashMask; - ignoreList |= NET::MenuMask; - ignoreList |= NET::PopupMenuMask; - ignoreList |= NET::DropdownMenuMask; - ignoreList |= NET::TopMenuMask; - ignoreList |= NET::NotificationMask; - - const auto wIds = KX11Extras::stackingOrder(); - for (auto const wId : wIds) - { - KWindowInfo info(wId, NET::WMWindowType | NET::WMState | NET::WMFrameExtents | NET::WMDesktop); - if (info.valid() - // skip windows that are on other desktops - && info.isOnCurrentDesktop() - // skip shaded, minimized or hidden windows - && !(info.state() & (NET::Shaded | NET::Hidden)) - // check against the list of ignored types - && !NET::typeMatchesMask(info.windowType(NET::AllTypesMask), ignoreList)) - { - if (info.frameGeometry().intersects(mGeometry)) - return true; - } - } - return false; + LXQtPanelApplication *a = reinterpret_cast(qApp); + + //TODO: calculate geometry on wayland + QRect area = mGeometry; + return a->getWMBackend()->isAreaOverlapped(area); } void LXQtPanel::showPanel(bool animate) diff --git a/panel/lxqtpanel.h b/panel/lxqtpanel.h index c1930c1bf..ed952dbdb 100644 --- a/panel/lxqtpanel.h +++ b/panel/lxqtpanel.h @@ -139,10 +139,11 @@ class LXQT_PANEL_API LXQtPanel : public QFrame, public ILXQtPanel * is given as parameter, the menu will be divided in two groups: * plugin-specific options and panel-related options. As these two are * shown together, this menu has to be created by LXQtPanel. + * @param cursorPos The global cursor pos * @param plugin The plugin whose menu options will be included in the * context menu. */ - void showPopupMenu(Plugin *plugin = 0); + void showPopupMenu(const QPoint &cursorPos, Plugin *plugin = nullptr); // ILXQtPanel overrides ........ ILXQtPanel::Position position() const override { return mPosition; } diff --git a/panel/lxqtpanelapplication.cpp b/panel/lxqtpanelapplication.cpp index 430ea6f35..c3b666620 100644 --- a/panel/lxqtpanelapplication.cpp +++ b/panel/lxqtpanelapplication.cpp @@ -25,22 +25,41 @@ * * END_COMMON_COPYRIGHT_HEADER */ - #include "lxqtpanelapplication.h" #include "lxqtpanelapplication_p.h" -#include "lxqtpanel.h" + #include "config/configpaneldialog.h" -#include -#include -#include +#include "lxqtpanel.h" + +#include #include +#include #include -#include +#include +#include + +#include "backends/lxqttaskbardummybackend.h" +#include "backends/xcb/lxqttaskbarbackend_x11.h" + +ILXQtTaskbarAbstractBackend *createWMBackend() +{ + if(qGuiApp->nativeInterface()) + return new LXQtTaskbarX11Backend; + + 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"; + + return new LXQtTaskBarDummyBackend; +} LXQtPanelApplicationPrivate::LXQtPanelApplicationPrivate(LXQtPanelApplication *q) : mSettings(nullptr), q_ptr(q) { + mWMBackend = createWMBackend(); } @@ -290,6 +309,12 @@ bool LXQtPanelApplication::isPluginSingletonAndRunning(QString const & pluginId) return false; } +ILXQtTaskbarAbstractBackend *LXQtPanelApplication::getWMBackend() const +{ + Q_D(const LXQtPanelApplication); + return d->mWMBackend; +} + // See LXQtPanelApplication::LXQtPanelApplication for why this isn't good. void LXQtPanelApplication::setIconTheme(const QString &iconTheme) { diff --git a/panel/lxqtpanelapplication.h b/panel/lxqtpanelapplication.h index a672e7e46..15c912884 100644 --- a/panel/lxqtpanelapplication.h +++ b/panel/lxqtpanelapplication.h @@ -37,6 +37,8 @@ class QScreen; class LXQtPanel; class LXQtPanelApplicationPrivate; +class ILXQtTaskbarAbstractBackend; + /*! * \brief The LXQtPanelApplication class inherits from LXQt::Application and * is therefore the QApplication that we will create and execute in our @@ -89,6 +91,8 @@ class LXQtPanelApplication : public LXQt::Application */ bool isPluginSingletonAndRunning(QString const & pluginId) const; + ILXQtTaskbarAbstractBackend* getWMBackend() const; + public slots: /*! * \brief Adds a new LXQtPanel which consists of the following steps: diff --git a/panel/lxqtpanelapplication_p.h b/panel/lxqtpanelapplication_p.h index 4dd26182c..db924bf62 100644 --- a/panel/lxqtpanelapplication_p.h +++ b/panel/lxqtpanelapplication_p.h @@ -27,6 +27,8 @@ namespace LXQt { class Settings; } +class ILXQtTaskbarAbstractBackend; + class LXQtPanelApplicationPrivate { Q_DECLARE_PUBLIC(LXQtPanelApplication) public: @@ -35,6 +37,7 @@ class LXQtPanelApplicationPrivate { ~LXQtPanelApplicationPrivate() {}; LXQt::Settings *mSettings; + ILXQtTaskbarAbstractBackend *mWMBackend; ILXQtPanel::Position computeNewPanelPosition(const LXQtPanel *p, const int screenNum); diff --git a/panel/plugin.cpp b/panel/plugin.cpp index eb22ed431..4844bd528 100644 --- a/panel/plugin.cpp +++ b/panel/plugin.cpp @@ -380,9 +380,9 @@ void Plugin::saveSettings() /************************************************ ************************************************/ -void Plugin::contextMenuEvent(QContextMenuEvent * /*event*/) +void Plugin::contextMenuEvent(QContextMenuEvent * event) { - mPanel->showPopupMenu(this); + mPanel->showPopupMenu(event->globalPos(), this); } diff --git a/plugin-desktopswitch/desktopswitch.cpp b/plugin-desktopswitch/desktopswitch.cpp index 91004b4ea..ea2a0fce3 100644 --- a/plugin-desktopswitch/desktopswitch.cpp +++ b/plugin-desktopswitch/desktopswitch.cpp @@ -25,6 +25,7 @@ * * END_COMMON_COPYRIGHT_HEADER */ +#include #include #include #include @@ -33,6 +34,8 @@ #include #include +#include "../panel/lxqtpanelapplication.h" +#include "../panel/backends/ilxqttaskbarabstractbackend.h" #include @@ -40,29 +43,22 @@ #include "desktopswitchbutton.h" #include "desktopswitchconfiguration.h" -#include -#include -#include - -//NOTE: Xlib.h defines Bool which conflicts with QJsonValue::Type enum -#include -#undef Bool - static const QString DEFAULT_SHORTCUT_TEMPLATE(QStringLiteral("Control+F%1")); DesktopSwitch::DesktopSwitch(const ILXQtPanelPluginStartupInfo &startupInfo) : QObject(), ILXQtPanelPlugin(startupInfo), m_pSignalMapper(new QSignalMapper(this)), - m_desktopCount(KX11Extras::numberOfDesktops()), + m_desktopCount(0), mRows(-1), mShowOnlyActive(false), - mDesktops(nullptr), mLabelType(DesktopSwitchButton::LABEL_TYPE_INVALID) { - auto *x11Application = qGuiApp->nativeInterface(); - Q_ASSERT_X(x11Application, "DesktopSwitch", "Expected X11 connection"); - mDesktops.reset(new NETRootInfo(x11Application->connection(), NET::NumberOfDesktops | NET::CurrentDesktop | NET::DesktopNames, NET::WM2DesktopLayout)); + LXQtPanelApplication *a = reinterpret_cast(qApp); + mBackend = a->getWMBackend(); + + + m_desktopCount = mBackend->getWorkspacesCount(); m_buttons = new QButtonGroup(this); @@ -74,17 +70,16 @@ DesktopSwitch::DesktopSwitch(const ILXQtPanelPluginStartupInfo &startupInfo) : settingsChanged(); - onCurrentDesktopChanged(KX11Extras::currentDesktop()); + onCurrentDesktopChanged(mBackend->getCurrentWorkspace()); QTimer::singleShot(0, this, SLOT(registerShortcuts())); connect(m_buttons, &QButtonGroup::idClicked, this, &DesktopSwitch::setDesktop); - connect(KX11Extras::self(), &KX11Extras::numberOfDesktopsChanged, this, &DesktopSwitch::onNumberOfDesktopsChanged); - connect(KX11Extras::self(), &KX11Extras::currentDesktopChanged, this, &DesktopSwitch::onCurrentDesktopChanged); - connect(KX11Extras::self(), &KX11Extras::desktopNamesChanged, this, &DesktopSwitch::onDesktopNamesChanged); + connect(mBackend, &ILXQtTaskbarAbstractBackend::workspacesCountChanged, this, &DesktopSwitch::onNumberOfDesktopsChanged); + connect(mBackend, &ILXQtTaskbarAbstractBackend::currentWorkspaceChanged, this, &DesktopSwitch::onCurrentDesktopChanged); + connect(mBackend, &ILXQtTaskbarAbstractBackend::workspaceNameChanged, this, &DesktopSwitch::onDesktopNamesChanged); - connect(KX11Extras::self(), static_cast(&KX11Extras::windowChanged), - this, &DesktopSwitch::onWindowChanged); + connect(mBackend, &ILXQtTaskbarAbstractBackend::windowPropertyChanged, this, &DesktopSwitch::onWindowChanged); } void DesktopSwitch::registerShortcuts() @@ -126,19 +121,18 @@ void DesktopSwitch::shortcutRegistered() } } -void DesktopSwitch::onWindowChanged(WId id, NET::Properties properties, NET::Properties2 /*properties2*/) +void DesktopSwitch::onWindowChanged(WId id, int prop) { - if (properties.testFlag(NET::WMState) && isWindowHighlightable(id)) + if (prop == int(LXQtTaskBarWindowProperty::State)) { - KWindowInfo info = KWindowInfo(id, NET::WMDesktop | NET::WMState); - int desktop = info.desktop(); - if (!info.valid() || info.onAllDesktops()) + int desktop = mBackend->getWindowWorkspace(id); + if (desktop == int(LXQtTaskBarWorkspace::ShowOnAll)) return; else { DesktopSwitchButton *button = static_cast(m_buttons->button(desktop - 1)); if(button) - button->setUrgencyHint(id, info.hasState(NET::DemandsAttention)); + button->setUrgencyHint(id, mBackend->applicationDemandsAttention(id)); } } } @@ -148,7 +142,7 @@ void DesktopSwitch::refresh() const QList btns = m_buttons->buttons(); int i = 0; - const int current_desktop = KX11Extras::currentDesktop(); + const int current_desktop = mBackend->getCurrentWorkspace(); const int current_cnt = btns.count(); const int border = qMin(btns.count(), m_desktopCount); //update existing buttons @@ -156,9 +150,9 @@ void DesktopSwitch::refresh() { DesktopSwitchButton * button = qobject_cast(btns[i]); button->update(i, mLabelType, - KX11Extras::desktopName(i + 1).isEmpty() ? + mBackend->getWorkspaceName(i + 1).isEmpty() ? tr("Desktop %1").arg(i + 1) : - KX11Extras::desktopName(i + 1)); + mBackend->getWorkspaceName(i + 1)); button->setVisible(!mShowOnlyActive || i + 1 == current_desktop); } @@ -167,9 +161,9 @@ void DesktopSwitch::refresh() for ( ; i < m_desktopCount; ++i) { b = new DesktopSwitchButton(&mWidget, i, mLabelType, - KX11Extras::desktopName(i+1).isEmpty() ? + mBackend->getWorkspaceName(i+1).isEmpty() ? tr("Desktop %1").arg(i+1) : - KX11Extras::desktopName(i+1)); + mBackend->getWorkspaceName(i+1)); mWidget.layout()->addWidget(b); m_buttons->addButton(b, i); b->setVisible(!mShowOnlyActive || i + 1 == current_desktop); @@ -185,56 +179,16 @@ void DesktopSwitch::refresh() } } -bool DesktopSwitch::isWindowHighlightable(WId window) -{ - // this method was borrowed from the taskbar plugin - QFlags ignoreList; - ignoreList |= NET::DesktopMask; - ignoreList |= NET::DockMask; - ignoreList |= NET::SplashMask; - ignoreList |= NET::ToolbarMask; - ignoreList |= NET::MenuMask; - ignoreList |= NET::PopupMenuMask; - ignoreList |= NET::NotificationMask; - - KWindowInfo info(window, NET::WMWindowType | NET::WMState, NET::WM2TransientFor); - if (!info.valid()) - return false; - - if (NET::typeMatchesMask(info.windowType(NET::AllTypesMask), ignoreList)) - return false; - - if (info.state() & NET::SkipTaskbar) - return false; - - auto *x11Application = qGuiApp->nativeInterface(); - Q_ASSERT_X(x11Application, "DesktopSwitch", "Expected X11 connection"); - WId appRootWindow = XDefaultRootWindow(x11Application->display()); - - // WM_TRANSIENT_FOR hint not set - normal window - WId transFor = info.transientFor(); - if (transFor == 0 || transFor == window || transFor == appRootWindow) - return true; - - info = KWindowInfo(transFor, NET::WMWindowType); - - QFlags normalFlag; - normalFlag |= NET::NormalMask; - normalFlag |= NET::DialogMask; - normalFlag |= NET::UtilityMask; - - return !NET::typeMatchesMask(info.windowType(NET::AllTypesMask), normalFlag); -} - DesktopSwitch::~DesktopSwitch() = default; void DesktopSwitch::setDesktop(int desktop) { - KX11Extras::setCurrentDesktop(desktop + 1); + mBackend->setCurrentWorkspace(desktop + 1); } -void DesktopSwitch::onNumberOfDesktopsChanged(int count) +void DesktopSwitch::onNumberOfDesktopsChanged() { + int count = mBackend->getWorkspacesCount(); qDebug() << "Desktop count changed from" << m_desktopCount << "to" << count; m_desktopCount = count; refresh(); @@ -291,14 +245,9 @@ void DesktopSwitch::settingsChanged() // "DesktopSwitch::realign()". Therefore, the desktop layout should not be changed // inside the latter method. int columns = static_cast(ceil(static_cast(m_desktopCount) / mRows)); - if (panel()->isHorizontal()) - { - mDesktops->setDesktopLayout(NET::OrientationHorizontal, columns, mRows, mWidget.isRightToLeft() ? NET::DesktopLayoutCornerTopRight : NET::DesktopLayoutCornerTopLeft); - } - else - { - mDesktops->setDesktopLayout(NET::OrientationHorizontal, mRows, columns, mWidget.isRightToLeft() ? NET::DesktopLayoutCornerTopRight : NET::DesktopLayoutCornerTopLeft); - } + mBackend->setDesktopLayout(panel()->isHorizontal() ? Qt::Horizontal : Qt::Vertical, + mRows, columns, mWidget.isRightToLeft()); + realign(); // in case it isn't called when the desktop layout changes } if (need_refresh) @@ -345,9 +294,12 @@ void DesktopSwitchWidget::wheelEvent(QWheelEvent *e) if(abs(m_mouseWheelThresholdCounter) < 100) return; - int max = KX11Extras::numberOfDesktops(); + LXQtPanelApplication *a = reinterpret_cast(qApp); + auto wmBackend = a->getWMBackend(); + + int max = wmBackend->getWorkspacesCount(); int delta = rotationSteps < 0 ? 1 : -1; - int current = KX11Extras::currentDesktop() + delta; + int current = wmBackend->getCurrentWorkspace() + delta; if (current > max){ current = 1; @@ -356,5 +308,33 @@ void DesktopSwitchWidget::wheelEvent(QWheelEvent *e) current = max; m_mouseWheelThresholdCounter = 0; - KX11Extras::setCurrentDesktop(current); + wmBackend->setCurrentWorkspace(current); +} + +ILXQtPanelPlugin *DesktopSwitchPluginLibrary::instance(const ILXQtPanelPluginStartupInfo &startupInfo) const +{ + LXQtPanelApplication *a = reinterpret_cast(qApp); + auto wmBackend = a ? a->getWMBackend() : nullptr; + if(!wmBackend || !wmBackend->supportsAction(0, LXQtTaskBarBackendAction::DesktopSwitch)) + return new DesktopSwitchUnsupported{startupInfo}; + + return new DesktopSwitch{startupInfo}; +} + +DesktopSwitchUnsupported::DesktopSwitchUnsupported(const ILXQtPanelPluginStartupInfo &startupInfo) + : ILXQtPanelPlugin(startupInfo) + , mLabel(new QLabel(tr("n/a"))) +{ + mLabel->setToolTip(tr("DesktopSwitch is unsupported on current platform: %1").arg(QGuiApplication::platformName())); +} + +DesktopSwitchUnsupported::~DesktopSwitchUnsupported() +{ + delete mLabel; + mLabel = nullptr; +} + +QWidget *DesktopSwitchUnsupported::widget() +{ + return mLabel; } diff --git a/plugin-desktopswitch/desktopswitch.h b/plugin-desktopswitch/desktopswitch.h index 93a930dc2..182eb0ced 100644 --- a/plugin-desktopswitch/desktopswitch.h +++ b/plugin-desktopswitch/desktopswitch.h @@ -30,21 +30,19 @@ #define DESKTOPSWITCH_H #include "../panel/ilxqtpanelplugin.h" -#include #include -#include -#include -#include #include "desktopswitchbutton.h" +class QLabel; class QSignalMapper; class QButtonGroup; -class NETRootInfo; namespace LXQt { class GridLayout; } +class ILXQtTaskbarAbstractBackend; + class DesktopSwitchWidget: public QFrame { Q_OBJECT @@ -85,39 +83,34 @@ class DesktopSwitch : public QObject, public ILXQtPanelPlugin LXQt::GridLayout *mLayout; int mRows; bool mShowOnlyActive; - std::unique_ptr mDesktops; + ILXQtTaskbarAbstractBackend *mBackend; DesktopSwitchButton::LabelType mLabelType; void refresh(); - bool isWindowHighlightable(WId window); private slots: void setDesktop(int desktop); - void onNumberOfDesktopsChanged(int); + void onNumberOfDesktopsChanged(); void onCurrentDesktopChanged(int); void onDesktopNamesChanged(); virtual void settingsChanged(); void registerShortcuts(); void shortcutRegistered(); - void onWindowChanged(WId id, NET::Properties properties, NET::Properties2 properties2); + void onWindowChanged(WId id, int prop); }; class DesktopSwitchUnsupported : public QObject, public ILXQtPanelPlugin { Q_OBJECT public: - DesktopSwitchUnsupported(const ILXQtPanelPluginStartupInfo &startupInfo) - : ILXQtPanelPlugin(startupInfo) - , mLabel{tr("n/a")} - { - mLabel.setToolTip(tr("DesktopSwitch is unsupported on current platform: %1").arg(QGuiApplication::platformName())); - } + DesktopSwitchUnsupported(const ILXQtPanelPluginStartupInfo &startupInfo); + ~DesktopSwitchUnsupported(); QString themeId() const { return QStringLiteral("DesktopSwitchUnsupported"); } - QWidget *widget() { return &mLabel; } + QWidget *widget(); bool isSeparate() const { return true; } private: - QLabel mLabel; + QLabel *mLabel; }; class DesktopSwitchPluginLibrary: public QObject, public ILXQtPanelPluginLibrary @@ -126,13 +119,7 @@ class DesktopSwitchPluginLibrary: public QObject, public ILXQtPanelPluginLibrary // Q_PLUGIN_METADATA(IID "lxqt.org/Panel/PluginInterface/3.0") Q_INTERFACES(ILXQtPanelPluginLibrary) public: - ILXQtPanelPlugin *instance(const ILXQtPanelPluginStartupInfo &startupInfo) const - { - if (QGuiApplication::platformName() == QStringLiteral("xcb")) - return new DesktopSwitch{startupInfo}; - else - return new DesktopSwitchUnsupported{startupInfo}; - } + ILXQtPanelPlugin *instance(const ILXQtPanelPluginStartupInfo &startupInfo) const; }; #endif diff --git a/plugin-desktopswitch/desktopswitchconfiguration.cpp b/plugin-desktopswitch/desktopswitchconfiguration.cpp index 943dbdd6b..33781f7d8 100644 --- a/plugin-desktopswitch/desktopswitchconfiguration.cpp +++ b/plugin-desktopswitch/desktopswitchconfiguration.cpp @@ -26,6 +26,10 @@ #include "desktopswitchconfiguration.h" #include "ui_desktopswitchconfiguration.h" + +#include "../panel/lxqtpanelapplication.h" +#include "../panel/backends/ilxqttaskbarabstractbackend.h" + #include #include @@ -64,18 +68,30 @@ void DesktopSwitchConfiguration::loadSettings() void DesktopSwitchConfiguration::loadDesktopsNames() { - int n = KX11Extras::numberOfDesktops(); + LXQtPanelApplication *a = reinterpret_cast(qApp); + auto wmBackend = a->getWMBackend(); + + int n = wmBackend->getWorkspacesCount(); for (int i = 1; i <= n; i++) { - QLineEdit *edit = new QLineEdit(KX11Extras::desktopName(i), this); + QLineEdit *edit = new QLineEdit(wmBackend->getWorkspaceName(i), this); ((QFormLayout *) ui->namesGroupBox->layout())->addRow(tr("Desktop %1:").arg(i), edit); - // C++11 rocks! - QTimer *timer = new QTimer(this); - timer->setInterval(400); - timer->setSingleShot(true); - connect(timer, &QTimer::timeout, this, [=] { KX11Extras::setDesktopName(i, edit->text()); }); - connect(edit, &QLineEdit::textEdited, this, [=] { timer->start(); }); + //TODO: on Wayland we cannot set desktop names in a standart way + // On KWin we could use DBus org.kde.KWin as done by kcm_kwin_virtualdesktops + if(qGuiApp->nativeInterface()) + { + // C++11 rocks! + QTimer *timer = new QTimer(this); + timer->setInterval(400); + timer->setSingleShot(true); + connect(timer, &QTimer::timeout, this, [=] { KX11Extras::setDesktopName(i, edit->text()); }); + connect(edit, &QLineEdit::textEdited, this, [=] { timer->start(); }); + } + else + { + edit->setReadOnly(true); + } } } diff --git a/plugin-mainmenu/lxqtmainmenu.cpp b/plugin-mainmenu/lxqtmainmenu.cpp index 1db7f911f..0f55cdfbd 100644 --- a/plugin-mainmenu/lxqtmainmenu.cpp +++ b/plugin-mainmenu/lxqtmainmenu.cpp @@ -54,7 +54,7 @@ #include #include #include -#include + #include #endif #define DEFAULT_SHORTCUT "Alt+F1" diff --git a/plugin-showdesktop/showdesktop.cpp b/plugin-showdesktop/showdesktop.cpp index 871da62ea..fb69f6067 100644 --- a/plugin-showdesktop/showdesktop.cpp +++ b/plugin-showdesktop/showdesktop.cpp @@ -29,11 +29,12 @@ #include #include #include -#include -#include #include "showdesktop.h" #include "../panel/pluginsettings.h" +#include "../panel/lxqtpanelapplication.h" +#include "../panel/backends/ilxqttaskbarabstractbackend.h" + #define DEFAULT_SHORTCUT "Control+Alt+D" ShowDesktop::ShowDesktop(const ILXQtPanelPluginStartupInfo &startupInfo) : @@ -69,7 +70,9 @@ void ShowDesktop::shortcutRegistered() void ShowDesktop::toggleShowingDesktop() { - KWindowSystem::setShowingDesktop(!KWindowSystem::showingDesktop()); + LXQtPanelApplication *a = reinterpret_cast(qApp); + auto wmBackend = a->getWMBackend(); + wmBackend->showDesktop(!wmBackend->isShowingDesktop()); } #undef DEFAULT_SHORTCUT diff --git a/plugin-taskbar/CMakeLists.txt b/plugin-taskbar/CMakeLists.txt index 1d627f23e..74a00d6e6 100644 --- a/plugin-taskbar/CMakeLists.txt +++ b/plugin-taskbar/CMakeLists.txt @@ -7,6 +7,8 @@ set(HEADERS lxqttaskbarplugin.h lxqttaskgroup.h lxqtgrouppopup.h + + lxqttaskbarproxymodel.h ) set(SOURCES @@ -16,6 +18,8 @@ set(SOURCES lxqttaskbarplugin.cpp lxqttaskgroup.cpp lxqtgrouppopup.cpp + + lxqttaskbarproxymodel.cpp ) set(UIS diff --git a/plugin-taskbar/lxqttaskbar.cpp b/plugin-taskbar/lxqttaskbar.cpp index 5dfa289c9..fb41ae7e0 100644 --- a/plugin-taskbar/lxqttaskbar.cpp +++ b/plugin-taskbar/lxqttaskbar.cpp @@ -28,6 +28,8 @@ * * END_COMMON_COPYRIGHT_HEADER */ +#include "lxqttaskbar.h" + #include #include #include @@ -39,17 +41,17 @@ #include #include +#include "../panel/ilxqtpanelplugin.h" +#include "../panel/pluginsettings.h" + #include #include -#include -#include "lxqttaskbar.h" #include "lxqttaskgroup.h" #include "../panel/pluginsettings.h" -//NOTE: Xlib.h defines Bool which conflicts with QJsonValue::Type enum -#include -#undef Bool +#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/lxqtpanelapplication.h" using namespace LXQt; @@ -77,7 +79,8 @@ LXQtTaskBar::LXQtTaskBar(ILXQtPanelPlugin *plugin, QWidget *parent) : mWheelDeltaThreshold(300), mPlugin(plugin), mPlaceHolder(new QWidget(this)), - mStyle(new LeftAlignedTextStyle()) + mStyle(new LeftAlignedTextStyle()), + mBackend(nullptr) { setStyle(mStyle); mLayout = new LXQt::GridLayout(this); @@ -91,16 +94,26 @@ LXQtTaskBar::LXQtTaskBar(ILXQtPanelPlugin *plugin, QWidget *parent) : mPlaceHolder->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); mLayout->addWidget(mPlaceHolder); + // Get backend + LXQtPanelApplication *a = static_cast(qApp); + mBackend = a->getWMBackend(); + QTimer::singleShot(0, this, &LXQtTaskBar::settingsChanged); setAcceptDrops(true); connect(mSignalMapper, &QSignalMapper::mappedInt, this, &LXQtTaskBar::activateTask); QTimer::singleShot(0, this, &LXQtTaskBar::registerShortcuts); - connect(KX11Extras::self(), static_cast(&KX11Extras::windowChanged) - , this, &LXQtTaskBar::onWindowChanged); - connect(KX11Extras::self(), &KX11Extras::windowAdded, this, &LXQtTaskBar::onWindowAdded); - connect(KX11Extras::self(), &KX11Extras::windowRemoved, this, &LXQtTaskBar::onWindowRemoved); + connect(mBackend, &ILXQtTaskbarAbstractBackend::windowPropertyChanged, this, &LXQtTaskBar::onWindowChanged); + connect(mBackend, &ILXQtTaskbarAbstractBackend::windowAdded, this, &LXQtTaskBar::onWindowAdded); + connect(mBackend, &ILXQtTaskbarAbstractBackend::windowRemoved, this, &LXQtTaskBar::onWindowRemoved); + + // Consider already fetched windows + const auto initialWindows = mBackend->getCurrentWindows(); + for(WId windowId : initialWindows) + { + onWindowAdded(windowId); + } } /************************************************ @@ -111,49 +124,6 @@ LXQtTaskBar::~LXQtTaskBar() delete mStyle; } -/************************************************ - - ************************************************/ -bool LXQtTaskBar::acceptWindow(WId window) const -{ - QFlags ignoreList; - ignoreList |= NET::DesktopMask; - ignoreList |= NET::DockMask; - ignoreList |= NET::SplashMask; - ignoreList |= NET::ToolbarMask; - ignoreList |= NET::MenuMask; - ignoreList |= NET::PopupMenuMask; - ignoreList |= NET::NotificationMask; - - KWindowInfo info(window, NET::WMWindowType | NET::WMState, NET::WM2TransientFor); - if (!info.valid()) - return false; - - if (NET::typeMatchesMask(info.windowType(NET::AllTypesMask), ignoreList)) - return false; - - if (info.state() & NET::SkipTaskbar) - return false; - - auto *x11Application = qGuiApp->nativeInterface(); - Q_ASSERT_X(x11Application, "DesktopSwitch", "Expected X11 connection"); - WId appRootWindow = XDefaultRootWindow(x11Application->display()); - - // WM_TRANSIENT_FOR hint not set - normal window - WId transFor = info.transientFor(); - if (transFor == 0 || transFor == window || transFor == appRootWindow) - return true; - - info = KWindowInfo(transFor, NET::WMWindowType); - - QFlags normalFlag; - normalFlag |= NET::NormalMask; - normalFlag |= NET::DialogMask; - normalFlag |= NET::UtilityMask; - - return !NET::typeMatchesMask(info.windowType(NET::AllTypesMask), normalFlag); -} - /************************************************ ************************************************/ @@ -279,7 +249,7 @@ void LXQtTaskBar::groupBecomeEmptySlot() void LXQtTaskBar::addWindow(WId window) { // If grouping disabled group behaves like regular button - const QString group_id = mGroupingEnabled ? QString::fromUtf8(KWindowInfo(window, NET::Properties(), NET::WM2WindowClass).windowClassClass()) : QString::number(window); + const QString group_id = mGroupingEnabled ? mBackend->getWindowClass(window) : QString::number(window); LXQtTaskGroup *group = nullptr; auto i_group = mKnownWindows.find(window); @@ -318,7 +288,7 @@ void LXQtTaskBar::addWindow(WId window) if (mUngroupedNextToExisting) { - const QString window_class = QString::fromUtf8(KWindowInfo(window, NET::Properties(), NET::WM2WindowClass).windowClassClass()); + const QString window_class = mBackend->getWindowClass(window); int src_index = mLayout->count() - 1; int dst_index = src_index; for (int i = mLayout->count() - 2; 0 <= i; --i) @@ -326,7 +296,7 @@ void LXQtTaskBar::addWindow(WId window) LXQtTaskGroup * current_group = qobject_cast(mLayout->itemAt(i)->widget()); if (nullptr != current_group) { - const QString current_class = QString::fromUtf8(KWindowInfo((current_group->groupName()).toUInt(), NET::Properties(), NET::WM2WindowClass).windowClassClass()); + const QString current_class = mBackend->getWindowClass(current_group->groupName().toUInt()); if(current_class == window_class) { dst_index = i + 1; @@ -360,43 +330,14 @@ auto LXQtTaskBar::removeWindow(windowMap_t::iterator pos) -> windowMap_t::iterat /************************************************ ************************************************/ -void LXQtTaskBar::refreshTaskList() -{ - QList new_list; - // Just add new windows to groups, deleting is up to the groups - const auto wnds = KX11Extras::stackingOrder(); - for (auto const wnd: wnds) - { - if (acceptWindow(wnd)) - { - new_list << wnd; - addWindow(wnd); - } - } - - //emulate windowRemoved if known window not reported by KWindowSystem - for (auto i = mKnownWindows.begin(), i_e = mKnownWindows.end(); i != i_e; ) - { - if (0 > new_list.indexOf(i.key())) - { - i = removeWindow(i); - } else - ++i; - } - - refreshPlaceholderVisibility(); -} - -/************************************************ - - ************************************************/ -void LXQtTaskBar::onWindowChanged(WId window, NET::Properties prop, NET::Properties2 prop2) +void LXQtTaskBar::onWindowChanged(WId window, int prop) { auto i = mKnownWindows.find(window); if (mKnownWindows.end() != i) { - if (!(*i)->onWindowChanged(window, prop, prop2) && acceptWindow(window)) - { // window is removed from a group because of class change, so we should add it again + if (!(*i)->onWindowChanged(window, LXQtTaskBarWindowProperty(prop))) + { + // window is removed from a group because of class change, so we should add it again addWindow(window); } } @@ -405,7 +346,7 @@ void LXQtTaskBar::onWindowChanged(WId window, NET::Properties prop, NET::Propert void LXQtTaskBar::onWindowAdded(WId window) { auto const pos = mKnownWindows.find(window); - if (mKnownWindows.end() == pos && acceptWindow(window)) + if (mKnownWindows.end() == pos) addWindow(window); } @@ -531,7 +472,8 @@ void LXQtTaskBar::settingsChanged() if (iconByClassOld != mIconByClass) emit iconByClassChanged(); - refreshTaskList(); + mBackend->reloadWindows(); + refreshPlaceholderVisibility(); } /************************************************ @@ -591,6 +533,11 @@ void LXQtTaskBar::realign() emit refreshIconGeometry(); } +ILXQtPanel *LXQtTaskBar::panel() const +{ + return mPlugin->panel(); +} + /************************************************ ************************************************/ diff --git a/plugin-taskbar/lxqttaskbar.h b/plugin-taskbar/lxqttaskbar.h index 03e2bd0d2..9f94a2958 100644 --- a/plugin-taskbar/lxqttaskbar.h +++ b/plugin-taskbar/lxqttaskbar.h @@ -32,30 +32,32 @@ #ifndef LXQTTASKBAR_H #define LXQTTASKBAR_H -#include "../panel/ilxqtpanel.h" -#include "../panel/ilxqtpanelplugin.h" - #include #include #include -#include + #include "../panel/ilxqtpanel.h" -#include -#include -#include + +class ILXQtPanel; +class ILXQtPanelPlugin; class QSignalMapper; -class LXQtTaskButton; class LXQtTaskGroup; -class ElidedButtonStyle; class LeftAlignedTextStyle; +class ILXQtTaskbarAbstractBackend; + namespace LXQt { class GridLayout; } +namespace GlobalKeyShortcut +{ +class Action; +} + class LXQtTaskBar : public QFrame { Q_OBJECT @@ -80,9 +82,12 @@ class LXQtTaskBar : public QFrame bool isIconByClass() const { return mIconByClass; } int wheelEventsAction() const { return mWheelEventsAction; } int wheelDeltaThreshold() const { return mWheelDeltaThreshold; } - inline ILXQtPanel * panel() const { return mPlugin->panel(); } + + ILXQtPanel * panel() const; inline ILXQtPanelPlugin * plugin() const { return mPlugin; } + inline ILXQtTaskbarAbstractBackend *getBackend() const { return mBackend; } + public slots: void settingsChanged(); @@ -99,13 +104,14 @@ public slots: virtual void dragMoveEvent(QDragMoveEvent * event); private slots: - void refreshTaskList(); void refreshButtonRotation(); void refreshPlaceholderVisibility(); void groupBecomeEmptySlot(); - void onWindowChanged(WId window, NET::Properties prop, NET::Properties2 prop2); + + void onWindowChanged(WId window, int prop); void onWindowAdded(WId window); void onWindowRemoved(WId window); + void registerShortcuts(); void shortcutRegistered(); void activateTask(int pos); @@ -142,7 +148,6 @@ private slots: int mWheelEventsAction; int mWheelDeltaThreshold; - bool acceptWindow(WId window) const; void setButtonStyle(Qt::ToolButtonStyle buttonStyle); void wheelEvent(QWheelEvent* event); @@ -152,6 +157,8 @@ private slots: ILXQtPanelPlugin *mPlugin; QWidget *mPlaceHolder; LeftAlignedTextStyle *mStyle; + + ILXQtTaskbarAbstractBackend *mBackend; }; #endif // LXQTTASKBAR_H diff --git a/plugin-taskbar/lxqttaskbarconfiguration.cpp b/plugin-taskbar/lxqttaskbarconfiguration.cpp index b346bb69d..0dd528e51 100644 --- a/plugin-taskbar/lxqttaskbarconfiguration.cpp +++ b/plugin-taskbar/lxqttaskbarconfiguration.cpp @@ -29,7 +29,9 @@ #include "lxqttaskbarconfiguration.h" #include "ui_lxqttaskbarconfiguration.h" -#include + +#include "../panel/lxqtpanelapplication.h" +#include "../panel/backends/ilxqttaskbarabstractbackend.h" LXQtTaskbarConfiguration::LXQtTaskbarConfiguration(PluginSettings *settings, QWidget *parent): LXQtPanelPluginConfigDialog(settings, parent), @@ -52,11 +54,14 @@ LXQtTaskbarConfiguration::LXQtTaskbarConfiguration(PluginSettings *settings, QWi ui->wheelEventsActionCB->addItem(tr("Scroll up to move to next desktop, down to previous"), 4); ui->wheelEventsActionCB->addItem(tr("Scroll up to move to previous desktop, down to next"), 5); + LXQtPanelApplication *a = reinterpret_cast(qApp); + auto wmBackend = a->getWMBackend(); + ui->showDesktopNumCB->addItem(tr("Current"), 0); //Note: in KWindowSystem desktops are numbered from 1..N - const int desk_cnt = KX11Extras::numberOfDesktops(); + const int desk_cnt = wmBackend->getWorkspacesCount(); for (int i = 1; desk_cnt >= i; ++i) - ui->showDesktopNumCB->addItem(QString(QStringLiteral("%1 - %2")).arg(i).arg(KX11Extras::desktopName(i)), i); + ui->showDesktopNumCB->addItem(QString(QStringLiteral("%1 - %2")).arg(i).arg(wmBackend->getWorkspaceName(i)), i); loadSettings(); ui->ungroupedNextToExistingCB->setEnabled(!(ui->groupingGB->isChecked())); diff --git a/plugin-taskbar/lxqttaskbarconfiguration.h b/plugin-taskbar/lxqttaskbarconfiguration.h index e559508b7..930e9f441 100644 --- a/plugin-taskbar/lxqttaskbarconfiguration.h +++ b/plugin-taskbar/lxqttaskbarconfiguration.h @@ -29,8 +29,8 @@ #define LXQTTASKBARCONFIGURATION_H #include "../panel/lxqtpanelpluginconfigdialog.h" -#include "../panel/pluginsettings.h" -#include + +class PluginSettings; namespace Ui { class LXQtTaskbarConfiguration; diff --git a/plugin-taskbar/lxqttaskbarplugin.cpp b/plugin-taskbar/lxqttaskbarplugin.cpp index ae8af1403..759e6db46 100644 --- a/plugin-taskbar/lxqttaskbarplugin.cpp +++ b/plugin-taskbar/lxqttaskbarplugin.cpp @@ -28,6 +28,8 @@ #include "lxqttaskbarplugin.h" +#include "lxqttaskbar.h" + #include "lxqttaskbarconfiguration.h" LXQtTaskBarPlugin::LXQtTaskBarPlugin(const ILXQtPanelPluginStartupInfo &startupInfo): @@ -36,7 +38,6 @@ LXQtTaskBarPlugin::LXQtTaskBarPlugin(const ILXQtPanelPluginStartupInfo &startupI { mTaskBar = new LXQtTaskBar(this); - } @@ -45,11 +46,18 @@ LXQtTaskBarPlugin::~LXQtTaskBarPlugin() delete mTaskBar; } +QWidget *LXQtTaskBarPlugin::widget() { return mTaskBar; } + QDialog *LXQtTaskBarPlugin::configureDialog() { return new LXQtTaskbarConfiguration(settings()); } +void LXQtTaskBarPlugin::settingsChanged() +{ + mTaskBar->settingsChanged(); +} + void LXQtTaskBarPlugin::realign() { mTaskBar->realign(); diff --git a/plugin-taskbar/lxqttaskbarplugin.h b/plugin-taskbar/lxqttaskbarplugin.h index 9c3076990..8566b7c2b 100644 --- a/plugin-taskbar/lxqttaskbarplugin.h +++ b/plugin-taskbar/lxqttaskbarplugin.h @@ -29,10 +29,8 @@ #ifndef LXQTTASKBARPLUGIN_H #define LXQTTASKBARPLUGIN_H -#include "../panel/ilxqtpanel.h" #include "../panel/ilxqtpanelplugin.h" -#include "lxqttaskbar.h" -#include + class LXQtTaskBar; class LXQtTaskBarPlugin : public QObject, public ILXQtPanelPlugin @@ -45,10 +43,10 @@ class LXQtTaskBarPlugin : public QObject, public ILXQtPanelPlugin QString themeId() const { return QStringLiteral("TaskBar"); } virtual Flags flags() const { return HaveConfigDialog | NeedsHandle; } - QWidget *widget() { return mTaskBar; } + QWidget *widget(); QDialog *configureDialog(); - void settingsChanged() { mTaskBar->settingsChanged(); } + void settingsChanged(); void realign(); bool isSeparate() const { return true; } diff --git a/plugin-taskbar/lxqttaskbarproxymodel.cpp b/plugin-taskbar/lxqttaskbarproxymodel.cpp new file mode 100644 index 000000000..fdcdfa4d4 --- /dev/null +++ b/plugin-taskbar/lxqttaskbarproxymodel.cpp @@ -0,0 +1,257 @@ +#include "lxqttaskbarproxymodel.h" + +#include "../panel/backends/ilxqttaskbarabstractbackend.h" + +#include + +LXQtTaskBarProxyModel::LXQtTaskBarProxyModel(QObject *parent) + : QAbstractListModel(parent) + , m_backend(nullptr) + , m_groupByWindowClass(false) +{ + +} + +int LXQtTaskBarProxyModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : m_items.count(); +} + +QVariant LXQtTaskBarProxyModel::data(const QModelIndex &idx, int role) const +{ + if (!idx.isValid() || idx.row() >= m_items.count()) + return QVariant(); + + const LXQtTaskBarProxyModelItem& item = m_items.at(idx.row()); + + switch (role) + { + case Qt::DisplayRole: + return item.windows.count() == 1 ? item.windows.first().title : item.windowClass; + default: + break; + } + + return QVariant(); +} + +QIcon LXQtTaskBarProxyModel::getWindowIcon(int itemRow, int windowIdxInGroup, int devicePixels) const +{ + if(!m_backend || itemRow < 0 || itemRow >= m_items.size() || windowIdxInGroup < 0) + return QIcon(); + + const LXQtTaskBarProxyModelItem& item = m_items.at(itemRow); + if(windowIdxInGroup >= item.windows.size()) + return QIcon(); + + const LXQtTaskBarProxyModelWindow& window = item.windows.at(windowIdxInGroup); + return m_backend->getApplicationIcon(window.windowId, devicePixels); +} + +void LXQtTaskBarProxyModel::onWindowAdded(WId windowId) +{ + QString windowClass = m_backend->getWindowClass(windowId); + bool willAddRow = !m_groupByWindowClass || !hasWindowClass(windowClass); + + if(willAddRow) + { + const int row = m_items.count(); + beginInsertRows(QModelIndex(), row, row); + } + + addWindow_internal(windowId); + + if(willAddRow) + endInsertRows(); +} + +void LXQtTaskBarProxyModel::onWindowRemoved(WId windowId) +{ + int row = -1; + int windowIdxInGroup = -1; + for(int i = 0; i < m_items.count(); i++) + { + windowIdxInGroup = m_items.at(i).indexOfWindow(windowId); + if(windowIdxInGroup != -1) + { + row = i; + break; + } + } + + if(row == -1) + return; + + LXQtTaskBarProxyModelItem& item = m_items[row]; + item.windows.removeAt(windowIdxInGroup); + + if(item.windows.isEmpty()) + { + // Remove the group + beginRemoveRows(QModelIndex(), row, row); + m_items.removeAt(row); + endRemoveRows(); + } +} + +void LXQtTaskBarProxyModel::onWindowPropertyChanged(WId windowId, int prop) +{ + int row = -1; + int windowIdxInGroup = -1; + for(int i = 0; i < m_items.count(); i++) + { + windowIdxInGroup = m_items.at(i).indexOfWindow(windowId); + if(windowIdxInGroup != -1) + { + row = i; + break; + } + } + + if(row == -1) + return; + + LXQtTaskBarProxyModelItem& item = m_items[row]; + LXQtTaskBarProxyModelWindow& window = item.windows[windowIdxInGroup]; + + switch (LXQtTaskBarWindowProperty(prop)) + { + case LXQtTaskBarWindowProperty::Title: + window.title = m_backend->getWindowTitle(window.windowId); + break; + + case LXQtTaskBarWindowProperty::Urgency: + window.demandsAttention = m_backend->applicationDemandsAttention(window.windowId); + break; + + case LXQtTaskBarWindowProperty::WindowClass: + { + // If window class is changed, window won't be part of same group + //TODO: optimize + onWindowRemoved(windowId); + onWindowAdded(windowId); + } + + default: + break; + } + + const QModelIndex idx = index(row); + emit dataChanged(idx, idx, {Qt::DisplayRole, Qt::DecorationRole}); +} + +void LXQtTaskBarProxyModel::addWindow_internal(WId windowId) +{ + LXQtTaskBarProxyModelWindow window; + window.windowId = windowId; + window.title = m_backend->getWindowTitle(window.windowId); + window.demandsAttention = m_backend->applicationDemandsAttention(window.windowId); + + QString windowClass = m_backend->getWindowClass(window.windowId); + + int row = -1; + if(m_groupByWindowClass) + { + // Find existing group + for(int i = 0; i < m_items.count(); i++) + { + if(m_items.at(i).windowClass == windowClass) + { + row = i; + break; + } + } + } + + if(row == -1) + { + // Create new group + LXQtTaskBarProxyModelItem item; + item.windowClass = windowClass; + m_items.append(item); + row = m_items.size() - 1; + } + + // Add window to group + LXQtTaskBarProxyModelItem& item = m_items[row]; + item.windows.append(window); +} + +bool LXQtTaskBarProxyModel::groupByWindowClass() const +{ + return m_groupByWindowClass; +} + +void LXQtTaskBarProxyModel::setGroupByWindowClass(bool newGroupByWindowClass) +{ + if(m_groupByWindowClass == newGroupByWindowClass) + return; + + m_groupByWindowClass = newGroupByWindowClass; + + if(m_backend && !m_items.isEmpty()) + { + beginResetModel(); + + m_items.clear(); + + // Reload current windows + const QVector windows = m_backend->getCurrentWindows(); + m_items.reserve(windows.size()); + + for(WId windowId : windows) + onWindowAdded(windowId); + + m_items.squeeze(); + + endResetModel(); + } + +} + +ILXQtTaskbarAbstractBackend *LXQtTaskBarProxyModel::backend() const +{ + return m_backend; +} + +void LXQtTaskBarProxyModel::setBackend(ILXQtTaskbarAbstractBackend *newBackend) +{ + beginResetModel(); + + m_items.clear(); + + if(m_backend) + { + disconnect(m_backend, &ILXQtTaskbarAbstractBackend::windowAdded, + this, &LXQtTaskBarProxyModel::onWindowAdded); + disconnect(m_backend, &ILXQtTaskbarAbstractBackend::windowRemoved, + this, &LXQtTaskBarProxyModel::onWindowRemoved); + disconnect(m_backend, &ILXQtTaskbarAbstractBackend::windowPropertyChanged, + this, &LXQtTaskBarProxyModel::onWindowPropertyChanged); + } + + m_backend = newBackend; + + if(m_backend) + { + connect(m_backend, &ILXQtTaskbarAbstractBackend::windowAdded, + this, &LXQtTaskBarProxyModel::onWindowAdded); + connect(m_backend, &ILXQtTaskbarAbstractBackend::windowRemoved, + this, &LXQtTaskBarProxyModel::onWindowRemoved); + connect(m_backend, &ILXQtTaskbarAbstractBackend::windowPropertyChanged, + this, &LXQtTaskBarProxyModel::onWindowPropertyChanged); + + // Reload current windows + const QVector windows = m_backend->getCurrentWindows(); + m_items.reserve(windows.size()); + + for(WId windowId : windows) + onWindowAdded(windowId); + + m_items.squeeze(); + } + + m_items.squeeze(); + + endResetModel(); +} diff --git a/plugin-taskbar/lxqttaskbarproxymodel.h b/plugin-taskbar/lxqttaskbarproxymodel.h new file mode 100644 index 000000000..8bbb5ec49 --- /dev/null +++ b/plugin-taskbar/lxqttaskbarproxymodel.h @@ -0,0 +1,100 @@ +#ifndef LXQTTASKBARPROXYMODEL_H +#define LXQTTASKBARPROXYMODEL_H + +#include +#include + +#include "../panel/backends/lxqttaskbartypes.h" + +class ILXQtTaskbarAbstractBackend; + +class LXQtTaskBarProxyModelWindow +{ +public: + LXQtTaskBarProxyModelWindow() = default; + + WId windowId; + QString title; + bool demandsAttention = false; +}; + +// Single window or group +class LXQtTaskBarProxyModelItem +{ +public: + LXQtTaskBarProxyModelItem() = default; + + QVector windows; + QString windowClass; + + inline bool demandsAttention() const + { + for(const LXQtTaskBarProxyModelWindow& w : windows) + { + if(w.demandsAttention) + return true; + } + + return false; + } + + int indexOfWindow(WId windowId) const + { + for(int i = 0; i < windows.size(); i++) + { + if(windows.at(i).windowId == windowId) + return i; + } + + return -1; + } +}; + +class LXQtTaskBarProxyModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit LXQtTaskBarProxyModel(QObject *parent = nullptr); + + // Basic functionality: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override; + + QIcon getWindowIcon(int itemRow, int windowIdxInGroup, int devicePixels) const; + + ILXQtTaskbarAbstractBackend *backend() const; + void setBackend(ILXQtTaskbarAbstractBackend *newBackend); + + bool groupByWindowClass() const; + void setGroupByWindowClass(bool newGroupByWindowClass); + +private slots: + void onWindowAdded(WId windowId); + void onWindowRemoved(WId windowId); + void onWindowPropertyChanged(WId windowId, int prop); + +private: + void addWindow_internal(WId windowId); + + inline bool hasWindowClass(const QString& windowClass) const + { + for(const LXQtTaskBarProxyModelItem& item : m_items) + { + if(item.windowClass == windowClass) + return true; + } + + return false; + } + +private: + ILXQtTaskbarAbstractBackend *m_backend; + + QVector m_items; + + bool m_groupByWindowClass; +}; + +#endif // LXQTTASKBARPROXYMODEL_H diff --git a/plugin-taskbar/lxqttaskbutton.cpp b/plugin-taskbar/lxqttaskbutton.cpp index bd6df460f..c86e6af5f 100644 --- a/plugin-taskbar/lxqttaskbutton.cpp +++ b/plugin-taskbar/lxqttaskbutton.cpp @@ -28,9 +28,10 @@ * END_COMMON_COPYRIGHT_HEADER */ #include "lxqttaskbutton.h" -#include "lxqttaskgroup.h" #include "lxqttaskbar.h" +#include "../panel/ilxqtpanelplugin.h" + #include #include @@ -49,17 +50,9 @@ #include #include -#include "lxqttaskbutton.h" -#include "lxqttaskgroup.h" -#include "lxqttaskbar.h" +#include "../panel/backends/ilxqttaskbarabstractbackend.h" -#include -// Necessary for closeApplication() -#include -//NOTE: Xlib.h defines Bool which conflicts with QJsonValue::Type enum -#include -#undef Bool bool LXQtTaskButton::sDraggging = false; @@ -84,6 +77,7 @@ void LeftAlignedTextStyle::drawItemText(QPainter * painter, const QRect & rect, ************************************************/ LXQtTaskButton::LXQtTaskButton(const WId window, LXQtTaskBar * taskbar, QWidget *parent) : QToolButton(parent), + mBackend(taskbar->getBackend()), mWindow(window), mUrgencyHint(false), mOrigin(Qt::TopLeftCorner), @@ -117,18 +111,7 @@ LXQtTaskButton::LXQtTaskButton(const WId window, LXQtTaskBar * taskbar, QWidget mWheelDelta = 0; // forget previous wheel deltas }); - auto *x11Application = qGuiApp->nativeInterface(); - if(x11Application) - { - WId appRootWindow = XDefaultRootWindow(x11Application->display()); - setUrgencyHint(NETWinInfo(x11Application->connection(), mWindow, appRootWindow, NET::Properties{}, NET::WM2Urgency).urgency() - || KWindowInfo{mWindow, NET::WMState}.hasState(NET::DemandsAttention)); - } - else - { - qWarning() << "LXQtTaskBar: not implemented on Wayland"; - } - + setUrgencyHint(mBackend->applicationDemandsAttention(mWindow)); connect(LXQt::Settings::globalSettings(), &LXQt::GlobalSettings::iconThemeChanged, this, &LXQtTaskButton::updateIcon); connect(mParentTaskBar, &LXQtTaskBar::iconByClassChanged, this, &LXQtTaskButton::updateIcon); @@ -144,8 +127,7 @@ LXQtTaskButton::~LXQtTaskButton() = default; ************************************************/ void LXQtTaskButton::updateText() { - KWindowInfo info(mWindow, NET::WMVisibleName | NET::WMName); - QString title = info.visibleName().isEmpty() ? info.name() : info.visibleName(); + QString title = mBackend->getWindowTitle(mWindow); setText(title.replace(QStringLiteral("&"), QStringLiteral("&&"))); setToolTip(title); } @@ -158,67 +140,16 @@ void LXQtTaskButton::updateIcon() QIcon ico; if (mParentTaskBar->isIconByClass()) { - ico = XdgIcon::fromTheme(QString::fromUtf8(KWindowInfo{mWindow, NET::Properties(), NET::WM2WindowClass}.windowClassClass()).toLower()); + ico = XdgIcon::fromTheme(mBackend->getWindowClass(mWindow).toLower()); } if (ico.isNull()) { int devicePixels = mIconSize * devicePixelRatioF(); - ico = KX11Extras::icon(mWindow, devicePixels, devicePixels); + ico = mBackend->getApplicationIcon(mWindow, devicePixels); } setIcon(ico.isNull() ? XdgIcon::defaultApplicationIcon() : ico); } -/************************************************ - - ************************************************/ -void LXQtTaskButton::refreshIconGeometry(QRect const & geom) -{ - // NOTE: This function announces where the task icon is, - // such that X11 WMs can perform their related animations correctly. - - WId appRootWindow = 0; - xcb_connection_t* x11conn = nullptr; - - auto *x11Application = qGuiApp->nativeInterface(); - if(x11Application) - { - appRootWindow = XDefaultRootWindow(x11Application->display()); - x11conn = x11Application->connection(); - } - else - { - //qWarning() << "LXQtTaskBar: not implemented on Wayland"; - return; - } - - - if (!x11conn) { - return; - } - - NETWinInfo info(x11conn, - windowId(), - appRootWindow, - NET::WMIconGeometry, - NET::Properties2()); - NETRect const curr = info.iconGeometry(); - - // see kwindowsystem -> NETWinInfo::setIconGeometry for the scale factor - const qreal scaleFactor = qApp->devicePixelRatio(); - int xPos = geom.x() * scaleFactor; - int yPos = geom.y() * scaleFactor; - int w = geom.width() * scaleFactor; - int h = geom.height() * scaleFactor; - if (xPos == curr.pos.x && yPos == curr.pos.y && w == curr.size.width && h == curr.size.height) - return; - NETRect nrect; - nrect.pos.x = geom.x(); - nrect.pos.y = geom.y(); - nrect.size.height = geom.height(); - nrect.size.width = geom.width(); - info.setIconGeometry(nrect); -} - /************************************************ ************************************************/ @@ -434,8 +365,7 @@ void LXQtTaskButton::mouseMoveEvent(QMouseEvent* event) ************************************************/ bool LXQtTaskButton::isApplicationHidden() const { - KWindowInfo info(mWindow, NET::WMState); - return (info.state() & NET::Hidden); + return false; //FIXME: unused } /************************************************ @@ -443,7 +373,7 @@ bool LXQtTaskButton::isApplicationHidden() const ************************************************/ bool LXQtTaskButton::isApplicationActive() const { - return KX11Extras::activeWindow() == mWindow; + return mBackend->isWindowActive(mWindow); } /************************************************ @@ -451,21 +381,7 @@ bool LXQtTaskButton::isApplicationActive() const ************************************************/ void LXQtTaskButton::raiseApplication() { - KWindowInfo info(mWindow, NET::WMDesktop | NET::WMState | NET::XAWMState); - if (parentTaskBar()->raiseOnCurrentDesktop() && info.isMinimized()) - { - KX11Extras::setOnDesktop(mWindow, KX11Extras::currentDesktop()); - } - else - { - int winDesktop = info.desktop(); - if (KX11Extras::currentDesktop() != winDesktop) - KX11Extras::setCurrentDesktop(winDesktop); - } - // bypass focus stealing prevention - KX11Extras::forceActiveWindow(mWindow); - - setUrgencyHint(false); + mBackend->raiseWindow(mWindow, parentTaskBar()->raiseOnCurrentDesktop()); } /************************************************ @@ -473,7 +389,7 @@ void LXQtTaskButton::raiseApplication() ************************************************/ void LXQtTaskButton::minimizeApplication() { - KX11Extras::minimizeWindow(mWindow); + mBackend->setWindowState(mWindow, LXQtTaskBarWindowState::Minimized, true); } /************************************************ @@ -486,23 +402,10 @@ void LXQtTaskButton::maximizeApplication() return; int state = act->data().toInt(); - switch (state) - { - case NET::MaxHoriz: - KX11Extras::setState(mWindow, NET::MaxHoriz); - break; - - case NET::MaxVert: - KX11Extras::setState(mWindow, NET::MaxVert); - break; + mBackend->setWindowState(mWindow, LXQtTaskBarWindowState(state), true); - default: - KX11Extras::setState(mWindow, NET::Max); - break; - } - - if (!isApplicationActive()) - raiseApplication(); + if(!mBackend->isWindowActive(mWindow)) + mBackend->raiseWindow(mWindow, parentTaskBar()->raiseOnCurrentDesktop()); } /************************************************ @@ -510,10 +413,10 @@ void LXQtTaskButton::maximizeApplication() ************************************************/ void LXQtTaskButton::deMaximizeApplication() { - KX11Extras::clearState(mWindow, NET::Max); + mBackend->setWindowState(mWindow, LXQtTaskBarWindowState::Maximized, false); - if (!isApplicationActive()) - raiseApplication(); + if(!mBackend->isWindowActive(mWindow)) + mBackend->raiseWindow(mWindow, parentTaskBar()->raiseOnCurrentDesktop()); } /************************************************ @@ -521,7 +424,7 @@ void LXQtTaskButton::deMaximizeApplication() ************************************************/ void LXQtTaskButton::shadeApplication() { - KX11Extras::setState(mWindow, NET::Shaded); + mBackend->setWindowState(mWindow, LXQtTaskBarWindowState::RolledUp, true); } /************************************************ @@ -529,7 +432,7 @@ void LXQtTaskButton::shadeApplication() ************************************************/ void LXQtTaskButton::unShadeApplication() { - KX11Extras::clearState(mWindow, NET::Shaded); + mBackend->setWindowState(mWindow, LXQtTaskBarWindowState::RolledUp, false); } /************************************************ @@ -537,16 +440,7 @@ void LXQtTaskButton::unShadeApplication() ************************************************/ void LXQtTaskButton::closeApplication() { - auto *x11Application = qGuiApp->nativeInterface(); - if(x11Application) - { - // FIXME: Why there is no such thing in KX11Extras?? - NETRootInfo(x11Application->connection(), NET::CloseWindow).closeWindowRequest(mWindow); - } - else - { - qWarning() << "LXQtTaskBar: not implemented on Wayland"; - } + mBackend->closeWindow(mWindow); } /************************************************ @@ -559,23 +453,7 @@ void LXQtTaskButton::setApplicationLayer() return; int layer = act->data().toInt(); - switch(layer) - { - case NET::KeepAbove: - KX11Extras::clearState(mWindow, NET::KeepBelow); - KX11Extras::setState(mWindow, NET::KeepAbove); - break; - - case NET::KeepBelow: - KX11Extras::clearState(mWindow, NET::KeepAbove); - KX11Extras::setState(mWindow, NET::KeepBelow); - break; - - default: - KX11Extras::clearState(mWindow, NET::KeepBelow); - KX11Extras::clearState(mWindow, NET::KeepAbove); - break; - } + mBackend->setWindowLayer(mWindow, LXQtTaskBarWindowLayer(layer)); } /************************************************ @@ -588,12 +466,12 @@ void LXQtTaskButton::moveApplicationToDesktop() return; bool ok; - int desk = act->data().toInt(&ok); + int idx = act->data().toInt(&ok); if (!ok) return; - KX11Extras::setOnDesktop(mWindow, desk); + mBackend->setWindowOnWorkspace(mWindow, idx); } /************************************************ @@ -601,17 +479,7 @@ void LXQtTaskButton::moveApplicationToDesktop() ************************************************/ void LXQtTaskButton::moveApplicationToPrevNextDesktop(bool next) { - int deskNum = KX11Extras::numberOfDesktops(); - if (deskNum <= 1) - return; - int targetDesk = KWindowInfo(mWindow, NET::WMDesktop).desktop() + (next ? 1 : -1); - // wrap around - if (targetDesk > deskNum) - targetDesk = 1; - else if (targetDesk < 1) - targetDesk = deskNum; - - KX11Extras::setOnDesktop(mWindow, targetDesk); + mBackend->moveApplicationToPrevNextDesktop(mWindow, next); } /************************************************ @@ -619,53 +487,7 @@ void LXQtTaskButton::moveApplicationToPrevNextDesktop(bool next) ************************************************/ void LXQtTaskButton::moveApplicationToPrevNextMonitor(bool next) { - KWindowInfo info(mWindow, NET::WMDesktop); - if (!info.isOnCurrentDesktop()) - KX11Extras::setCurrentDesktop(info.desktop()); - if (isMinimized()) - KX11Extras::unminimizeWindow(mWindow); - KX11Extras::forceActiveWindow(mWindow); - const QRect& windowGeometry = KWindowInfo(mWindow, NET::WMFrameExtents).frameGeometry(); - QList screens = QGuiApplication::screens(); - if (screens.size() > 1){ - for (int i = 0; i < screens.size(); ++i) - { - QRect screenGeometry = screens[i]->geometry(); - if (screenGeometry.intersects(windowGeometry)) - { - int targetScreen = i + (next ? 1 : -1); - if (targetScreen < 0) - targetScreen += screens.size(); - else if (targetScreen >= screens.size()) - targetScreen -= screens.size(); - QRect targetScreenGeometry = screens[targetScreen]->geometry(); - int X = windowGeometry.x() - screenGeometry.x() + targetScreenGeometry.x(); - int Y = windowGeometry.y() - screenGeometry.y() + targetScreenGeometry.y(); - NET::States state = KWindowInfo(mWindow, NET::WMState).state(); - // NW geometry | y/x | from panel - const int flags = 1 | (0b011 << 8) | (0b010 << 12); - KX11Extras::clearState(mWindow, NET::MaxHoriz | NET::MaxVert | NET::Max | NET::FullScreen); - - - auto *x11Application = qGuiApp->nativeInterface(); - if(x11Application) - { - NETRootInfo(x11Application->connection(), NET::Properties(), NET::WM2MoveResizeWindow).moveResizeWindowRequest(mWindow, flags, X, Y, 0, 0); - } - else - { - //qWarning() << "LXQtTaskBar: not implemented on Wayland"; - } - - QTimer::singleShot(200, this, [this, state] - { - KX11Extras::setState(mWindow, state); - raiseApplication(); - }); - break; - } - } - } + mBackend->moveApplicationToPrevNextMonitor(mWindow, next, parentTaskBar()->raiseOnCurrentDesktop()); } /************************************************ @@ -673,26 +495,7 @@ void LXQtTaskButton::moveApplicationToPrevNextMonitor(bool next) ************************************************/ void LXQtTaskButton::moveApplication() { - KWindowInfo info(mWindow, NET::WMDesktop); - if (!info.isOnCurrentDesktop()) - KX11Extras::setCurrentDesktop(info.desktop()); - if (isMinimized()) - KX11Extras::unminimizeWindow(mWindow); - KX11Extras::forceActiveWindow(mWindow); - const QRect& g = KWindowInfo(mWindow, NET::WMGeometry).geometry(); - int X = g.center().x(); - int Y = g.center().y(); - QCursor::setPos(X, Y); - - auto *x11Application = qGuiApp->nativeInterface(); - if(x11Application) - { - NETRootInfo(x11Application->connection(), NET::WMMoveResize).moveResizeRequest(mWindow, X, Y, NET::Move); - } - else - { - //qWarning() << "LXQtTaskBar: not implemented on Wayland"; - } + mBackend->moveApplication(mWindow); } /************************************************ @@ -700,26 +503,7 @@ void LXQtTaskButton::moveApplication() ************************************************/ void LXQtTaskButton::resizeApplication() { - KWindowInfo info(mWindow, NET::WMDesktop); - if (!info.isOnCurrentDesktop()) - KX11Extras::setCurrentDesktop(info.desktop()); - if (isMinimized()) - KX11Extras::unminimizeWindow(mWindow); - KX11Extras::forceActiveWindow(mWindow); - const QRect& g = KWindowInfo(mWindow, NET::WMGeometry).geometry(); - int X = g.bottomRight().x(); - int Y = g.bottomRight().y(); - QCursor::setPos(X, Y); - - auto *x11Application = qGuiApp->nativeInterface(); - if(x11Application) - { - NETRootInfo(x11Application->connection(), NET::WMMoveResize).moveResizeRequest(mWindow, X, Y, NET::BottomRight); - } - else - { - //qWarning() << "LXQtTaskBar: not implemented on Wayland"; - } + mBackend->resizeApplication(mWindow); } /************************************************ @@ -733,10 +517,9 @@ void LXQtTaskButton::contextMenuEvent(QContextMenuEvent* event) return; } - KWindowInfo info(mWindow, NET::Properties(), NET::WM2AllowedActions); - unsigned long state = KWindowInfo(mWindow, NET::WMState).state(); + const LXQtTaskBarWindowState state = mBackend->getWindowState(mWindow); - QMenu * menu = new QMenu(tr("Application")); + QMenu * menu = new QMenu(tr("Application"), this); menu->setAttribute(Qt::WA_DeleteOnClose); QAction* a; @@ -766,21 +549,21 @@ void LXQtTaskButton::contextMenuEvent(QContextMenuEvent* event) */ /********** Desktop menu **********/ - int deskNum = KX11Extras::numberOfDesktops(); + int deskNum = mBackend->getWorkspacesCount(); if (deskNum > 1) { - int winDesk = KWindowInfo(mWindow, NET::WMDesktop).desktop(); + int winDesk = mBackend->getWindowWorkspace(mWindow); QMenu* deskMenu = menu->addMenu(tr("To &Desktop")); a = deskMenu->addAction(tr("&All Desktops")); - a->setData(NET::OnAllDesktops); - a->setEnabled(winDesk != NET::OnAllDesktops); + a->setData(int(LXQtTaskBarWorkspace::ShowOnAll)); + a->setEnabled(winDesk != int(LXQtTaskBarWorkspace::ShowOnAll)); connect(a, &QAction::triggered, this, &LXQtTaskButton::moveApplicationToDesktop); deskMenu->addSeparator(); for (int i = 1; i <= deskNum; ++i) { - auto deskName = KX11Extras::desktopName(i).trimmed(); + auto deskName = mBackend->getWorkspaceName(i).trimmed(); if (deskName.isEmpty()) a = deskMenu->addAction(tr("Desktop &%1").arg(i)); else @@ -791,7 +574,7 @@ void LXQtTaskButton::contextMenuEvent(QContextMenuEvent* event) connect(a, &QAction::triggered, this, &LXQtTaskButton::moveApplicationToDesktop); } - int curDesk = KX11Extras::currentDesktop(); + int curDesk = mBackend->getCurrentWorkspace(); a = menu->addAction(tr("&To Current Desktop")); a->setData(curDesk); a->setEnabled(curDesk != winDesk); @@ -803,57 +586,79 @@ void LXQtTaskButton::contextMenuEvent(QContextMenuEvent* event) menu->addSeparator(); a = menu->addAction(tr("Move To N&ext Monitor")); connect(a, &QAction::triggered, this, [this] { moveApplicationToPrevNextMonitor(true); }); - a->setEnabled(info.actionSupported(NET::ActionMove) && (!(state & NET::FullScreen) || ((state & NET::FullScreen) && info.actionSupported(NET::ActionFullScreen)))); + a->setEnabled(mBackend->supportsAction(mWindow, LXQtTaskBarBackendAction::Move) && + (state != LXQtTaskBarWindowState::FullScreen + || ((state == LXQtTaskBarWindowState::FullScreen) && mBackend->supportsAction(mWindow, LXQtTaskBarBackendAction::FullScreen)))); a = menu->addAction(tr("Move To &Previous Monitor")); connect(a, &QAction::triggered, this, [this] { moveApplicationToPrevNextMonitor(false); }); } + menu->addSeparator(); a = menu->addAction(tr("&Move")); - a->setEnabled(info.actionSupported(NET::ActionMove) && !(state & NET::Max) && !(state & NET::FullScreen)); + a->setEnabled(mBackend->supportsAction(mWindow, LXQtTaskBarBackendAction::Move) + && state != LXQtTaskBarWindowState::Maximized + && state != LXQtTaskBarWindowState::FullScreen); connect(a, &QAction::triggered, this, &LXQtTaskButton::moveApplication); a = menu->addAction(tr("Resi&ze")); - a->setEnabled(info.actionSupported(NET::ActionResize) && !(state & NET::Max) && !(state & NET::FullScreen)); + a->setEnabled(mBackend->supportsAction(mWindow, LXQtTaskBarBackendAction::Resize) + && state != LXQtTaskBarWindowState::Maximized + && state != LXQtTaskBarWindowState::FullScreen); connect(a, &QAction::triggered, this, &LXQtTaskButton::resizeApplication); /********** State menu **********/ menu->addSeparator(); a = menu->addAction(tr("Ma&ximize")); - a->setEnabled(info.actionSupported(NET::ActionMax) && (!(state & NET::Max) || (state & NET::Hidden))); - a->setData(NET::Max); + a->setEnabled(mBackend->supportsAction(mWindow, LXQtTaskBarBackendAction::Maximize) + && state != LXQtTaskBarWindowState::Maximized + && state != LXQtTaskBarWindowState::Hidden); + a->setData(int(LXQtTaskBarWindowState::Maximized)); connect(a, &QAction::triggered, this, &LXQtTaskButton::maximizeApplication); if (event->modifiers() & Qt::ShiftModifier) { a = menu->addAction(tr("Maximize vertically")); - a->setEnabled(info.actionSupported(NET::ActionMaxVert) && !((state & NET::MaxVert) || (state & NET::Hidden))); - a->setData(NET::MaxVert); + a->setEnabled(mBackend->supportsAction(mWindow, LXQtTaskBarBackendAction::MaximizeVertically) + && state != LXQtTaskBarWindowState::MaximizedVertically + && state != LXQtTaskBarWindowState::Hidden); + a->setData(int(LXQtTaskBarWindowState::MaximizedVertically)); connect(a, &QAction::triggered, this, &LXQtTaskButton::maximizeApplication); a = menu->addAction(tr("Maximize horizontally")); - a->setEnabled(info.actionSupported(NET::ActionMaxHoriz) && !((state & NET::MaxHoriz) || (state & NET::Hidden))); - a->setData(NET::MaxHoriz); + a->setEnabled(mBackend->supportsAction(mWindow, LXQtTaskBarBackendAction::MaximizeHorizontally) + && state != LXQtTaskBarWindowState::MaximizedHorizontally + && state != LXQtTaskBarWindowState::Hidden); + a->setData(int(LXQtTaskBarWindowState::MaximizedHorizontally)); connect(a, &QAction::triggered, this, &LXQtTaskButton::maximizeApplication); } a = menu->addAction(tr("&Restore")); - a->setEnabled((state & NET::Hidden) || (state & NET::Max) || (state & NET::MaxHoriz) || (state & NET::MaxVert)); + a->setEnabled(state == LXQtTaskBarWindowState::Hidden + || state == LXQtTaskBarWindowState::Minimized + || state == LXQtTaskBarWindowState::Maximized + || state == LXQtTaskBarWindowState::MaximizedVertically + || state == LXQtTaskBarWindowState::MaximizedHorizontally); connect(a, &QAction::triggered, this, &LXQtTaskButton::deMaximizeApplication); a = menu->addAction(tr("Mi&nimize")); - a->setEnabled(info.actionSupported(NET::ActionMinimize) && !(state & NET::Hidden)); + a->setEnabled(mBackend->supportsAction(mWindow, LXQtTaskBarBackendAction::Minimize) + && state != LXQtTaskBarWindowState::Hidden + && state != LXQtTaskBarWindowState::Minimized); connect(a, &QAction::triggered, this, &LXQtTaskButton::minimizeApplication); - if (state & NET::Shaded) + if (state == LXQtTaskBarWindowState::RolledUp) { a = menu->addAction(tr("Roll down")); - a->setEnabled(info.actionSupported(NET::ActionShade) && !(state & NET::Hidden)); + a->setEnabled(mBackend->supportsAction(mWindow, LXQtTaskBarBackendAction::RollUp) + && state != LXQtTaskBarWindowState::Hidden + && state != LXQtTaskBarWindowState::Minimized); connect(a, &QAction::triggered, this, &LXQtTaskButton::unShadeApplication); } else { a = menu->addAction(tr("Roll up")); - a->setEnabled(info.actionSupported(NET::ActionShade) && !(state & NET::Hidden)); + a->setEnabled(mBackend->supportsAction(mWindow, LXQtTaskBarBackendAction::RollUp) + && state != LXQtTaskBarWindowState::Hidden); connect(a, &QAction::triggered, this, &LXQtTaskButton::shadeApplication); } @@ -862,22 +667,21 @@ void LXQtTaskButton::contextMenuEvent(QContextMenuEvent* event) QMenu* layerMenu = menu->addMenu(tr("&Layer")); + LXQtTaskBarWindowLayer currentLayer = mBackend->getWindowLayer(mWindow); + a = layerMenu->addAction(tr("Always on &top")); - // FIXME: There is no info.actionSupported(NET::ActionKeepAbove) - a->setEnabled(!(state & NET::KeepAbove)); - a->setData(NET::KeepAbove); + a->setEnabled(currentLayer != LXQtTaskBarWindowLayer::KeepAbove); + a->setData(int(LXQtTaskBarWindowLayer::KeepAbove)); connect(a, &QAction::triggered, this, &LXQtTaskButton::setApplicationLayer); a = layerMenu->addAction(tr("&Normal")); - a->setEnabled((state & NET::KeepAbove) || (state & NET::KeepBelow)); - // FIXME: There is no NET::KeepNormal, so passing 0 - a->setData(0); + a->setEnabled(currentLayer != LXQtTaskBarWindowLayer::Normal); + a->setData(int(LXQtTaskBarWindowLayer::Normal)); connect(a, &QAction::triggered, this, &LXQtTaskButton::setApplicationLayer); a = layerMenu->addAction(tr("Always on &bottom")); - // FIXME: There is no info.actionSupported(NET::ActionKeepBelow) - a->setEnabled(!(state & NET::KeepBelow)); - a->setData(NET::KeepBelow); + a->setEnabled(currentLayer != LXQtTaskBarWindowLayer::KeepBelow); + a->setData(int(LXQtTaskBarWindowLayer::KeepBelow)); connect(a, &QAction::triggered, this, &LXQtTaskButton::setApplicationLayer); /********** Kill menu **********/ @@ -909,21 +713,18 @@ void LXQtTaskButton::setUrgencyHint(bool set) ************************************************/ bool LXQtTaskButton::isOnDesktop(int desktop) const { - return KWindowInfo(mWindow, NET::WMDesktop).isOnDesktop(desktop); + return mBackend->getWindowWorkspace(mWindow) == desktop; } bool LXQtTaskButton::isOnCurrentScreen() const { QScreen *screen = parentTaskBar()->screen(); - QRect screenGeo = screen->geometry(); - QRect windowGeo = KWindowInfo(mWindow, NET::WMFrameExtents).frameGeometry(); - - return screenGeo.intersects(windowGeo); + return mBackend->isWindowOnScreen(screen, mWindow); } bool LXQtTaskButton::isMinimized() const { - return KWindowInfo(mWindow,NET::WMState | NET::XAWMState).isMinimized(); + return mBackend->getWindowState(mWindow) == LXQtTaskBarWindowState::Minimized; } Qt::Corner LXQtTaskButton::origin() const diff --git a/plugin-taskbar/lxqttaskbutton.h b/plugin-taskbar/lxqttaskbutton.h index 69f3b41d8..9ccca36fa 100644 --- a/plugin-taskbar/lxqttaskbutton.h +++ b/plugin-taskbar/lxqttaskbutton.h @@ -33,14 +33,16 @@ #include #include + #include "../panel/ilxqtpanel.h" class QPainter; class QPalette; class QMimeData; -class LXQtTaskGroup; class LXQtTaskBar; +class ILXQtTaskbarAbstractBackend; + class LeftAlignedTextStyle : public QProxyStyle { using QProxyStyle::QProxyStyle; @@ -79,7 +81,6 @@ class LXQtTaskButton : public QToolButton LXQtTaskBar * parentTaskBar() const {return mParentTaskBar;} - void refreshIconGeometry(QRect const & geom); static QString mimeDataFormat() { return QLatin1String("lxqt/lxqttaskbutton"); } /*! \return true if this button received DragEnter event (and no DragLeave event yet) * */ @@ -121,6 +122,10 @@ public slots: inline ILXQtPanelPlugin * plugin() const { return mPlugin; } +protected: + //TODO: public getter instead? + ILXQtTaskbarAbstractBackend *mBackend; + private: void moveApplicationToPrevNextDesktop(bool next); void moveApplicationToPrevNextMonitor(bool next); diff --git a/plugin-taskbar/lxqttaskgroup.cpp b/plugin-taskbar/lxqttaskgroup.cpp index e192baabf..8317c034f 100644 --- a/plugin-taskbar/lxqttaskgroup.cpp +++ b/plugin-taskbar/lxqttaskgroup.cpp @@ -32,6 +32,8 @@ #include "lxqttaskbar.h" #include "lxqtgrouppopup.h" +#include "../panel/ilxqtpanelplugin.h" + #include #include #include @@ -39,11 +41,8 @@ #include #include #include -#include -#include //For nativeInterface() -#include -#include +#include "../panel/backends/ilxqttaskbarabstractbackend.h" /************************************************ @@ -60,14 +59,14 @@ LXQtTaskGroup::LXQtTaskGroup(const QString &groupName, WId window, LXQtTaskBar * setObjectName(groupName); setText(groupName); - connect(this, &LXQtTaskGroup::clicked, this, &LXQtTaskGroup::onClicked); - connect(KX11Extras::self(), &KX11Extras::currentDesktopChanged, this, &LXQtTaskGroup::onDesktopChanged); - connect(KX11Extras::self(), &KX11Extras::activeWindowChanged, this, &LXQtTaskGroup::onActiveWindowChanged); - connect(parent, &LXQtTaskBar::buttonRotationRefreshed, this, &LXQtTaskGroup::setAutoRotation); - connect(parent, &LXQtTaskBar::refreshIconGeometry, this, &LXQtTaskGroup::refreshIconsGeometry); - connect(parent, &LXQtTaskBar::buttonStyleRefreshed, this, &LXQtTaskGroup::setToolButtonsStyle); - connect(parent, &LXQtTaskBar::showOnlySettingChanged, this, &LXQtTaskGroup::refreshVisibility); - connect(parent, &LXQtTaskBar::popupShown, this, &LXQtTaskGroup::groupPopupShown); + connect(this, &LXQtTaskGroup::clicked, this, &LXQtTaskGroup::onClicked); + connect(parent, &LXQtTaskBar::buttonRotationRefreshed, this, &LXQtTaskGroup::setAutoRotation); + connect(parent, &LXQtTaskBar::refreshIconGeometry, this, &LXQtTaskGroup::refreshIconsGeometry); + 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); } /************************************************ @@ -100,7 +99,7 @@ void LXQtTaskGroup::contextMenuEvent(QContextMenuEvent *event) void LXQtTaskGroup::closeGroup() { for (LXQtTaskButton *button : std::as_const(mButtonHash) ) - if (button->isOnDesktop(KX11Extras::currentDesktop())) + if (button->isOnDesktop(mBackend->getCurrentWorkspace())) button->closeApplication(); } @@ -308,7 +307,7 @@ void LXQtTaskGroup::onClicked(bool) { if (visibleButtonsCount() > 1) { - setChecked(mButtonHash.contains(KX11Extras::activeWindow())); + setChecked(mButtonHash.contains(mBackend->getActiveWindow())); setPopupVisible(true); } } @@ -387,7 +386,7 @@ void LXQtTaskGroup::refreshVisibility() const int showDesktop = taskbar->showDesktopNum(); for(LXQtTaskButton * btn : std::as_const(mButtonHash)) { - bool visible = taskbar->isShowOnlyOneDesktopTasks() ? btn->isOnDesktop(0 == showDesktop ? KX11Extras::currentDesktop() : showDesktop) : true; + bool visible = taskbar->isShowOnlyOneDesktopTasks() ? btn->isOnDesktop(0 == showDesktop ? mBackend->getCurrentWorkspace() : showDesktop) : true; visible &= taskbar->isShowOnlyCurrentScreenTasks() ? btn->isOnCurrentScreen() : true; visible &= taskbar->isShowOnlyMinimizedTasks() ? btn->isMinimized() : true; btn->setVisible(visible); @@ -447,13 +446,13 @@ void LXQtTaskGroup::refreshIconsGeometry() if (mSingleButton) { - refreshIconGeometry(rect); + mBackend->refreshIconGeometry(windowId(), rect); return; } for(LXQtTaskButton *but : std::as_const(mButtonHash)) { - but->refreshIconGeometry(rect); + mBackend->refreshIconGeometry(but->windowId(), rect); but->setIconSize(QSize(plugin()->panel()->iconSize(), plugin()->panel()->iconSize())); } } @@ -617,8 +616,10 @@ void LXQtTaskGroup::wheelEvent(QWheelEvent* event) /************************************************ ************************************************/ -bool LXQtTaskGroup::onWindowChanged(WId window, NET::Properties prop, NET::Properties2 prop2) -{ // returns true if the class is preserved +bool LXQtTaskGroup::onWindowChanged(WId window, LXQtTaskBarWindowProperty prop) +{ + // Returns true if the class is preserved + bool needsRefreshVisibility{false}; QList buttons; if (mButtonHash.contains(window)) @@ -631,66 +632,48 @@ bool LXQtTaskGroup::onWindowChanged(WId window, NET::Properties prop, NET::Prope if (!buttons.isEmpty()) { // if class is changed the window won't belong to our group any more - if (parentTaskBar()->isGroupingEnabled() && prop2.testFlag(NET::WM2WindowClass)) + if (parentTaskBar()->isGroupingEnabled() && prop == LXQtTaskBarWindowProperty::WindowClass) { - KWindowInfo info(window, NET::Properties(), NET::WM2WindowClass); - if (QString::fromUtf8(info.windowClassClass()) != mGroupName) + if (mBackend->getWindowClass(windowId()) != mGroupName) { onWindowRemoved(window); return false; } } // window changed virtual desktop - if (prop.testFlag(NET::WMDesktop) || prop.testFlag(NET::WMGeometry)) + if (prop == LXQtTaskBarWindowProperty::Workspace) { if (parentTaskBar()->isShowOnlyOneDesktopTasks() - || parentTaskBar()->isShowOnlyCurrentScreenTasks()) + || parentTaskBar()->isShowOnlyCurrentScreenTasks()) { needsRefreshVisibility = true; } } - if (prop.testFlag(NET::WMVisibleName) || prop.testFlag(NET::WMName)) + if (prop == LXQtTaskBarWindowProperty::Title) std::for_each(buttons.begin(), buttons.end(), std::mem_fn(&LXQtTaskButton::updateText)); // XXX: we are setting window icon geometry -> don't need to handle NET::WMIconGeometry // Icon of the button can be based on windowClass - if (prop.testFlag(NET::WMIcon) || prop2.testFlag(NET::WM2WindowClass)) + if (prop == LXQtTaskBarWindowProperty::Icon) std::for_each(buttons.begin(), buttons.end(), std::mem_fn(&LXQtTaskButton::updateIcon)); bool set_urgency = false; bool urgency = false; - if (prop2.testFlag(NET::WM2Urgency)) + if (prop == LXQtTaskBarWindowProperty::Urgency) { set_urgency = true; - if (auto *x11Application = qGuiApp->nativeInterface()) - { - WId appRootWindow = XDefaultRootWindow(x11Application->display()); - urgency = NETWinInfo(x11Application->connection(), window, appRootWindow, NET::Properties{}, NET::WM2Urgency).urgency(); - } + //FIXME: original code here did not consider "demand attention", was it intentional? + urgency = mBackend->applicationDemandsAttention(window); } - if (prop.testFlag(NET::WMState)) + if (prop == LXQtTaskBarWindowProperty::State) { - KWindowInfo info{window, NET::WMState}; - - if(!set_urgency) - { - if (auto *x11Application = qGuiApp->nativeInterface()) - { - WId appRootWindow = XDefaultRootWindow(x11Application->display()); - urgency = NETWinInfo(x11Application->connection(), window, appRootWindow, NET::Properties{}, NET::WM2Urgency).urgency(); - } - } - - // Force refresh urgency - //TODO: maybe do it in common place with NET::WM2Urgency - std::for_each(buttons.begin(), buttons.end(), std::bind(&LXQtTaskButton::setUrgencyHint, std::placeholders::_1, urgency || info.hasState(NET::DemandsAttention))); + if (!set_urgency) + urgency = mBackend->applicationDemandsAttention(window); + std::for_each(buttons.begin(), buttons.end(), std::bind(&LXQtTaskButton::setUrgencyHint, std::placeholders::_1, urgency)); set_urgency = false; - if (info.hasState(NET::SkipTaskbar)) - onWindowRemoved(window); - if (parentTaskBar()->isShowOnlyMinimizedTasks()) { needsRefreshVisibility = true; diff --git a/plugin-taskbar/lxqttaskgroup.h b/plugin-taskbar/lxqttaskgroup.h index f1e7e2469..31ab784fb 100644 --- a/plugin-taskbar/lxqttaskgroup.h +++ b/plugin-taskbar/lxqttaskgroup.h @@ -31,11 +31,9 @@ #ifndef LXQTTASKGROUP_H #define LXQTTASKGROUP_H -#include "../panel/ilxqtpanel.h" -#include "../panel/ilxqtpanelplugin.h" - #include "lxqttaskbutton.h" -#include + +#include "../panel/backends/lxqttaskbartypes.h" class QVBoxLayout; class ILXQtPanelPlugin; @@ -62,7 +60,8 @@ class LXQtTaskGroup: public LXQtTaskButton // if circular is true, then it will go around the list of buttons LXQtTaskButton * getNextPrevChildButton(bool next, bool circular); - bool onWindowChanged(WId window, NET::Properties prop, NET::Properties2 prop2); + bool onWindowChanged(WId window, LXQtTaskBarWindowProperty prop); + void setAutoRotation(bool value, ILXQtPanel::Position position); Qt::ToolButtonStyle popupButtonStyle() const; void setToolButtonsStyle(Qt::ToolButtonStyle style);