diff --git a/autostart/CMakeLists.txt b/autostart/CMakeLists.txt index 098103168..f56d282ff 100644 --- a/autostart/CMakeLists.txt +++ b/autostart/CMakeLists.txt @@ -1,9 +1,9 @@ -file(GLOB DESKTOP_FILES_IN *.desktop.in) +set(AUTOSTART_DESKTOP_FILES_IN lxqt-panel.desktop.in) # Translations ********************************** lxqt_translate_desktop(DESKTOP_FILES SOURCES - ${DESKTOP_FILES_IN} + ${AUTOSTART_DESKTOP_FILES_IN} USE_YAML ) add_custom_target(lxqt_panel_autostart_desktop_files ALL DEPENDS ${DESKTOP_FILES}) @@ -14,3 +14,12 @@ install(FILES DESTINATION "${LXQT_ETC_XDG_DIR}/autostart" COMPONENT Runtime ) + +configure_file(lxqt-panel_wayland.desktop.in lxqt-panel_wayland.desktop @ONLY) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/lxqt-panel_wayland.desktop" + DESTINATION "/usr/share/applications" + RENAME "lxqt-panel.desktop" + COMPONENT Runtime +) diff --git a/autostart/lxqt-panel_wayland.desktop.in b/autostart/lxqt-panel_wayland.desktop.in new file mode 100644 index 000000000..540955e18 --- /dev/null +++ b/autostart/lxqt-panel_wayland.desktop.in @@ -0,0 +1,14 @@ +[Desktop Entry] +Type=Application +TryExec=lxqt-panel +NoDisplay=true + +# NOTE: KWin wants absolute path here, get it from CMake install path +Exec=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/lxqt-panel + +# NOTE: adding KDE to make it work under Plasma Wayland session +OnlyShowIn=LXQt;KDE +X-LXQt-Module=true + +# Make KWin recognize us as priviledged client +X-KDE-Wayland-Interfaces=org_kde_plasma_window_management diff --git a/panel/CMakeLists.txt b/panel/CMakeLists.txt index 074c62af3..b9ba8f10f 100644 --- a/panel/CMakeLists.txt +++ b/panel/CMakeLists.txt @@ -1,6 +1,6 @@ set(PROJECT lxqt-panel) -# TODO +# Window Manager backends add_subdirectory(backends) set(PRIV_HEADERS @@ -21,12 +21,6 @@ set(PRIV_HEADERS config/configstyling.h config/configpluginswidget.h config/addplugindialog.h - - backends/ilxqttaskbarabstractbackend.h - backends/lxqttaskbartypes.h - - backends/lxqttaskbardummybackend.h - backends/xcb/lxqttaskbarbackend_x11.h ) # using LXQt namespace in the public headers. @@ -35,9 +29,6 @@ set(PUB_HEADERS pluginsettings.h ilxqtpanelplugin.h ilxqtpanel.h - - backends/ilxqttaskbarabstractbackend.h - backends/lxqttaskbartypes.h ) set(SOURCES @@ -57,11 +48,6 @@ set(SOURCES config/configstyling.cpp config/configpluginswidget.cpp config/addplugindialog.cpp - - backends/ilxqttaskbarabstractbackend.cpp - - backends/lxqttaskbardummybackend.cpp - backends/xcb/lxqttaskbarbackend_x11.cpp ) set(UI @@ -122,6 +108,7 @@ target_link_libraries(${PROJECT} KF6::WindowSystem LayerShellQt::Interface ${STATIC_PLUGINS} + lxqt-panel-backend-common ) set_property(TARGET ${PROJECT} PROPERTY ENABLE_EXPORTS TRUE) diff --git a/panel/backends/CMakeLists.txt b/panel/backends/CMakeLists.txt index 8f34a3c67..3c47cc9bd 100644 --- a/panel/backends/CMakeLists.txt +++ b/panel/backends/CMakeLists.txt @@ -1 +1,19 @@ +# Common interface for Window Manager abstraction backend +# This also contains dummy backend + +add_library(lxqt-panel-backend-common STATIC + + lxqttaskbartypes.h + ilxqtabstractwmiface.h + ilxqtabstractwmiface.cpp + + lxqtdummywmbackend.h + lxqtdummywmbackend.cpp +) + +target_link_libraries(lxqt-panel-backend-common + Qt6::Gui +) + +add_subdirectory(wayland) add_subdirectory(xcb) diff --git a/panel/backends/ilxqtabstractwmiface.cpp b/panel/backends/ilxqtabstractwmiface.cpp new file mode 100644 index 000000000..cb8678647 --- /dev/null +++ b/panel/backends/ilxqtabstractwmiface.cpp @@ -0,0 +1,52 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + + +#include "ilxqtabstractwmiface.h" + +ILXQtAbstractWMInterface::ILXQtAbstractWMInterface(QObject *parent) + : QObject(parent) +{ + +} + +void ILXQtAbstractWMInterface::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/ilxqtabstractwmiface.h similarity index 61% rename from panel/backends/ilxqttaskbarabstractbackend.h rename to panel/backends/ilxqtabstractwmiface.h index 44840671a..3f234f542 100644 --- a/panel/backends/ilxqttaskbarabstractbackend.h +++ b/panel/backends/ilxqtabstractwmiface.h @@ -1,19 +1,48 @@ -#ifndef ILXQTTASKBARABSTRACTBACKEND_H -#define ILXQTTASKBARABSTRACTBACKEND_H +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + + +#ifndef ILXQT_ABSTRACT_WM_INTERFACE_H +#define ILXQT_ABSTRACT_WM_INTERFACE_H #include +#include "../lxqtpanelglobals.h" #include "lxqttaskbartypes.h" class QIcon; class QScreen; -class ILXQtTaskbarAbstractBackend : public QObject +class LXQT_PANEL_API ILXQtAbstractWMInterface : public QObject { Q_OBJECT public: - explicit ILXQtTaskbarAbstractBackend(QObject *parent = nullptr); + explicit ILXQtAbstractWMInterface(QObject *parent = nullptr); // Backend virtual bool supportsAction(WId windowId, LXQtTaskBarBackendAction action) const = 0; @@ -96,4 +125,28 @@ class ILXQtTaskbarAbstractBackend : public QObject void activeWindowChanged(WId windowId); }; -#endif // ILXQTTASKBARABSTRACTBACKEND_H +class LXQT_PANEL_API ILXQtWMBackendLibrary +{ +public: + /** + Destroys the ILXQtWMBackendLibrary object. + **/ + virtual ~ILXQtWMBackendLibrary() {} + + /** + Returns the score of this backend for current detected environment. + This is used to select correct backend at runtime + **/ + virtual int getBackendScore(const QString& key) const = 0; + + /** + Returns the root component object of the backend. When the library is finally unloaded, the root component will automatically be deleted. + **/ + virtual ILXQtAbstractWMInterface* instance() const = 0; +}; + + +Q_DECLARE_INTERFACE(ILXQtWMBackendLibrary, + "lxqt.org/Panel/WMInterface/1.0") + +#endif // ILXQT_ABSTRACT_WM_INTERFACE_H diff --git a/panel/backends/ilxqttaskbarabstractbackend.cpp b/panel/backends/ilxqttaskbarabstractbackend.cpp deleted file mode 100644 index 137728263..000000000 --- a/panel/backends/ilxqttaskbarabstractbackend.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#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/lxqtdummywmbackend.cpp b/panel/backends/lxqtdummywmbackend.cpp new file mode 100644 index 000000000..071cbbfbd --- /dev/null +++ b/panel/backends/lxqtdummywmbackend.cpp @@ -0,0 +1,197 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + +#include "lxqtdummywmbackend.h" + +#include + +LXQtDummyWMBackend::LXQtDummyWMBackend(QObject *parent) + : ILXQtAbstractWMInterface(parent) +{ + +} + +/************************************************ + * Windows function + ************************************************/ +bool LXQtDummyWMBackend::supportsAction(WId, LXQtTaskBarBackendAction) const +{ + return false; +} + +bool LXQtDummyWMBackend::reloadWindows() +{ + return false; +} + +QVector LXQtDummyWMBackend::getCurrentWindows() const +{ + return {}; +} + +QString LXQtDummyWMBackend::getWindowTitle(WId) const +{ + return QString(); +} + +bool LXQtDummyWMBackend::applicationDemandsAttention(WId) const +{ + return false; +} + +QIcon LXQtDummyWMBackend::getApplicationIcon(WId, int) const +{ + return QIcon(); +} + +QString LXQtDummyWMBackend::getWindowClass(WId) const +{ + return QString(); +} + +LXQtTaskBarWindowLayer LXQtDummyWMBackend::getWindowLayer(WId) const +{ + return LXQtTaskBarWindowLayer::Normal; +} + +bool LXQtDummyWMBackend::setWindowLayer(WId, LXQtTaskBarWindowLayer) +{ + return false; +} + +LXQtTaskBarWindowState LXQtDummyWMBackend::getWindowState(WId) const +{ + return LXQtTaskBarWindowState::Normal; +} + +bool LXQtDummyWMBackend::setWindowState(WId, LXQtTaskBarWindowState, bool) +{ + return false; +} + +bool LXQtDummyWMBackend::isWindowActive(WId) const +{ + return false; +} + +bool LXQtDummyWMBackend::raiseWindow(WId, bool) +{ + return false; +} + +bool LXQtDummyWMBackend::closeWindow(WId) +{ + return false; +} + +WId LXQtDummyWMBackend::getActiveWindow() const +{ + return 0; +} + + +/************************************************ + * Workspaces + ************************************************/ +int LXQtDummyWMBackend::getWorkspacesCount() const +{ + return 1; // Fake 1 workspace +} + +QString LXQtDummyWMBackend::getWorkspaceName(int) const +{ + return QString(); +} + +int LXQtDummyWMBackend::getCurrentWorkspace() const +{ + return 0; +} + +bool LXQtDummyWMBackend::setCurrentWorkspace(int) +{ + return false; +} + +int LXQtDummyWMBackend::getWindowWorkspace(WId) const +{ + return 0; +} + +bool LXQtDummyWMBackend::setWindowOnWorkspace(WId, int) +{ + return false; +} + +void LXQtDummyWMBackend::moveApplicationToPrevNextMonitor(WId, bool, bool) +{ + //No-op +} + +bool LXQtDummyWMBackend::isWindowOnScreen(QScreen *, WId) const +{ + return false; +} + +bool LXQtDummyWMBackend::setDesktopLayout(Qt::Orientation, int, int, bool) +{ + return false; +} + +/************************************************ + * X11 Specific + ************************************************/ +void LXQtDummyWMBackend::moveApplication(WId) +{ + //No-op +} + +void LXQtDummyWMBackend::resizeApplication(WId) +{ + //No-op +} + +void LXQtDummyWMBackend::refreshIconGeometry(WId, QRect const &) +{ + //No-op +} + +bool LXQtDummyWMBackend::isAreaOverlapped(const QRect &) const +{ + return false; +} + +bool LXQtDummyWMBackend::isShowingDesktop() const +{ + return false; +} + +bool LXQtDummyWMBackend::showDesktop(bool) +{ + return false; +} + diff --git a/panel/backends/lxqttaskbardummybackend.h b/panel/backends/lxqtdummywmbackend.h similarity index 65% rename from panel/backends/lxqttaskbardummybackend.h rename to panel/backends/lxqtdummywmbackend.h index 15506838f..de6217903 100644 --- a/panel/backends/lxqttaskbardummybackend.h +++ b/panel/backends/lxqtdummywmbackend.h @@ -1,14 +1,42 @@ -#ifndef LXQTTASKBARDUMMYBACKEND_H -#define LXQTTASKBARDUMMYBACKEND_H - -#include "ilxqttaskbarabstractbackend.h" - -class LXQtTaskBarDummyBackend : public ILXQtTaskbarAbstractBackend +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + + +#ifndef LXQT_DUMMY_WM_BACKEND_H +#define LXQT_DUMMY_WM_BACKEND_H + +#include "ilxqtabstractwmiface.h" + +class LXQtDummyWMBackend : public ILXQtAbstractWMInterface { Q_OBJECT public: - explicit LXQtTaskBarDummyBackend(QObject *parent = nullptr); + explicit LXQtDummyWMBackend(QObject *parent = nullptr); // Backend bool supportsAction(WId windowId, LXQtTaskBarBackendAction action) const override; @@ -85,4 +113,4 @@ class LXQtTaskBarDummyBackend : public ILXQtTaskbarAbstractBackend void activeWindowChanged(WId windowId); }; -#endif // LXQTTASKBARDUMMYBACKEND_H +#endif // LXQT_DUMMY_WM_BACKEND_H diff --git a/panel/backends/lxqttaskbardummybackend.cpp b/panel/backends/lxqttaskbardummybackend.cpp deleted file mode 100644 index 15e7e1149..000000000 --- a/panel/backends/lxqttaskbardummybackend.cpp +++ /dev/null @@ -1,171 +0,0 @@ -#include "lxqttaskbardummybackend.h" - -#include - -LXQtTaskBarDummyBackend::LXQtTaskBarDummyBackend(QObject *parent) - : ILXQtTaskbarAbstractBackend(parent) -{ - -} - - -/************************************************ - * Windows function - ************************************************/ -bool LXQtTaskBarDummyBackend::supportsAction(WId, LXQtTaskBarBackendAction) const -{ - return false; -} - -bool LXQtTaskBarDummyBackend::reloadWindows() -{ - return false; -} - -QVector LXQtTaskBarDummyBackend::getCurrentWindows() const -{ - return {}; -} - -QString LXQtTaskBarDummyBackend::getWindowTitle(WId) const -{ - return QString(); -} - -bool LXQtTaskBarDummyBackend::applicationDemandsAttention(WId) const -{ - return false; -} - -QIcon LXQtTaskBarDummyBackend::getApplicationIcon(WId, int) const -{ - return QIcon(); -} - -QString LXQtTaskBarDummyBackend::getWindowClass(WId) const -{ - return QString(); -} - -LXQtTaskBarWindowLayer LXQtTaskBarDummyBackend::getWindowLayer(WId) const -{ - return LXQtTaskBarWindowLayer::Normal; -} - -bool LXQtTaskBarDummyBackend::setWindowLayer(WId, LXQtTaskBarWindowLayer) -{ - return false; -} - -LXQtTaskBarWindowState LXQtTaskBarDummyBackend::getWindowState(WId) const -{ - return LXQtTaskBarWindowState::Normal; -} - -bool LXQtTaskBarDummyBackend::setWindowState(WId, LXQtTaskBarWindowState, bool) -{ - return false; -} - -bool LXQtTaskBarDummyBackend::isWindowActive(WId) const -{ - return false; -} - -bool LXQtTaskBarDummyBackend::raiseWindow(WId, bool) -{ - return false; -} - -bool LXQtTaskBarDummyBackend::closeWindow(WId) -{ - return false; -} - -WId LXQtTaskBarDummyBackend::getActiveWindow() const -{ - return 0; -} - - -/************************************************ - * Workspaces - ************************************************/ -int LXQtTaskBarDummyBackend::getWorkspacesCount() const -{ - return 1; // Fake 1 workspace -} - -QString LXQtTaskBarDummyBackend::getWorkspaceName(int) const -{ - return QString(); -} - -int LXQtTaskBarDummyBackend::getCurrentWorkspace() const -{ - return 0; -} - -bool LXQtTaskBarDummyBackend::setCurrentWorkspace(int) -{ - return false; -} - -int LXQtTaskBarDummyBackend::getWindowWorkspace(WId) const -{ - return 0; -} - -bool LXQtTaskBarDummyBackend::setWindowOnWorkspace(WId, int) -{ - return false; -} - -void LXQtTaskBarDummyBackend::moveApplicationToPrevNextMonitor(WId, bool, bool) -{ - //No-op -} - -bool LXQtTaskBarDummyBackend::isWindowOnScreen(QScreen *, WId) const -{ - return false; -} - -bool LXQtTaskBarDummyBackend::setDesktopLayout(Qt::Orientation, int, int, bool) -{ - return false; -} - -/************************************************ - * X11 Specific - ************************************************/ -void LXQtTaskBarDummyBackend::moveApplication(WId) -{ - //No-op -} - -void LXQtTaskBarDummyBackend::resizeApplication(WId) -{ - //No-op -} - -void LXQtTaskBarDummyBackend::refreshIconGeometry(WId, QRect const &) -{ - //No-op -} - -bool LXQtTaskBarDummyBackend::isAreaOverlapped(const QRect &) const -{ - return false; -} - -bool LXQtTaskBarDummyBackend::isShowingDesktop() const -{ - return false; -} - -bool LXQtTaskBarDummyBackend::showDesktop(bool) -{ - return false; -} - diff --git a/panel/backends/lxqttaskbartypes.h b/panel/backends/lxqttaskbartypes.h index 656591fbc..e821b410d 100644 --- a/panel/backends/lxqttaskbartypes.h +++ b/panel/backends/lxqttaskbartypes.h @@ -1,3 +1,31 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + + #ifndef LXQTTASKBARTYPES_H #define LXQTTASKBARTYPES_H @@ -50,7 +78,7 @@ enum class LXQtTaskBarWindowLayer enum class LXQtTaskBarWorkspace { - ShowOnAll = -1 + ShowOnAll = 0 // Virtual destops have 1-based indexes }; #endif // LXQTTASKBARTYPES_H diff --git a/panel/backends/wayland/CMakeLists.txt b/panel/backends/wayland/CMakeLists.txt new file mode 100644 index 000000000..19340698c --- /dev/null +++ b/panel/backends/wayland/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(kwin_wayland) +add_subdirectory(wlroots) diff --git a/panel/backends/wayland/kwin_wayland/CMakeLists.txt b/panel/backends/wayland/kwin_wayland/CMakeLists.txt new file mode 100644 index 000000000..a4a097f69 --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/CMakeLists.txt @@ -0,0 +1,43 @@ +set(PLATFORM_NAME kwin_wayland) + +set(PREFIX_NAME wmbackend) +set(PROGRAM "lxqt-panel") +set(BACKEND "backend") +set(NAME ${PREFIX_NAME}_${PLATFORM_NAME}) +project(${PROGRAM}_${BACKEND}_${NAME}) + +find_package(Qt6 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS WaylandClient Concurrent) + +set(PROG_SHARE_DIR ${CMAKE_INSTALL_FULL_DATAROOTDIR}/lxqt/${PROGRAM}/${BACKEND}) +set(PLUGIN_SHARE_DIR ${PROG_SHARE_DIR}/${BACKEND}/${NAME}) +#************************************************ + +if (NOT DEFINED PLUGIN_DIR) + set (PLUGIN_DIR ${CMAKE_INSTALL_FULL_LIBDIR}/${PROGRAM}) +endif (NOT DEFINED PLUGIN_DIR) + +set(QTX_LIBRARIES Qt6::Gui) + +set(SRC + lxqtwmbackend_kwinwayland.h + lxqtwmbackend_kwinwayland.cpp + + lxqtplasmavirtualdesktop.h + lxqtplasmavirtualdesktop.cpp + + lxqttaskbarplasmawindowmanagment.h + lxqttaskbarplasmawindowmanagment.cpp +) + +add_library(${NAME} MODULE ${SRC}) # build dynamically loadable modules +install(TARGETS ${NAME} DESTINATION ${PLUGIN_DIR}/${BACKEND}) # install the *.so file + +target_link_libraries(${NAME} ${QTX_LIBRARIES} Qt6::Concurrent Qt6::WaylandClient) + +qt6_generate_wayland_protocol_client_sources(${NAME} FILES + ${CMAKE_CURRENT_SOURCE_DIR}/protocols/plasma-window-management.xml +) + +qt6_generate_wayland_protocol_client_sources(${NAME} FILES + ${CMAKE_CURRENT_SOURCE_DIR}/protocols/org-kde-plasma-virtual-desktop.xml +) diff --git a/panel/backends/wayland/kwin_wayland/lxqtplasmavirtualdesktop.cpp b/panel/backends/wayland/kwin_wayland/lxqtplasmavirtualdesktop.cpp new file mode 100644 index 000000000..d26574e62 --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/lxqtplasmavirtualdesktop.cpp @@ -0,0 +1,258 @@ +/* + SPDX-FileCopyrightText: 2016 Eike Hein + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + + Adapted from KDE Plasma Workspace: plasma-workspace/libtaskmanager/waylandtasksmodel.cpp +*/ + + +#include "lxqtplasmavirtualdesktop.h" + +#include + +LXQtPlasmaVirtualDesktop::LXQtPlasmaVirtualDesktop(::org_kde_plasma_virtual_desktop *object, const QString &id) + : org_kde_plasma_virtual_desktop(object) + , id(id) +{ +} + +LXQtPlasmaVirtualDesktop::~LXQtPlasmaVirtualDesktop() +{ + wl_proxy_destroy(reinterpret_cast(object())); +} + +void LXQtPlasmaVirtualDesktop::org_kde_plasma_virtual_desktop_name(const QString &name) +{ + this->name = name; + Q_EMIT nameChanged(); +} + +void LXQtPlasmaVirtualDesktop::org_kde_plasma_virtual_desktop_done() +{ + Q_EMIT done(); +} + +void LXQtPlasmaVirtualDesktop::org_kde_plasma_virtual_desktop_activated() +{ + Q_EMIT activated(); +} + +LXQtPlasmaVirtualDesktopManagment::LXQtPlasmaVirtualDesktopManagment() + : QWaylandClientExtensionTemplate(version) +{ + connect(this, &QWaylandClientExtension::activeChanged, this, [this] { + if (!isActive()) { + org_kde_plasma_virtual_desktop_management_destroy(object()); + } + }); +} + +LXQtPlasmaVirtualDesktopManagment::~LXQtPlasmaVirtualDesktopManagment() +{ + if (isActive()) { + org_kde_plasma_virtual_desktop_management_destroy(object()); + } +} + +void LXQtPlasmaVirtualDesktopManagment::org_kde_plasma_virtual_desktop_management_desktop_created(const QString &desktop_id, uint32_t position) +{ + emit desktopCreated(desktop_id, position); +} + +void LXQtPlasmaVirtualDesktopManagment::org_kde_plasma_virtual_desktop_management_desktop_removed(const QString &desktop_id) +{ + emit desktopRemoved(desktop_id); +} + +void LXQtPlasmaVirtualDesktopManagment::org_kde_plasma_virtual_desktop_management_rows(uint32_t rows) +{ + emit rowsChanged(rows); +} + +LXQtPlasmaWaylandWorkspaceInfo::LXQtPlasmaWaylandWorkspaceInfo() +{ + init(); +} + +LXQtPlasmaWaylandWorkspaceInfo::VirtualDesktopsIterator LXQtPlasmaWaylandWorkspaceInfo::findDesktop(const QString &id) const +{ + return std::find_if(virtualDesktops.begin(), virtualDesktops.end(), + [&id](const std::unique_ptr &desktop) { + return desktop->id == id; + }); +} + +QString LXQtPlasmaWaylandWorkspaceInfo::getDesktopName(int pos) const +{ + if(pos < 0 || size_t(pos) >= virtualDesktops.size()) + return QString(); + return virtualDesktops[pos]->name; +} + +QString LXQtPlasmaWaylandWorkspaceInfo::getDesktopId(int pos) const +{ + if(pos < 0 || size_t(pos) >= virtualDesktops.size()) + return QString(); + return virtualDesktops[pos]->id; +} + +void LXQtPlasmaWaylandWorkspaceInfo::init() +{ + virtualDesktopManagement = std::make_unique(); + + connect(virtualDesktopManagement.get(), &LXQtPlasmaVirtualDesktopManagment::activeChanged, this, [this] { + if (!virtualDesktopManagement->isActive()) { + rows = 0; + virtualDesktops.clear(); + currentVirtualDesktop.clear(); + Q_EMIT currentDesktopChanged(); + Q_EMIT numberOfDesktopsChanged(); + Q_EMIT navigationWrappingAroundChanged(); + Q_EMIT desktopIdsChanged(); + Q_EMIT desktopLayoutRowsChanged(); + } + }); + + connect(virtualDesktopManagement.get(), &LXQtPlasmaVirtualDesktopManagment::desktopCreated, + this, &LXQtPlasmaWaylandWorkspaceInfo::addDesktop); + + connect(virtualDesktopManagement.get(), &LXQtPlasmaVirtualDesktopManagment::desktopRemoved, this, [this](const QString &id) { + + + virtualDesktops.erase(std::remove_if(virtualDesktops.begin(), virtualDesktops.end(), + [id](const std::unique_ptr &desktop) + { + return desktop->id == id; + }), + virtualDesktops.end()); + + Q_EMIT numberOfDesktopsChanged(); + Q_EMIT desktopIdsChanged(); + + if (currentVirtualDesktop == id) { + currentVirtualDesktop.clear(); + Q_EMIT currentDesktopChanged(); + } + }); + + connect(virtualDesktopManagement.get(), &LXQtPlasmaVirtualDesktopManagment::rowsChanged, this, [this](quint32 rows) { + this->rows = rows; + Q_EMIT desktopLayoutRowsChanged(); + }); +} + +void LXQtPlasmaWaylandWorkspaceInfo::addDesktop(const QString &id, quint32 pos) +{ + if (findDesktop(id) != virtualDesktops.end()) { + return; + } + + auto desktop = std::make_unique(virtualDesktopManagement->get_virtual_desktop(id), id); + + connect(desktop.get(), &LXQtPlasmaVirtualDesktop::activated, this, [id, this]() { + currentVirtualDesktop = id; + Q_EMIT currentDesktopChanged(); + }); + + connect(desktop.get(), &LXQtPlasmaVirtualDesktop::nameChanged, this, [id, this]() { + Q_EMIT desktopNameChanged(position(id)); + }); + + connect(desktop.get(), &LXQtPlasmaVirtualDesktop::done, this, [id, this]() { + Q_EMIT desktopNameChanged(position(id)); + }); + + virtualDesktops.insert(std::next(virtualDesktops.begin(), pos), std::move(desktop)); + + Q_EMIT numberOfDesktopsChanged(); + Q_EMIT desktopIdsChanged(); + Q_EMIT desktopNameChanged(position(id)); +} + +QVariant LXQtPlasmaWaylandWorkspaceInfo::currentDesktop() const +{ + return currentVirtualDesktop; +} + +int LXQtPlasmaWaylandWorkspaceInfo::numberOfDesktops() const +{ + return virtualDesktops.size(); +} + +quint32 LXQtPlasmaWaylandWorkspaceInfo::position(const QVariant &desktop) const +{ + return std::distance(virtualDesktops.begin(), findDesktop(desktop.toString())); +} + +QVariantList LXQtPlasmaWaylandWorkspaceInfo::desktopIds() const +{ + QVariantList ids; + ids.reserve(virtualDesktops.size()); + + std::transform(virtualDesktops.cbegin(), virtualDesktops.cend(), std::back_inserter(ids), [](const std::unique_ptr &desktop) { + return desktop->id; + }); + return ids; +} + +QStringList LXQtPlasmaWaylandWorkspaceInfo::desktopNames() const +{ + if (!virtualDesktopManagement->isActive()) { + return QStringList(); + } + QStringList names; + names.reserve(virtualDesktops.size()); + + std::transform(virtualDesktops.cbegin(), virtualDesktops.cend(), std::back_inserter(names), [](const std::unique_ptr &desktop) { + return desktop->name; + }); + return names; +} + +int LXQtPlasmaWaylandWorkspaceInfo::desktopLayoutRows() const +{ + if (!virtualDesktopManagement->isActive()) { + return 0; + } + + return rows; +} + +void LXQtPlasmaWaylandWorkspaceInfo::requestActivate(const QVariant &desktop) +{ + if (!virtualDesktopManagement->isActive()) { + return; + } + + if (auto it = findDesktop(desktop.toString()); it != virtualDesktops.end()) { + (*it)->request_activate(); + } +} + +void LXQtPlasmaWaylandWorkspaceInfo::requestCreateDesktop(quint32 position) +{ + if (!virtualDesktopManagement->isActive()) { + return; + } + + //TODO: translatestd + virtualDesktopManagement->request_create_virtual_desktop(QLatin1String("New Desktop"), position); +} + +void LXQtPlasmaWaylandWorkspaceInfo::requestRemoveDesktop(quint32 position) +{ + if (!virtualDesktopManagement->isActive()) { + return; + } + if (virtualDesktops.size() == 1) { + return; + } + + if (position > (virtualDesktops.size() - 1)) { + return; + } + + virtualDesktopManagement->request_remove_virtual_desktop(virtualDesktops.at(position)->id); +} + diff --git a/panel/backends/wayland/kwin_wayland/lxqtplasmavirtualdesktop.h b/panel/backends/wayland/kwin_wayland/lxqtplasmavirtualdesktop.h new file mode 100644 index 000000000..16935be1a --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/lxqtplasmavirtualdesktop.h @@ -0,0 +1,99 @@ +/* + SPDX-FileCopyrightText: 2016 Eike Hein + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + + Adapted from KDE Plasma Workspace: plasma-workspace/libtaskmanager/waylandtasksmodel.cpp +*/ + +#ifndef LXQTPLASMAVIRTUALDESKTOP_H +#define LXQTPLASMAVIRTUALDESKTOP_H + +#include +#include + +#include + +#include "qwayland-org-kde-plasma-virtual-desktop.h" + +class LXQtPlasmaVirtualDesktop : public QObject, public QtWayland::org_kde_plasma_virtual_desktop +{ + Q_OBJECT +public: + LXQtPlasmaVirtualDesktop(::org_kde_plasma_virtual_desktop *object, const QString &id); + ~LXQtPlasmaVirtualDesktop(); + const QString id; + QString name; +Q_SIGNALS: + void done(); + void activated(); + void nameChanged(); + +protected: + void org_kde_plasma_virtual_desktop_name(const QString &name) override; + void org_kde_plasma_virtual_desktop_done() override; + void org_kde_plasma_virtual_desktop_activated() override; +}; + + +class LXQtPlasmaVirtualDesktopManagment : public QWaylandClientExtensionTemplate, + public QtWayland::org_kde_plasma_virtual_desktop_management +{ + Q_OBJECT +public: + static constexpr int version = 2; + + LXQtPlasmaVirtualDesktopManagment(); + ~LXQtPlasmaVirtualDesktopManagment(); + +signals: + void desktopCreated(const QString &id, quint32 position); + void desktopRemoved(const QString &id); + void rowsChanged(const quint32 rows); + +protected: + virtual void org_kde_plasma_virtual_desktop_management_desktop_created(const QString &desktop_id, uint32_t position) override; + virtual void org_kde_plasma_virtual_desktop_management_desktop_removed(const QString &desktop_id) override; + virtual void org_kde_plasma_virtual_desktop_management_rows(uint32_t rows) override; +}; + +class Q_DECL_HIDDEN LXQtPlasmaWaylandWorkspaceInfo : public QObject +{ + Q_OBJECT +public: + LXQtPlasmaWaylandWorkspaceInfo(); + + QVariant currentVirtualDesktop; + std::vector> virtualDesktops; + std::unique_ptr virtualDesktopManagement; + quint32 rows; + + typedef std::vector>::const_iterator VirtualDesktopsIterator; + + VirtualDesktopsIterator findDesktop(const QString &id) const; + + QString getDesktopName(int pos) const; + QString getDesktopId(int pos) const; + + void init(); + void addDesktop(const QString &id, quint32 pos); + QVariant currentDesktop() const; + int numberOfDesktops() const; + QVariantList desktopIds() const; + QStringList desktopNames() const; + quint32 position(const QVariant &desktop) const; + int desktopLayoutRows() const; + void requestActivate(const QVariant &desktop); + void requestCreateDesktop(quint32 position); + void requestRemoveDesktop(quint32 position); + +signals: + void currentDesktopChanged(); + void numberOfDesktopsChanged(); + void navigationWrappingAroundChanged(); + void desktopIdsChanged(); + void desktopNameChanged(quint32 position); + void desktopLayoutRowsChanged(); +}; + +#endif // LXQTPLASMAVIRTUALDESKTOP_H diff --git a/panel/backends/wayland/kwin_wayland/lxqttaskbarplasmawindowmanagment.cpp b/panel/backends/wayland/kwin_wayland/lxqttaskbarplasmawindowmanagment.cpp new file mode 100644 index 000000000..d64ee9b0d --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/lxqttaskbarplasmawindowmanagment.cpp @@ -0,0 +1,311 @@ +/* + SPDX-FileCopyrightText: 2016 Eike Hein + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + + Adapted from KDE Plasma Workspace: plasma-workspace/libtaskmanager/waylandtasksmodel.cpp +*/ + +#include "lxqttaskbarplasmawindowmanagment.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * LXQtTaskBarPlasmaWindow + */ + +LXQtTaskBarPlasmaWindow::LXQtTaskBarPlasmaWindow(const QString &uuid, ::org_kde_plasma_window *id) + : org_kde_plasma_window(id) + , uuid(uuid) +{ +} + +LXQtTaskBarPlasmaWindow::~LXQtTaskBarPlasmaWindow() +{ + destroy(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_unmapped() +{ + wasUnmapped = true; + Q_EMIT unmapped(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_title_changed(const QString &title) +{ + if(this->title == title) + return; + this->title = title; + Q_EMIT titleChanged(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_app_id_changed(const QString &app_id) +{ + if(appId == app_id) + return; + appId = app_id; + Q_EMIT appIdChanged(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_icon_changed() +{ + int pipeFds[2]; + if (pipe2(pipeFds, O_CLOEXEC) != 0) { + qWarning() << "TaskManager: failed creating pipe"; + return; + } + get_icon(pipeFds[1]); + ::close(pipeFds[1]); + auto readIcon = [uuid = uuid](int fd) { + auto closeGuard = qScopeGuard([fd]() { + ::close(fd); + }); + pollfd pollFd; + pollFd.fd = fd; + pollFd.events = POLLIN; + QByteArray data; + while (true) { + int ready = poll(&pollFd, 1, 1000); + if (ready < 0 && errno != EINTR) { + qWarning() << "TaskManager: polling for icon of window" << uuid << "failed"; + return QIcon(); + } else if (ready == 0) { + qWarning() << "TaskManager: time out polling for icon of window" << uuid; + return QIcon(); + } else { + char buffer[4096]; + int n = read(fd, buffer, sizeof(buffer)); + if (n < 0) { + qWarning() << "TaskManager: error reading icon of window" << uuid; + return QIcon(); + } else if (n > 0) { + data.append(buffer, n); + } else { + QIcon icon; + QDataStream ds(data); + ds >> icon; + return icon; + } + } + } + }; + QFuture future = QtConcurrent::run(readIcon, pipeFds[0]); + auto watcher = new QFutureWatcher(); + watcher->setFuture(future); + connect(watcher, &QFutureWatcher::finished, this, [this, watcher] { + icon = watcher->future().result(); + Q_EMIT iconChanged(); + }); + connect(watcher, &QFutureWatcher::finished, watcher, &QObject::deleteLater); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_themed_icon_name_changed(const QString &name) +{ + icon = QIcon::fromTheme(name); + Q_EMIT iconChanged(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_state_changed(uint32_t flags) +{ + auto diff = windowState ^ flags; + if (diff & state::state_active) { + windowState.setFlag(state::state_active, flags & state::state_active); + Q_EMIT activeChanged(); + } + if (diff & state::state_minimized) { + windowState.setFlag(state::state_minimized, flags & state::state_minimized); + Q_EMIT minimizedChanged(); + } + if (diff & state::state_maximized) { + windowState.setFlag(state::state_maximized, flags & state::state_maximized); + Q_EMIT maximizedChanged(); + } + if (diff & state::state_fullscreen) { + windowState.setFlag(state::state_fullscreen, flags & state::state_fullscreen); + Q_EMIT fullscreenChanged(); + } + if (diff & state::state_keep_above) { + windowState.setFlag(state::state_keep_above, flags & state::state_keep_above); + Q_EMIT keepAboveChanged(); + } + if (diff & state::state_keep_below) { + windowState.setFlag(state::state_keep_below, flags & state::state_keep_below); + Q_EMIT keepBelowChanged(); + } + if (diff & state::state_on_all_desktops) { + windowState.setFlag(state::state_on_all_desktops, flags & state::state_on_all_desktops); + Q_EMIT onAllDesktopsChanged(); + } + if (diff & state::state_demands_attention) { + windowState.setFlag(state::state_demands_attention, flags & state::state_demands_attention); + Q_EMIT demandsAttentionChanged(); + } + if (diff & state::state_closeable) { + windowState.setFlag(state::state_closeable, flags & state::state_closeable); + Q_EMIT closeableChanged(); + } + if (diff & state::state_minimizable) { + windowState.setFlag(state::state_minimizable, flags & state::state_minimizable); + Q_EMIT minimizeableChanged(); + } + if (diff & state::state_maximizable) { + windowState.setFlag(state::state_maximizable, flags & state::state_maximizable); + Q_EMIT maximizeableChanged(); + } + if (diff & state::state_fullscreenable) { + windowState.setFlag(state::state_fullscreenable, flags & state::state_fullscreenable); + Q_EMIT fullscreenableChanged(); + } + if (diff & state::state_skiptaskbar) { + windowState.setFlag(state::state_skiptaskbar, flags & state::state_skiptaskbar); + Q_EMIT skipTaskbarChanged(); + } + if (diff & state::state_shadeable) { + windowState.setFlag(state::state_shadeable, flags & state::state_shadeable); + Q_EMIT shadeableChanged(); + } + if (diff & state::state_shaded) { + windowState.setFlag(state::state_shaded, flags & state::state_shaded); + Q_EMIT shadedChanged(); + } + if (diff & state::state_movable) { + windowState.setFlag(state::state_movable, flags & state::state_movable); + Q_EMIT movableChanged(); + } + if (diff & state::state_resizable) { + windowState.setFlag(state::state_resizable, flags & state::state_resizable); + Q_EMIT resizableChanged(); + } + if (diff & state::state_virtual_desktop_changeable) { + windowState.setFlag(state::state_virtual_desktop_changeable, flags & state::state_virtual_desktop_changeable); + Q_EMIT virtualDesktopChangeableChanged(); + } + if (diff & state::state_skipswitcher) { + windowState.setFlag(state::state_skipswitcher, flags & state::state_skipswitcher); + Q_EMIT skipSwitcherChanged(); + } +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_virtual_desktop_entered(const QString &id) +{ + virtualDesktops.push_back(id); + Q_EMIT virtualDesktopEntered(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_virtual_desktop_left(const QString &id) +{ + virtualDesktops.removeAll(id); + Q_EMIT virtualDesktopLeft(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_geometry(int32_t x, int32_t y, uint32_t width, uint32_t height) +{ + geometry = QRect(x, y, width, height); + Q_EMIT geometryChanged(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_application_menu(const QString &service_name, const QString &object_path) +{ + applicationMenuService = service_name; + applicationMenuObjectPath = object_path; + Q_EMIT applicationMenuChanged(); +} + +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_activity_entered(const QString &id) +{ + activities.push_back(id); + Q_EMIT activitiesChanged(); +} +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_activity_left(const QString &id) +{ + activities.removeAll(id); + Q_EMIT activitiesChanged(); +} +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_pid_changed(uint32_t pid) +{ + this->pid = pid; +} +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_resource_name_changed(const QString &resource_name) +{ + resourceName = resource_name; +} +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_parent_window(::org_kde_plasma_window *parent) +{ + LXQtTaskBarPlasmaWindow *parentWindow = nullptr; + if (parent) { + parentWindow = dynamic_cast(LXQtTaskBarPlasmaWindow::fromObject(parent)); + } + setParentWindow(parentWindow); +} +void LXQtTaskBarPlasmaWindow::org_kde_plasma_window_initial_state() +{ + Q_EMIT initialStateDone(); +} + +void LXQtTaskBarPlasmaWindow::setParentWindow(LXQtTaskBarPlasmaWindow *parent) +{ + const auto old = parentWindow; + QObject::disconnect(parentWindowUnmappedConnection); + + if (parent && !parent->wasUnmapped) { + parentWindow = QPointer(parent); + parentWindowUnmappedConnection = QObject::connect(parent, &LXQtTaskBarPlasmaWindow::unmapped, this, [this] { + setParentWindow(nullptr); + }); + } else { + parentWindow = QPointer(); + parentWindowUnmappedConnection = QMetaObject::Connection(); + } + + if (parentWindow.data() != old.data()) { + Q_EMIT parentWindowChanged(); + } +} + +/* + * LXQtTaskBarPlasmaWindowManagment + */ + +LXQtTaskBarPlasmaWindowManagment::LXQtTaskBarPlasmaWindowManagment() + : QWaylandClientExtensionTemplate(version) +{ + connect(this, &QWaylandClientExtension::activeChanged, this, [this] { + if (!isActive()) { + org_kde_plasma_window_management_destroy(object()); + } + }); +} + +LXQtTaskBarPlasmaWindowManagment::~LXQtTaskBarPlasmaWindowManagment() +{ + if (isActive()) { + org_kde_plasma_window_management_destroy(object()); + } +} + +void LXQtTaskBarPlasmaWindowManagment::org_kde_plasma_window_management_show_desktop_changed(uint32_t state) +{ + m_isShowingDesktop = (state == show_desktop::show_desktop_enabled); +} + +void LXQtTaskBarPlasmaWindowManagment::org_kde_plasma_window_management_window_with_uuid(uint32_t id, const QString &uuid) +{ + Q_UNUSED(id) + Q_EMIT windowCreated(new LXQtTaskBarPlasmaWindow(uuid, get_window_by_uuid(uuid))); +} +void LXQtTaskBarPlasmaWindowManagment::org_kde_plasma_window_management_stacking_order_uuid_changed(const QString &uuids) +{ + Q_EMIT stackingOrderChanged(uuids); +} diff --git a/panel/backends/wayland/kwin_wayland/lxqttaskbarplasmawindowmanagment.h b/panel/backends/wayland/kwin_wayland/lxqttaskbarplasmawindowmanagment.h new file mode 100644 index 000000000..b7c6d1f00 --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/lxqttaskbarplasmawindowmanagment.h @@ -0,0 +1,170 @@ +/* + SPDX-FileCopyrightText: 2016 Eike Hein + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + + Adapted from KDE Plasma Workspace: plasma-workspace/libtaskmanager/waylandtasksmodel.cpp +*/ + +#ifndef LXQTTASKBARPLASMAWINDOWMANAGMENT_H +#define LXQTTASKBARPLASMAWINDOWMANAGMENT_H + +#include +#include +#include + +#include "qwayland-plasma-window-management.h" + +typedef quintptr WId; + +class LXQtTaskBarPlasmaWindowManagment; + +class LXQtTaskBarPlasmaWindow : public QObject, + public QtWayland::org_kde_plasma_window +{ + Q_OBJECT +public: + LXQtTaskBarPlasmaWindow(const QString &uuid, ::org_kde_plasma_window *id); + ~LXQtTaskBarPlasmaWindow(); + + inline WId getWindowId() const { return reinterpret_cast(this); } + + using state = QtWayland::org_kde_plasma_window_management::state; + const QString uuid; + QString title; + QString appId; + QIcon icon; + QFlags windowState; + QList virtualDesktops; + QRect geometry; + QString applicationMenuService; + QString applicationMenuObjectPath; + QList activities; + quint32 pid; + QString resourceName; + QPointer parentWindow; + bool wasUnmapped = false; + bool acceptedInTaskBar = false; + +Q_SIGNALS: + void unmapped(); + void titleChanged(); + void appIdChanged(); + void iconChanged(); + void activeChanged(); + void minimizedChanged(); + void maximizedChanged(); + void fullscreenChanged(); + void keepAboveChanged(); + void keepBelowChanged(); + void onAllDesktopsChanged(); + void demandsAttentionChanged(); + void closeableChanged(); + void minimizeableChanged(); + void maximizeableChanged(); + void fullscreenableChanged(); + void skiptaskbarChanged(); + void shadeableChanged(); + void shadedChanged(); + void movableChanged(); + void resizableChanged(); + void virtualDesktopChangeableChanged(); + void skipSwitcherChanged(); + void virtualDesktopEntered(); + void virtualDesktopLeft(); + void geometryChanged(); + void skipTaskbarChanged(); + void applicationMenuChanged(); + void activitiesChanged(); + void parentWindowChanged(); + void initialStateDone(); + +protected: + void org_kde_plasma_window_unmapped() override; + void org_kde_plasma_window_title_changed(const QString &title) override; + void org_kde_plasma_window_app_id_changed(const QString &app_id) override; + void org_kde_plasma_window_icon_changed() override; + void org_kde_plasma_window_themed_icon_name_changed(const QString &name) override; + void org_kde_plasma_window_state_changed(uint32_t flags) override; + void org_kde_plasma_window_virtual_desktop_entered(const QString &id) override; + + void org_kde_plasma_window_virtual_desktop_left(const QString &id) override; + void org_kde_plasma_window_geometry(int32_t x, int32_t y, uint32_t width, uint32_t height) override; + void org_kde_plasma_window_application_menu(const QString &service_name, const QString &object_path) override; + void org_kde_plasma_window_activity_entered(const QString &id) override; + void org_kde_plasma_window_activity_left(const QString &id) override; + void org_kde_plasma_window_pid_changed(uint32_t pid) override; + void org_kde_plasma_window_resource_name_changed(const QString &resource_name) override; + void org_kde_plasma_window_parent_window(::org_kde_plasma_window *parent) override; + void org_kde_plasma_window_initial_state() override; + +private: + void setParentWindow(LXQtTaskBarPlasmaWindow *parent); + + QMetaObject::Connection parentWindowUnmappedConnection; +}; + +class LXQtTaskBarPlasmaWindowManagment : public QWaylandClientExtensionTemplate, + public QtWayland::org_kde_plasma_window_management +{ + Q_OBJECT +public: + static constexpr int version = 16; + + LXQtTaskBarPlasmaWindowManagment(); + ~LXQtTaskBarPlasmaWindowManagment(); + + inline bool isShowingDesktop() const { return m_isShowingDesktop; } + +protected: + void org_kde_plasma_window_management_show_desktop_changed(uint32_t state) override; + void org_kde_plasma_window_management_window_with_uuid(uint32_t id, const QString &uuid) override; + void org_kde_plasma_window_management_stacking_order_uuid_changed(const QString &uuids) override; + +Q_SIGNALS: + void windowCreated(LXQtTaskBarPlasmaWindow *window); + void stackingOrderChanged(const QString &uuids); + +private: + bool m_isShowingDesktop = false; +}; + +// class Q_DECL_HIDDEN WaylandTasksModel::Private +// { +// public: +// Private(WaylandTasksModel *q); +// QHash appDataCache; +// QHash lastActivated; +// PlasmaWindow *activeWindow = nullptr; +// std::vector> windows; +// // key=transient child, value=leader +// QHash transients; +// // key=leader, values=transient children +// QMultiHash transientsDemandingAttention; +// std::unique_ptr windowManagement; +// KSharedConfig::Ptr rulesConfig; +// KDirWatch *configWatcher = nullptr; +// VirtualDesktopInfo *virtualDesktopInfo = nullptr; +// static QUuid uuid; +// QList stackingOrder; + +// void init(); +// void initWayland(); +// auto findWindow(PlasmaWindow *window) const; +// void addWindow(PlasmaWindow *window); + +// const AppData &appData(PlasmaWindow *window); + +// QIcon icon(PlasmaWindow *window); + +// static QString mimeType(); +// static QString groupMimeType(); + +// void dataChanged(PlasmaWindow *window, int role); +// void dataChanged(PlasmaWindow *window, const QList &roles); + +// private: +// WaylandTasksModel *q; +// }; + +#endif // LXQTTASKBARPLASMAWINDOWMANAGMENT_H diff --git a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp new file mode 100644 index 000000000..0d39f4e17 --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.cpp @@ -0,0 +1,794 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + +#include "lxqtwmbackend_kwinwayland.h" + +#include "lxqttaskbarplasmawindowmanagment.h" +#include "lxqtplasmavirtualdesktop.h" + +#include +#include +#include + +auto findWindow(const std::vector>& windows, LXQtTaskBarPlasmaWindow *window) +{ + //TODO: use algorithms + auto end = windows.end(); + for(auto it = windows.begin(); it != end; it++) + { + if((*it).get() == window) + { + return it; + } + } + + return windows.end(); +} + +LXQtWMBackend_KWinWayland::LXQtWMBackend_KWinWayland(QObject *parent) : + ILXQtAbstractWMInterface(parent) +{ + m_managment.reset(new LXQtTaskBarPlasmaWindowManagment); + m_workspaceInfo.reset(new LXQtPlasmaWaylandWorkspaceInfo); + + connect(m_managment.get(), &LXQtTaskBarPlasmaWindowManagment::windowCreated, this, [this](LXQtTaskBarPlasmaWindow *window) { + connect(window, &LXQtTaskBarPlasmaWindow::initialStateDone, this, [this, window] { + addWindow(window); + }); + }); + + // connect(m_managment.get(), &LXQtTaskBarPlasmaWindowManagment::stackingOrderChanged, + // this, [this](const QString &order) { + // // stackingOrder = order.split(QLatin1Char(';')); + // // for (const auto &window : std::as_const(windows)) { + // // this->dataChanged(window.get(), StackingOrder); + // // } + // }); + + connect(m_workspaceInfo.get(), &LXQtPlasmaWaylandWorkspaceInfo::currentDesktopChanged, this, + [this](){ + int idx = m_workspaceInfo->position(m_workspaceInfo->currentDesktop()); + idx += 1; // Make 1-based + emit currentWorkspaceChanged(idx); + }); + + connect(m_workspaceInfo.get(), &LXQtPlasmaWaylandWorkspaceInfo::numberOfDesktopsChanged, + this, &ILXQtAbstractWMInterface::workspacesCountChanged); + + connect(m_workspaceInfo.get(), &LXQtPlasmaWaylandWorkspaceInfo::desktopNameChanged, + this, [this](int idx) { + emit workspaceNameChanged(idx + 1); // Make 1-based + }); +} + +bool LXQtWMBackend_KWinWayland::supportsAction(WId windowId, LXQtTaskBarBackendAction action) const +{ + if(action == LXQtTaskBarBackendAction::DesktopSwitch) + return true; + + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + LXQtTaskBarPlasmaWindow::state state; + + switch (action) + { + case LXQtTaskBarBackendAction::Move: + state = LXQtTaskBarPlasmaWindow::state::state_movable; + break; + + case LXQtTaskBarBackendAction::Resize: + state = LXQtTaskBarPlasmaWindow::state::state_resizable; + break; + + case LXQtTaskBarBackendAction::Maximize: + state = LXQtTaskBarPlasmaWindow::state::state_maximizable; + break; + + case LXQtTaskBarBackendAction::Minimize: + state = LXQtTaskBarPlasmaWindow::state::state_minimizable; + break; + + case LXQtTaskBarBackendAction::RollUp: + state = LXQtTaskBarPlasmaWindow::state::state_shadeable; + break; + + case LXQtTaskBarBackendAction::FullScreen: + state = LXQtTaskBarPlasmaWindow::state::state_fullscreenable; + break; + + default: + return false; + } + + return window->windowState.testFlag(state); +} + +bool LXQtWMBackend_KWinWayland::reloadWindows() +{ + const QVector wids = getCurrentWindows(); + + // Force removal and re-adding + for(WId windowId : wids) + { + emit windowRemoved(windowId); + } + for(WId windowId : wids) + { + emit windowAdded(windowId); + } + + return true; +} + +QVector LXQtWMBackend_KWinWayland::getCurrentWindows() const +{ + QVector wids; + wids.reserve(wids.size()); + + for(const std::unique_ptr& window : std::as_const(windows)) + { + if(window->acceptedInTaskBar) + wids << window->getWindowId(); + } + return wids; +} + +QString LXQtWMBackend_KWinWayland::getWindowTitle(WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return QString(); + + return window->title; +} + +bool LXQtWMBackend_KWinWayland::applicationDemandsAttention(WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + return window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_demands_attention) || transientsDemandingAttention.contains(window); +} + +QIcon LXQtWMBackend_KWinWayland::getApplicationIcon(WId windowId, int devicePixels) const +{ + Q_UNUSED(devicePixels) + + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return QIcon(); + + return window->icon; +} + +QString LXQtWMBackend_KWinWayland::getWindowClass(WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return QString(); + return window->appId; +} + +LXQtTaskBarWindowLayer LXQtWMBackend_KWinWayland::getWindowLayer(WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return LXQtTaskBarWindowLayer::Normal; + + if(window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_keep_above)) + return LXQtTaskBarWindowLayer::KeepAbove; + + if(window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_keep_below)) + return LXQtTaskBarWindowLayer::KeepBelow; + + return LXQtTaskBarWindowLayer::Normal; +} + +bool LXQtWMBackend_KWinWayland::setWindowLayer(WId windowId, LXQtTaskBarWindowLayer layer) +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + if(getWindowLayer(windowId) == layer) + return true; //TODO: make more efficient + + LXQtTaskBarPlasmaWindow::state plasmaState = LXQtTaskBarPlasmaWindow::state::state_keep_above; + switch (layer) + { + case LXQtTaskBarWindowLayer::Normal: + case LXQtTaskBarWindowLayer::KeepAbove: + break; + case LXQtTaskBarWindowLayer::KeepBelow: + plasmaState = LXQtTaskBarPlasmaWindow::state::state_keep_below; + break; + default: + return false; + } + + window->set_state(plasmaState, layer == LXQtTaskBarWindowLayer::Normal ? 0 : plasmaState); + return false; +} + +LXQtTaskBarWindowState LXQtWMBackend_KWinWayland::getWindowState(WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return LXQtTaskBarWindowState::Normal; + + if(window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_minimized)) + return LXQtTaskBarWindowState::Hidden; + if(window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_maximizable)) + return LXQtTaskBarWindowState::Maximized; + if(window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_shaded)) + return LXQtTaskBarWindowState::RolledUp; + if(window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_fullscreen)) + return LXQtTaskBarWindowState::FullScreen; + + return LXQtTaskBarWindowState::Normal; +} + +bool LXQtWMBackend_KWinWayland::setWindowState(WId windowId, LXQtTaskBarWindowState state, bool set) +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + LXQtTaskBarPlasmaWindow::state plasmaState; + switch (state) + { + case LXQtTaskBarWindowState::Minimized: + { + plasmaState = LXQtTaskBarPlasmaWindow::state::state_minimized; + break; + } + case LXQtTaskBarWindowState::Maximized: + case LXQtTaskBarWindowState::MaximizedVertically: + case LXQtTaskBarWindowState::MaximizedHorizontally: + { + plasmaState = LXQtTaskBarPlasmaWindow::state::state_maximized; + break; + } + case LXQtTaskBarWindowState::Normal: + { + plasmaState = LXQtTaskBarPlasmaWindow::state::state_maximized; + set = !set; //TODO: correct + break; + } + case LXQtTaskBarWindowState::RolledUp: + { + plasmaState = LXQtTaskBarPlasmaWindow::state::state_shaded; + break; + } + default: + return false; + } + + window->set_state(plasmaState, set ? plasmaState : 0); + return true; +} + +bool LXQtWMBackend_KWinWayland::isWindowActive(WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + return activeWindow == window || window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_active); +} + +bool LXQtWMBackend_KWinWayland::raiseWindow(WId windowId, bool onCurrentWorkSpace) +{ + Q_UNUSED(onCurrentWorkSpace) //TODO + + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + // Pull forward any transient demanding attention. + if (auto *transientDemandingAttention = transientsDemandingAttention.value(window)) + { + window = transientDemandingAttention; + } + else + { + // TODO Shouldn't KWin take care of that? + // Bringing a transient to the front usually brings its parent with it + // but focus is not handled properly. + // TODO take into account d->lastActivation instead + // of just taking the first one. + while (transients.key(window)) + { + window = transients.key(window); + } + } + + window->set_state(LXQtTaskBarPlasmaWindow::state::state_active, LXQtTaskBarPlasmaWindow::state::state_active); + return true; +} + +bool LXQtWMBackend_KWinWayland::closeWindow(WId windowId) +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + window->close(); + return true; +} + +WId LXQtWMBackend_KWinWayland::getActiveWindow() const +{ + if(activeWindow) + return activeWindow->getWindowId(); + return 0; +} + +int LXQtWMBackend_KWinWayland::getWorkspacesCount() const +{ + return m_workspaceInfo->numberOfDesktops(); +} + +QString LXQtWMBackend_KWinWayland::getWorkspaceName(int idx) const +{ + return m_workspaceInfo->getDesktopName(idx - 1); //Return to 0-based +} + +int LXQtWMBackend_KWinWayland::getCurrentWorkspace() const +{ + if(!m_workspaceInfo->currentDesktop().isValid()) + return 0; + return m_workspaceInfo->position(m_workspaceInfo->currentDesktop()) + 1; // 1-based +} + +bool LXQtWMBackend_KWinWayland::setCurrentWorkspace(int idx) +{ + QString id = m_workspaceInfo->getDesktopId(idx - 1); //Return to 0-based + if(id.isEmpty()) + return false; + m_workspaceInfo->requestActivate(id); + return true; +} + +int LXQtWMBackend_KWinWayland::getWindowWorkspace(WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return 0; + + // TODO: this protocol seems to allow multiple desktop for each window + // We do not support that yet + // Also from KDE Plasma task switch it's not clear how to actually put + // a window on multiple desktops (which is different from "All desktops") + QString id = window->virtualDesktops.value(0, QString()); + if(id.isEmpty()) + return 0; + + return m_workspaceInfo->position(id) + 1; //Make 1-based +} + +bool LXQtWMBackend_KWinWayland::setWindowOnWorkspace(WId windowId, int idx) +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + // Prepare for future multiple virtual desktops per window + QList newDesktops; + + // Fill the list + newDesktops.append(m_workspaceInfo->getDesktopId(idx - 1)); //Return to 0-based + + // Keep only valid IDs + newDesktops.erase(std::remove_if(newDesktops.begin(), newDesktops.end(), + [](const QString& id) { return id.isEmpty(); }), + newDesktops.end()); + + // Add to new requested desktops + for(const QString& id : std::as_const(newDesktops)) + { + if(!window->virtualDesktops.contains(id)) + window->request_enter_virtual_desktop(id); + } + + // Remove from non-requested destops + const QList currentDesktops = window->virtualDesktops; + for(const QString& id : std::as_const(currentDesktops)) + { + if(!newDesktops.contains(id)) + window->request_leave_virtual_desktop(id); + } + + return true; +} + +void LXQtWMBackend_KWinWayland::moveApplicationToPrevNextMonitor(WId windowId, bool next, bool raiseOnCurrentDesktop) +{ + +} + +bool LXQtWMBackend_KWinWayland::isWindowOnScreen(QScreen *screen, WId windowId) const +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return false; + + return screen->geometry().intersects(window->geometry); +} + +bool LXQtWMBackend_KWinWayland::setDesktopLayout(Qt::Orientation, int, int, bool) +{ + //TODO: implement + return false; +} + +void LXQtWMBackend_KWinWayland::moveApplication(WId windowId) +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return; + + window->set_state(LXQtTaskBarPlasmaWindow::state::state_active, LXQtTaskBarPlasmaWindow::state::state_active); + window->request_move(); +} + +void LXQtWMBackend_KWinWayland::resizeApplication(WId windowId) +{ + LXQtTaskBarPlasmaWindow *window = getWindow(windowId); + if(!window) + return; + + window->set_state(LXQtTaskBarPlasmaWindow::state::state_active, LXQtTaskBarPlasmaWindow::state::state_active); + window->request_resize(); +} + +void LXQtWMBackend_KWinWayland::refreshIconGeometry(WId windowId, const QRect &geom) +{ + +} + +bool LXQtWMBackend_KWinWayland::isAreaOverlapped(const QRect &area) const +{ + for(auto &window : std::as_const(windows)) + { + if(window->geometry.intersects(area)) + return true; + } + return false; +} + +bool LXQtWMBackend_KWinWayland::isShowingDesktop() const +{ + return m_managment->isActive() ? m_managment->isShowingDesktop() : false; +} + +bool LXQtWMBackend_KWinWayland::showDesktop(bool value) +{ + if(!m_managment->isActive()) + return false; + + enum LXQtTaskBarPlasmaWindowManagment::show_desktop flag_; + if(value) + flag_ = LXQtTaskBarPlasmaWindowManagment::show_desktop::show_desktop_enabled; + else + flag_ = LXQtTaskBarPlasmaWindowManagment::show_desktop::show_desktop_disabled; + + m_managment->show_desktop(flag_); + return true; +} + +void LXQtWMBackend_KWinWayland::addWindow(LXQtTaskBarPlasmaWindow *window) +{ + if (findWindow(windows, window) != windows.end() || transients.contains(window)) + { + return; + } + + auto removeWindow = [window, this] + { + auto it = findWindow(windows, window); + if (it != windows.end()) + { + if(window->acceptedInTaskBar) + emit windowRemoved(window->getWindowId()); + windows.erase(it); + transientsDemandingAttention.remove(window); + lastActivated.remove(window); + } + else + { + // Could be a transient. + // Removing a transient might change the demands attention state of the leader. + if (transients.remove(window)) + { + if (LXQtTaskBarPlasmaWindow *leader = transientsDemandingAttention.key(window)) { + transientsDemandingAttention.remove(leader, window); + emit windowPropertyChanged(leader->getWindowId(), int(LXQtTaskBarWindowProperty::Urgency)); + } + } + } + + if (activeWindow == window) + { + activeWindow = nullptr; + emit activeWindowChanged(0); + } + }; + + connect(window, &LXQtTaskBarPlasmaWindow::unmapped, this, removeWindow); + + connect(window, &LXQtTaskBarPlasmaWindow::titleChanged, this, [window, this] { + updateWindowAcceptance(window); + if(window->acceptedInTaskBar) + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::Title)); + }); + + connect(window, &LXQtTaskBarPlasmaWindow::iconChanged, this, [window, this] { + updateWindowAcceptance(window); + if(window->acceptedInTaskBar) + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::Icon)); + }); + + connect(window, &LXQtTaskBarPlasmaWindow::geometryChanged, this, [window, this] { + updateWindowAcceptance(window); + if(window->acceptedInTaskBar) + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::Geometry)); + }); + + connect(window, &LXQtTaskBarPlasmaWindow::appIdChanged, this, [window, this] { + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::WindowClass)); + }); + + if (window->windowState & LXQtTaskBarPlasmaWindow::state::state_active) { + LXQtTaskBarPlasmaWindow *effectiveActive = window; + while (effectiveActive->parentWindow) { + effectiveActive = effectiveActive->parentWindow; + } + + lastActivated[effectiveActive] = QTime::currentTime(); + activeWindow = effectiveActive; + } + + connect(window, &LXQtTaskBarPlasmaWindow::activeChanged, this, [window, this] { + const bool active = window->windowState & LXQtTaskBarPlasmaWindow::state::state_active; + + LXQtTaskBarPlasmaWindow *effectiveWindow = window; + + while (effectiveWindow->parentWindow) + { + effectiveWindow = effectiveWindow->parentWindow; + } + + if (active) + { + lastActivated[effectiveWindow] = QTime::currentTime(); + + if (activeWindow != effectiveWindow) + { + activeWindow = effectiveWindow; + emit activeWindowChanged(activeWindow->getWindowId()); + } + } + else + { + // NOTE: LXQtTaskGroup does not handle well null active window + // This would break minimize on click functionality. + // Since window is deactivated because another window became active, + // we pretend to still be active until we receive signal from newly active + // window which will register itself and emit activeWindowChanged() as above + + // if (activeWindow == effectiveWindow) + // { + // activeWindow = nullptr; + // emit activeWindowChanged(0); + // } + } + }); + + connect(window, &LXQtTaskBarPlasmaWindow::parentWindowChanged, this, [window, this] { + LXQtTaskBarPlasmaWindow *leader = window->parentWindow.data(); + + // Migrate demanding attention to new leader. + if (window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_demands_attention)) + { + if (auto *oldLeader = transientsDemandingAttention.key(window)) + { + if (window->parentWindow != oldLeader) + { + transientsDemandingAttention.remove(oldLeader, window); + transientsDemandingAttention.insert(leader, window); + emit windowPropertyChanged(oldLeader->getWindowId(), int(LXQtTaskBarWindowProperty::Urgency)); + emit windowPropertyChanged(leader->getWindowId(), int(LXQtTaskBarWindowProperty::Urgency)); + } + } + } + + if (transients.remove(window)) + { + if (leader) + { + // leader change. + transients.insert(window, leader); + } + else + { + // lost a leader, add to regular windows list. + Q_ASSERT(findWindow(windows, window) == windows.end()); + + windows.emplace_back(window); + } + } + else if (leader) + { + // gained a leader, remove from regular windows list. + auto it = findWindow(windows, window); + Q_ASSERT(it != windows.end()); + + windows.erase(it); + lastActivated.remove(window); + } + }); + + auto stateChanged = [window, this] { + updateWindowAcceptance(window); + if(window->acceptedInTaskBar) + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::State)); + }; + + connect(window, &LXQtTaskBarPlasmaWindow::fullscreenChanged, this, stateChanged); + + connect(window, &LXQtTaskBarPlasmaWindow::maximizedChanged, this, stateChanged); + + connect(window, &LXQtTaskBarPlasmaWindow::minimizedChanged, this, stateChanged); + + connect(window, &LXQtTaskBarPlasmaWindow::shadedChanged, this, stateChanged); + + auto workspaceChanged = [window, this] { + updateWindowAcceptance(window); + if(window->acceptedInTaskBar) + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::Workspace)); + }; + + connect(window, &LXQtTaskBarPlasmaWindow::virtualDesktopEntered, this, workspaceChanged); + connect(window, &LXQtTaskBarPlasmaWindow::virtualDesktopLeft, this, workspaceChanged); + + connect(window, &LXQtTaskBarPlasmaWindow::demandsAttentionChanged, this, [window, this] { + // Changes to a transient's state might change demands attention state for leader. + if (auto *leader = transients.value(window)) + { + if (window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_demands_attention)) + { + if (!transientsDemandingAttention.values(leader).contains(window)) + { + transientsDemandingAttention.insert(leader, window); + emit windowPropertyChanged(leader->getWindowId(), int(LXQtTaskBarWindowProperty::Urgency)); + } + } + else if (transientsDemandingAttention.remove(window)) + { + emit windowPropertyChanged(leader->getWindowId(), int(LXQtTaskBarWindowProperty::Urgency)); + } + } + else + { + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::Urgency)); + } + }); + + connect(window, &LXQtTaskBarPlasmaWindow::skipTaskbarChanged, this, [window, this] { + updateWindowAcceptance(window); + }); + + // QObject::connect(window, &PlasmaWindow::applicationMenuChanged, q, [window, this] { + // this->dataChanged(window, QList{ApplicationMenuServiceName, ApplicationMenuObjectPath}); + // }); + + // Handle transient. + if (LXQtTaskBarPlasmaWindow *leader = window->parentWindow.data()) + { + transients.insert(window, leader); + + // Update demands attention state for leader. + if (window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_demands_attention)) + { + transientsDemandingAttention.insert(leader, window); + if(leader->acceptedInTaskBar) + emit windowPropertyChanged(leader->getWindowId(), int(LXQtTaskBarWindowProperty::Urgency)); + } + } + else + { + windows.emplace_back(window); + updateWindowAcceptance(window); + } +} + +bool LXQtWMBackend_KWinWayland::acceptWindow(LXQtTaskBarPlasmaWindow *window) const +{ + if(window->windowState.testFlag(LXQtTaskBarPlasmaWindow::state::state_skiptaskbar)) + return false; + + if(transients.contains(window)) + return false; + + return true; +} + +void LXQtWMBackend_KWinWayland::updateWindowAcceptance(LXQtTaskBarPlasmaWindow *window) +{ + if(!window->acceptedInTaskBar && acceptWindow(window)) + { + window->acceptedInTaskBar = true; + emit windowAdded(window->getWindowId()); + } + else if(window->acceptedInTaskBar && !acceptWindow(window)) + { + window->acceptedInTaskBar = false; + emit windowRemoved(window->getWindowId()); + } +} + +LXQtTaskBarPlasmaWindow *LXQtWMBackend_KWinWayland::getWindow(WId windowId) const +{ + for(auto &window : std::as_const(windows)) + { + if(window->getWindowId() == windowId) + return window.get(); + } + + return nullptr; +} + +int LXQtWMBackendKWinWaylandLibrary::getBackendScore(const QString &key) const +{ + auto *waylandApplication = qGuiApp->nativeInterface(); + if(!waylandApplication) + return 0; + + // Detect KWin Plasma (Wayland) + if(key == QLatin1String("KDE") || key == QLatin1String("KWIN")) + { + return 100; + } + + // kwin_wayland compositor, but not full-blown DE + else if (key == QLatin1String("kwin_wayland")) + { + return 100; + } + + // It's not useful for other wayland compositors + return 0; +} + +ILXQtAbstractWMInterface *LXQtWMBackendKWinWaylandLibrary::instance() const +{ + return new LXQtWMBackend_KWinWayland; +} diff --git a/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.h b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.h new file mode 100644 index 000000000..d44ae6ed6 --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/lxqtwmbackend_kwinwayland.h @@ -0,0 +1,135 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + +#ifndef LXQT_WM_BACKEND_KWIN_WAYLAND_H +#define LXQT_WM_BACKEND_KWIN_WAYLAND_H + +#include "../../ilxqtabstractwmiface.h" + +#include +#include +#include + +class LXQtTaskBarPlasmaWindow; +class LXQtTaskBarPlasmaWindowManagment; +class LXQtPlasmaWaylandWorkspaceInfo; + + +class LXQtWMBackend_KWinWayland : public ILXQtAbstractWMInterface +{ + Q_OBJECT + +public: + explicit LXQtWMBackend_KWinWayland(QObject *parent = nullptr); + + // Backend + virtual bool supportsAction(WId windowId, LXQtTaskBarBackendAction action) const override; + + // Windows + virtual bool reloadWindows() override; + + virtual QVector getCurrentWindows() const override; + virtual QString getWindowTitle(WId windowId) const override; + virtual bool applicationDemandsAttention(WId windowId) const override; + virtual QIcon getApplicationIcon(WId windowId, int devicePixels) const override; + virtual QString getWindowClass(WId windowId) const override; + + virtual LXQtTaskBarWindowLayer getWindowLayer(WId windowId) const override; + virtual bool setWindowLayer(WId windowId, LXQtTaskBarWindowLayer layer) override; + + virtual LXQtTaskBarWindowState getWindowState(WId windowId) const override; + virtual bool setWindowState(WId windowId, LXQtTaskBarWindowState state, bool set) override; + + virtual bool isWindowActive(WId windowId) const override; + virtual bool raiseWindow(WId windowId, bool onCurrentWorkSpace) override; + + virtual bool closeWindow(WId windowId) override; + + virtual WId getActiveWindow() const override; + + // Workspaces + virtual int getWorkspacesCount() const override; + virtual QString getWorkspaceName(int idx) const override; + + virtual int getCurrentWorkspace() const override; + virtual bool setCurrentWorkspace(int idx) override; + + virtual int getWindowWorkspace(WId windowId) const override; + virtual bool setWindowOnWorkspace(WId windowId, int idx) override; + + virtual void moveApplicationToPrevNextMonitor(WId windowId, bool next, bool raiseOnCurrentDesktop) override; + + virtual bool isWindowOnScreen(QScreen *screen, WId windowId) const override; + + virtual bool setDesktopLayout(Qt::Orientation orientation, int rows, int columns, bool rightToLeft) override; + + // X11 Specific + virtual void moveApplication(WId windowId) override; + virtual void resizeApplication(WId windowId) override; + + virtual void refreshIconGeometry(WId windowId, const QRect &geom) override; + + // Panel internal + virtual bool isAreaOverlapped(const QRect& area) const override; + + // Show Destop + virtual bool isShowingDesktop() const override; + virtual bool showDesktop(bool value) override; + +private: + void addWindow(LXQtTaskBarPlasmaWindow *window); + bool acceptWindow(LXQtTaskBarPlasmaWindow *window) const; + void updateWindowAcceptance(LXQtTaskBarPlasmaWindow *window); + +private: + LXQtTaskBarPlasmaWindow *getWindow(WId windowId) const; + + std::unique_ptr m_workspaceInfo; + + std::unique_ptr m_managment; + + QHash lastActivated; + LXQtTaskBarPlasmaWindow *activeWindow = nullptr; + std::vector> windows; + // key=transient child, value=leader + QHash transients; + // key=leader, values=transient children + QMultiHash transientsDemandingAttention; +}; + +class LXQtWMBackendKWinWaylandLibrary: public QObject, public ILXQtWMBackendLibrary +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "lxqt.org/Panel/WMInterface/1.0") + Q_INTERFACES(ILXQtWMBackendLibrary) +public: + int getBackendScore(const QString& key) const override; + + ILXQtAbstractWMInterface* instance() const override; +}; + +#endif // LXQT_WM_BACKEND_KWIN_WAYLAND_H diff --git a/panel/backends/wayland/kwin_wayland/protocols/org-kde-plasma-virtual-desktop.xml b/panel/backends/wayland/kwin_wayland/protocols/org-kde-plasma-virtual-desktop.xml new file mode 100644 index 000000000..0e0551b0a --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/protocols/org-kde-plasma-virtual-desktop.xml @@ -0,0 +1,110 @@ + + + + + + + + Given the id of a particular virtual desktop, get the corresponding org_kde_plasma_virtual_desktop which represents only the desktop with that id. + + + + + + + + Ask the server to create a new virtual desktop, and position it at a specified position. If the position is zero or less, it will be positioned at the beginning, if the position is the count or more, it will be positioned at the end. + + + + + + + + Ask the server to get rid of a virtual desktop, the server may or may not acconsent to the request. + + + + + + + + + + + + + + + + + + + This event is sent after all other properties has been + sent after binding to the desktop manager object and after any + other property changes done after that. This allows + changes to the org_kde_plasma_virtual_desktop_management properties to be seen as + atomic, even if they happen via multiple events. + + + + + + + + + + + + + Request the server to set the status of this desktop to active: The server is free to consent or deny the request. This will be the new "current" virtual desktop of the system. + + + + + + The format of the id is decided by the compositor implementation. A desktop id univocally identifies a virtual desktop and must be guaranteed to never exist two desktops with the same id. The format of the string id is up to the server implementation. + + + + + + + + + + + The desktop will be the new "current" desktop of the system. The server may support either one virtual desktop active at a time, or other combinations such as one virtual desktop active per screen. + Windows associated to this virtual desktop will be shown. + + + + + + Windows that were associated only to this desktop will be hidden. + + + + + + This event is sent after all other properties has been + sent after binding to the desktop object and after any + other property changes done after that. This allows + changes to the org_kde_plasma_virtual_desktop properties to be seen as + atomic, even if they happen via multiple events. + + + + + + This virtual desktop has just been removed by the server: + All windows will lose the association to this desktop. + + + + + diff --git a/panel/backends/wayland/kwin_wayland/protocols/plasma-window-management.xml b/panel/backends/wayland/kwin_wayland/protocols/plasma-window-management.xml new file mode 100644 index 000000000..5990ebfda --- /dev/null +++ b/panel/backends/wayland/kwin_wayland/protocols/plasma-window-management.xml @@ -0,0 +1,425 @@ + + + + + + + This interface manages application windows. + It provides requests to show and hide the desktop and emits + an event every time a window is created so that the client can + use it to manage the window. + + Only one client can bind this interface at a time. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tell the compositor to show/hide the desktop. + + + + + + Deprecated: use get_window_by_uuid + + + + + + + + + + + + This event will be sent whenever the show desktop mode changes. E.g. when it is entered + or left. + + On binding the interface the current state is sent. + + + + + + + This event will be sent immediately after a window is mapped. + + + + + + + This event will be sent when stacking order changed and on bind + + + + + + + This event will be sent when stacking order changed and on bind + + + + + + + This event will be sent immediately after a window is mapped. + + + + + + + + + Manages and control an application window. + + Only one client can bind this interface at a time. + + + + + Set window state. + + Values for state argument are described by org_kde_plasma_window_management.state + and can be used together in a bitfield. The flags bitfield describes which flags are + supposed to be set, the state bitfield the value for the set flags + + + + + + + + Deprecated: use enter_virtual_desktop + Maps the window to a different virtual desktop. + + To show the window on all virtual desktops, call the + org_kde_plasma_window.set_state request and specify a on_all_desktops + state in the bitfield. + + + + + + + Sets the geometry of the taskbar entry for this window. + The geometry is relative to a panel in particular. + + + + + + + + + + + Remove the task geometry information for a particular panel. + + + + + + + + + Close this window. + + + + + + Request an interactive move for this window. + + + + + + Request an interactive resize for this window. + + + + + + Removes the resource bound for this org_kde_plasma_window. + + + + + + The compositor will write the window icon into the provided file descriptor. + The data is a serialized QIcon with QDataStream. + + + + + + + This event will be sent as soon as the window title is changed. + + + + + + + This event will be sent as soon as the application + identifier is changed. + + + + + + + This event will be sent as soon as the window state changes. + + Values for state argument are described by org_kde_plasma_window_management.state. + + + + + + + DEPRECATED: use virtual_desktop_entered and virtual_desktop_left instead + This event will be sent when a window is moved to another + virtual desktop. + + It is not sent if it becomes visible on all virtual desktops though. + + + + + + + This event will be sent whenever the themed icon name changes. May be null. + + + + + + + This event will be sent immediately after the window is closed + and its surface is unmapped. + + + + + + This event will be sent immediately after all initial state been sent to the client. + If the Plasma window is already unmapped, the unmapped event will be sent before the + initial_state event. + + + + + + This event will be sent whenever the parent window of this org_kde_plasma_window changes. + The passed parent is another org_kde_plasma_window and this org_kde_plasma_window is a + transient window to the parent window. If the parent argument is null, this + org_kde_plasma_window does not have a parent window. + + + + + + + This event will be sent whenever the window geometry of this org_kde_plasma_window changes. + The coordinates are in absolute coordinates of the windowing system. + + + + + + + + + + This event will be sent whenever the icon of the window changes, but there is no themed + icon name. Common examples are Xwayland windows which have a pixmap based icon. + + The client can request the icon using get_icon. + + + + + + This event will be sent when the compositor has set the process id this window belongs to. + This should be set once before the initial_state is sent. + + + + + + + + + + Make the window enter a virtual desktop. A window can enter more + than one virtual desktop. if the id is empty or invalid, no action will be performed. + + + + + + RFC: do this with an empty id to request_enter_virtual_desktop? + Make the window enter a new virtual desktop. If the server consents the request, + it will create a new virtual desktop and assign the window to it. + + + + + + Make the window exit a virtual desktop. If it exits all desktops it will be considered on all of them. + + + + + + + This event will be sent when the window has entered a new virtual desktop. The window can be on more than one desktop, or none: then is considered on all of them. + + + + + + + This event will be sent when the window left a virtual desktop. If the window leaves all desktops, it can be considered on all. + If the window gets manually added on all desktops, the server has to send virtual_desktop_left for every previous desktop it was in for the window to be really considered on all desktops. + + + + + + + + + This event will be sent after the application menu + for the window has changed. + + + + + + + + Make the window enter an activity. A window can enter more activity. If the id is empty or invalid, no action will be performed. + + + + + + + Make the window exit a an activity. If it exits all activities it will be considered on all of them. + + + + + + + This event will be sent when the window has entered an activity. The window can be on more than one activity, or none: then is considered on all of them. + + + + + + + This event will be sent when the window left an activity. If the window leaves all activities, it will be considered on all. + If the window gets manually added on all activities, the server has to send activity_left for every previous activity it was in for the window to be really considered on all activities. + + + + + + + Requests this window to be displayed in a specific output. + + + + + + + This event will be sent when the X11 resource name of the window has changed. + This is only set for XWayland windows. + + + + + + + + The activation manager interface provides a way to get notified + when an application is about to be activated. + + + + + Destroy the activation manager object. The activation objects introduced + by this manager object will be unaffected. + + + + + + Will be issued when an app is set to be activated. It offers + an instance of org_kde_plasma_activation that will tell us the app_id + and the extent of the activation. + + + + + + + + + Notify the compositor that the org_kde_plasma_activation object will no + longer be used. + + + + + + + + + + + + + diff --git a/panel/backends/wayland/protocols/org-kde-plasma-virtual-desktop.xml b/panel/backends/wayland/protocols/org-kde-plasma-virtual-desktop.xml new file mode 100644 index 000000000..0e0551b0a --- /dev/null +++ b/panel/backends/wayland/protocols/org-kde-plasma-virtual-desktop.xml @@ -0,0 +1,110 @@ + + + + + + + + Given the id of a particular virtual desktop, get the corresponding org_kde_plasma_virtual_desktop which represents only the desktop with that id. + + + + + + + + Ask the server to create a new virtual desktop, and position it at a specified position. If the position is zero or less, it will be positioned at the beginning, if the position is the count or more, it will be positioned at the end. + + + + + + + + Ask the server to get rid of a virtual desktop, the server may or may not acconsent to the request. + + + + + + + + + + + + + + + + + + + This event is sent after all other properties has been + sent after binding to the desktop manager object and after any + other property changes done after that. This allows + changes to the org_kde_plasma_virtual_desktop_management properties to be seen as + atomic, even if they happen via multiple events. + + + + + + + + + + + + + Request the server to set the status of this desktop to active: The server is free to consent or deny the request. This will be the new "current" virtual desktop of the system. + + + + + + The format of the id is decided by the compositor implementation. A desktop id univocally identifies a virtual desktop and must be guaranteed to never exist two desktops with the same id. The format of the string id is up to the server implementation. + + + + + + + + + + + The desktop will be the new "current" desktop of the system. The server may support either one virtual desktop active at a time, or other combinations such as one virtual desktop active per screen. + Windows associated to this virtual desktop will be shown. + + + + + + Windows that were associated only to this desktop will be hidden. + + + + + + This event is sent after all other properties has been + sent after binding to the desktop object and after any + other property changes done after that. This allows + changes to the org_kde_plasma_virtual_desktop properties to be seen as + atomic, even if they happen via multiple events. + + + + + + This virtual desktop has just been removed by the server: + All windows will lose the association to this desktop. + + + + + diff --git a/panel/backends/wayland/protocols/plasma-window-management.xml b/panel/backends/wayland/protocols/plasma-window-management.xml new file mode 100644 index 000000000..5990ebfda --- /dev/null +++ b/panel/backends/wayland/protocols/plasma-window-management.xml @@ -0,0 +1,425 @@ + + + + + + + This interface manages application windows. + It provides requests to show and hide the desktop and emits + an event every time a window is created so that the client can + use it to manage the window. + + Only one client can bind this interface at a time. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tell the compositor to show/hide the desktop. + + + + + + Deprecated: use get_window_by_uuid + + + + + + + + + + + + This event will be sent whenever the show desktop mode changes. E.g. when it is entered + or left. + + On binding the interface the current state is sent. + + + + + + + This event will be sent immediately after a window is mapped. + + + + + + + This event will be sent when stacking order changed and on bind + + + + + + + This event will be sent when stacking order changed and on bind + + + + + + + This event will be sent immediately after a window is mapped. + + + + + + + + + Manages and control an application window. + + Only one client can bind this interface at a time. + + + + + Set window state. + + Values for state argument are described by org_kde_plasma_window_management.state + and can be used together in a bitfield. The flags bitfield describes which flags are + supposed to be set, the state bitfield the value for the set flags + + + + + + + + Deprecated: use enter_virtual_desktop + Maps the window to a different virtual desktop. + + To show the window on all virtual desktops, call the + org_kde_plasma_window.set_state request and specify a on_all_desktops + state in the bitfield. + + + + + + + Sets the geometry of the taskbar entry for this window. + The geometry is relative to a panel in particular. + + + + + + + + + + + Remove the task geometry information for a particular panel. + + + + + + + + + Close this window. + + + + + + Request an interactive move for this window. + + + + + + Request an interactive resize for this window. + + + + + + Removes the resource bound for this org_kde_plasma_window. + + + + + + The compositor will write the window icon into the provided file descriptor. + The data is a serialized QIcon with QDataStream. + + + + + + + This event will be sent as soon as the window title is changed. + + + + + + + This event will be sent as soon as the application + identifier is changed. + + + + + + + This event will be sent as soon as the window state changes. + + Values for state argument are described by org_kde_plasma_window_management.state. + + + + + + + DEPRECATED: use virtual_desktop_entered and virtual_desktop_left instead + This event will be sent when a window is moved to another + virtual desktop. + + It is not sent if it becomes visible on all virtual desktops though. + + + + + + + This event will be sent whenever the themed icon name changes. May be null. + + + + + + + This event will be sent immediately after the window is closed + and its surface is unmapped. + + + + + + This event will be sent immediately after all initial state been sent to the client. + If the Plasma window is already unmapped, the unmapped event will be sent before the + initial_state event. + + + + + + This event will be sent whenever the parent window of this org_kde_plasma_window changes. + The passed parent is another org_kde_plasma_window and this org_kde_plasma_window is a + transient window to the parent window. If the parent argument is null, this + org_kde_plasma_window does not have a parent window. + + + + + + + This event will be sent whenever the window geometry of this org_kde_plasma_window changes. + The coordinates are in absolute coordinates of the windowing system. + + + + + + + + + + This event will be sent whenever the icon of the window changes, but there is no themed + icon name. Common examples are Xwayland windows which have a pixmap based icon. + + The client can request the icon using get_icon. + + + + + + This event will be sent when the compositor has set the process id this window belongs to. + This should be set once before the initial_state is sent. + + + + + + + + + + Make the window enter a virtual desktop. A window can enter more + than one virtual desktop. if the id is empty or invalid, no action will be performed. + + + + + + RFC: do this with an empty id to request_enter_virtual_desktop? + Make the window enter a new virtual desktop. If the server consents the request, + it will create a new virtual desktop and assign the window to it. + + + + + + Make the window exit a virtual desktop. If it exits all desktops it will be considered on all of them. + + + + + + + This event will be sent when the window has entered a new virtual desktop. The window can be on more than one desktop, or none: then is considered on all of them. + + + + + + + This event will be sent when the window left a virtual desktop. If the window leaves all desktops, it can be considered on all. + If the window gets manually added on all desktops, the server has to send virtual_desktop_left for every previous desktop it was in for the window to be really considered on all desktops. + + + + + + + + + This event will be sent after the application menu + for the window has changed. + + + + + + + + Make the window enter an activity. A window can enter more activity. If the id is empty or invalid, no action will be performed. + + + + + + + Make the window exit a an activity. If it exits all activities it will be considered on all of them. + + + + + + + This event will be sent when the window has entered an activity. The window can be on more than one activity, or none: then is considered on all of them. + + + + + + + This event will be sent when the window left an activity. If the window leaves all activities, it will be considered on all. + If the window gets manually added on all activities, the server has to send activity_left for every previous activity it was in for the window to be really considered on all activities. + + + + + + + Requests this window to be displayed in a specific output. + + + + + + + This event will be sent when the X11 resource name of the window has changed. + This is only set for XWayland windows. + + + + + + + + The activation manager interface provides a way to get notified + when an application is about to be activated. + + + + + Destroy the activation manager object. The activation objects introduced + by this manager object will be unaffected. + + + + + + Will be issued when an app is set to be activated. It offers + an instance of org_kde_plasma_activation that will tell us the app_id + and the extent of the activation. + + + + + + + + + Notify the compositor that the org_kde_plasma_activation object will no + longer be used. + + + + + + + + + + + + + diff --git a/panel/backends/wayland/wlroots/CMakeLists.txt b/panel/backends/wayland/wlroots/CMakeLists.txt new file mode 100644 index 000000000..aa53d5a99 --- /dev/null +++ b/panel/backends/wayland/wlroots/CMakeLists.txt @@ -0,0 +1,35 @@ +set(PLATFORM_NAME wlroots) + +set(PREFIX_NAME wmbackend) +set(PROGRAM "lxqt-panel") +set(BACKEND "backend") +set(NAME ${PREFIX_NAME}_${PLATFORM_NAME}) +project(${PROGRAM}_${BACKEND}_${NAME}) + +find_package(Qt6 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS WaylandClient Concurrent) +find_package(Qt6Xdg) + +set(PROG_SHARE_DIR ${CMAKE_INSTALL_FULL_DATAROOTDIR}/lxqt/${PROGRAM}/${BACKEND}) +set(PLUGIN_SHARE_DIR ${PROG_SHARE_DIR}/${BACKEND}/${NAME}) +#************************************************ + +if (NOT DEFINED PLUGIN_DIR) + set (PLUGIN_DIR ${CMAKE_INSTALL_FULL_LIBDIR}/${PROGRAM}) +endif (NOT DEFINED PLUGIN_DIR) + +set(QTX_LIBRARIES Qt6::Gui Qt6::GuiPrivate) + +set( + SRC + lxqtwmbackend_wlr.cpp lxqtwmbackend_wlr.h + lxqttaskbarwlrwm.cpp lxqttaskbarwlrwm.h +) + +add_library(${NAME} MODULE ${SRC}) # build dynamically loadable modules +install(TARGETS ${NAME} DESTINATION ${PLUGIN_DIR}/${BACKEND}) # install the *.so file + +target_link_libraries(${NAME} ${QTX_LIBRARIES} Qt6::Concurrent Qt6::WaylandClient Qt6Xdg) + +qt6_generate_wayland_protocol_client_sources(${NAME} FILES + ${CMAKE_CURRENT_SOURCE_DIR}/wlr-foreign-toplevel-management-unstable-v1.xml +) diff --git a/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.cpp b/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.cpp new file mode 100644 index 000000000..7a84ab630 --- /dev/null +++ b/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.cpp @@ -0,0 +1,545 @@ +#include "lxqttaskbarwlrwm.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include + +QString U8Str( const char *str ) { + return QString::fromUtf8( str ); +} + +static inline QString getPixmapIcon(QString name) +{ + QStringList paths{ + U8Str("/usr/local/share/pixmaps/"), + U8Str("/usr/share/pixmaps/"), + }; + + QStringList sfxs{ + U8Str( ".svg" ), U8Str( ".png" ), U8Str( ".xpm" ) + }; + + for (QString path: paths) + { + for (QString sfx: sfxs) + { + if (QFile::exists(path + name + sfx)) + { + return path + name + sfx; + } + } + } + + return QString(); +} + + +QIcon getIconForAppId(QString mAppId) +{ + if (mAppId.isEmpty() or (mAppId == U8Str("Unknown"))) + { + return QIcon(); + } + + /** Wine apps */ + if (mAppId.endsWith(U8Str(".exe"))) + { + return QIcon::fromTheme(U8Str("wine")); + } + + /** Check if a theme icon exists called @mAppId */ + if (QIcon::hasThemeIcon(mAppId)) + { + return QIcon::fromTheme(mAppId); + } + + /** Check if the theme icon is @mAppId, but all lower-case letters */ + else if (QIcon::hasThemeIcon(mAppId.toLower())) + { + return QIcon::fromTheme(mAppId.toLower()); + } + + QStringList appDirs = { + QDir::home().filePath(U8Str(".local/share/applications/")), + U8Str("/usr/local/share/applications/"), + U8Str("/usr/share/applications/"), + }; + + /** + * Assume mAppId == desktop-file-name (ideal situation) + * or mAppId.toLower() == desktop-file-name (cheap fallback) + */ + QString iconName; + + for (QString path: appDirs) + { + /** Get the icon name from desktop (mAppId: as it is) */ + if (QFile::exists(path + mAppId + U8Str(".desktop"))) + { + QSettings desktop(path + mAppId + U8Str(".desktop"), QSettings::IniFormat); + iconName = desktop.value(U8Str("Desktop Entry/Icon")).toString(); + } + + /** Get the icon name from desktop (mAppId: all lower-case letters) */ + else if (QFile::exists(path + mAppId.toLower() + U8Str(".desktop"))) + { + QSettings desktop(path + mAppId.toLower() + U8Str(".desktop"), QSettings::IniFormat); + iconName = desktop.value(U8Str("Desktop Entry/Icon")).toString(); + } + + /** No icon specified: try else-where */ + if (iconName.isEmpty()) + { + continue; + } + + /** We got an iconName, and it's in the current theme */ + if (QIcon::hasThemeIcon(iconName)) + { + return QIcon::fromTheme(iconName); + } + + /** Not a theme icon, but an absolute path */ + else if (QFile::exists(iconName)) + { + return QIcon(iconName); + } + + /** Not theme icon or absolute path. So check /usr/share/pixmaps/ */ + else + { + iconName = getPixmapIcon(iconName); + + if (not iconName.isEmpty()) + { + return QIcon(iconName); + } + } + } + + /* Check all desktop files for @mAppId */ + for (QString path: appDirs) + { + QStringList desktops = QDir(path).entryList({ U8Str("*.desktop") }); + for (QString dskf: desktops) + { + QSettings desktop(path + dskf, QSettings::IniFormat); + + QString exec = desktop.value(U8Str("Desktop Entry/Exec"), U8Str("abcd1234/-")).toString(); + QString name = desktop.value(U8Str("Desktop Entry/Name"), U8Str("abcd1234/-")).toString(); + QString cls = desktop.value(U8Str("Desktop Entry/StartupWMClass"), U8Str("abcd1234/-")).toString(); + + QString execPath = U8Str(std::filesystem::path(exec.toStdString()).filename().c_str()); + + if (mAppId.compare(execPath, Qt::CaseInsensitive) == 0) + { + iconName = desktop.value(U8Str("Desktop Entry/Icon")).toString(); + } + + else if (mAppId.compare(name, Qt::CaseInsensitive) == 0) + { + iconName = desktop.value(U8Str("Desktop Entry/Icon")).toString(); + } + + else if (mAppId.compare(cls, Qt::CaseInsensitive) == 0) + { + iconName = desktop.value(U8Str("Desktop Entry/Icon")).toString(); + } + + if (not iconName.isEmpty()) + { + if (QIcon::hasThemeIcon(iconName)) + { + return QIcon::fromTheme(iconName); + } + + else if (QFile::exists(iconName)) + { + return QIcon(iconName); + } + + else + { + iconName = getPixmapIcon(iconName); + + if (not iconName.isEmpty()) + { + return QIcon(iconName); + } + } + } + } + } + + iconName = getPixmapIcon(iconName); + + if (not iconName.isEmpty()) + { + return QIcon(iconName); + } + + return QIcon(); +} + + +static inline wl_seat *get_seat() +{ + QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); + + if (!native) + { + return nullptr; + } + + struct wl_seat *seat = reinterpret_cast(native->nativeResourceForIntegration("wl_seat")); + + return seat; +} + + +/* + * LXQtTaskbarWlrootsWindowManagment + */ + +LXQtTaskbarWlrootsWindowManagment::LXQtTaskbarWlrootsWindowManagment() : QWaylandClientExtensionTemplate(version) +{ + /** Automatically destroy thie object */ + connect( + this, &QWaylandClientExtension::activeChanged, this, [ this ] { + if (!isActive()) + { + zwlr_foreign_toplevel_manager_v1_destroy(object()); + } + }); +} + + +LXQtTaskbarWlrootsWindowManagment::~LXQtTaskbarWlrootsWindowManagment() +{ + if (isActive()) + { + zwlr_foreign_toplevel_manager_v1_destroy(object()); + } +} + + +void LXQtTaskbarWlrootsWindowManagment::zwlr_foreign_toplevel_manager_v1_toplevel(struct ::zwlr_foreign_toplevel_handle_v1 *toplevel) +{ + /** + * A window was created. + * Wait for the window to become ready, i.e. wait for done() event to be sent by the compositor. + * Once we recieve done(), emit the windowReady() signal. + */ + + auto w = new LXQtTaskbarWlrootsWindow(toplevel); + + connect(w, &LXQtTaskbarWlrootsWindow::windowReady, [w, this] () { + emit windowCreated(w->getWindowId()); + }); +} + + +/* + * LXQtTaskbarWlrootsWindow + */ + +LXQtTaskbarWlrootsWindow::LXQtTaskbarWlrootsWindow(::zwlr_foreign_toplevel_handle_v1 *id) : zwlr_foreign_toplevel_handle_v1(id) +{ +} + + +LXQtTaskbarWlrootsWindow::~LXQtTaskbarWlrootsWindow() +{ + destroy(); +} + + +void LXQtTaskbarWlrootsWindow::activate() +{ + /** + * Activate on default seat. + * TODO: Worry about multi-seat setups, when we have no other worries :P + */ + zwlr_foreign_toplevel_handle_v1::activate(get_seat()); +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_title(const QString& title) +{ + /** Store the incoming title in pending */ + m_pendingState.title = title; + m_pendingState.titleChanged = true; +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_app_id(const QString& app_id) +{ + /** Store the incoming appId in pending */ + m_pendingState.appId = app_id; + m_pendingState.appIdChanged = true; + + /** Update the icon */ + this->icon = getIconForAppId(app_id); + + /** We did not get any icon from app-id. Let's use application-x-executable */ + if (this->icon.pixmap(64).width() == 0) + { + this->icon = XdgIcon::fromTheme(QString::fromUtf8("application-x-executable")); + } +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_output_enter(struct ::wl_output *output) +{ + /** This view was added to an output */ + m_pendingState.outputs << output; + m_pendingState.outputsChanged = true; +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_output_leave(struct ::wl_output *output) +{ + /** This view was removed from an output; store it in pending. */ + m_pendingState.outputsLeft << output; + + if (m_pendingState.outputs.contains(output)) + { + m_pendingState.outputs.removeAll(output); + } + + m_pendingState.outputsChanged = true; +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_state(wl_array *state) +{ + /** State of this window was changed; store it in pending. */ + auto *states = static_cast(state->data); + int numStates = static_cast(state->size / sizeof(uint32_t)); + + for (int i = 0; i < numStates; i++) + { + switch ((uint32_t)states[ i ]) + { + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED: { + m_pendingState.maximized = true; + m_pendingState.maximizedChanged = true; + break; + } + + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED: { + m_pendingState.minimized = true; + m_pendingState.minimizedChanged = true; + break; + } + + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED: { + m_pendingState.activated = true; + m_pendingState.activatedChanged = true; + break; + } + + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN: { + m_pendingState.fullscreen = true; + m_pendingState.fullscreenChanged = true; + break; + } + } + } +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_done() +{ + /** + * All the states/properties have been sent. + * We can now emit the signals and clear the pending state: + * 1. Update all the variables first. + * 2. Then clear the m_pendingState. + * 3. Emit the changed signals. + * 4. Finally, cleanr the m_pendingState.Changed flags. + */ + + // (1) title, if it changed + if (m_pendingState.titleChanged) + { + windowState.title = m_pendingState.title; + } + + // (2) appId, if it changed + if (m_pendingState.appIdChanged) + { + windowState.appId = m_pendingState.appId; + } + + // (3) outputs, if they changed + if (m_pendingState.outputsChanged) + { + for (::wl_output *op: m_pendingState.outputsLeft) + { + if (windowState.outputs.contains(op)) + { + windowState.outputs.removeAll(op); + } + } + + for (::wl_output *op: m_pendingState.outputs) + { + if (!windowState.outputs.contains(op)) + { + windowState.outputs << op; + } + } + } + + // (4) states, if they changed. Don't trust the changed flag. + if (m_pendingState.maximized != windowState.maximized) + { + windowState.maximized = m_pendingState.maximized; + m_pendingState.maximizedChanged = true; + } + + if (m_pendingState.minimized != windowState.minimized) + { + windowState.minimized = m_pendingState.minimized; + m_pendingState.minimizedChanged = true; + } + + if (m_pendingState.activated != windowState.activated) + { + windowState.activated = m_pendingState.activated; + m_pendingState.activatedChanged = true; + } + + if (m_pendingState.fullscreen != windowState.fullscreen) + { + windowState.fullscreen = m_pendingState.fullscreen; + m_pendingState.fullscreenChanged = true; + } + + // (5) parent, if it changed. + if (m_pendingState.parentChanged) + { + if (m_pendingState.parent) + { + setParentWindow(new LXQtTaskbarWlrootsWindow(m_pendingState.parent)); + } + + else + { + setParentWindow(nullptr); + } + } + + /** 2. Clear all m_pendingState. for next run */ + m_pendingState.title = QString(); + m_pendingState.appId = QString(); + m_pendingState.outputs.clear(); + m_pendingState.maximized = false; + m_pendingState.minimized = false; + m_pendingState.activated = false; + m_pendingState.fullscreen = false; + m_pendingState.parent = nullptr; + + /** + * 3. Emit signals + * (a) First time done was emitted after the window was created. + * (b) Other times. + */ + + /** (a) First time done was emitted */ + if (initDone == false) + { + /** + * All the states/properties are already set. + * Any query will give valid results. + */ + initDone = true; + emit windowReady(); + } + + /** (b) All the subsequent times */ + else + { + if (m_pendingState.titleChanged) + emit titleChanged(); + if (m_pendingState.appIdChanged) + emit appIdChanged(); + if (m_pendingState.outputsChanged) + emit outputsChanged(); + if (m_pendingState.maximizedChanged) + emit maximizedChanged(); + if (m_pendingState.minimizedChanged) + emit minimizedChanged(); + if (m_pendingState.activatedChanged) + emit activatedChanged(); + if (m_pendingState.fullscreenChanged) + emit fullscreenChanged(); + if (m_pendingState.parentChanged) + emit parentChanged(); + + emit stateChanged(); + } + + /** 4. Clear m+m_pendingState.Changed flags */ + m_pendingState.titleChanged = false; + m_pendingState.appIdChanged = false; + m_pendingState.outputsChanged = false; + m_pendingState.maximizedChanged = false; + m_pendingState.minimizedChanged = false; + m_pendingState.activatedChanged = false; + m_pendingState.fullscreenChanged = false; + m_pendingState.parentChanged = false; +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_closed() +{ + /** This window was closed */ + emit closed(); +} + + +void LXQtTaskbarWlrootsWindow::zwlr_foreign_toplevel_handle_v1_parent(struct ::zwlr_foreign_toplevel_handle_v1 *parent) +{ + /** Parent of this window changed; store it in pending. */ + m_pendingState.parent = parent; + m_pendingState.parentChanged = true; +} + + +void LXQtTaskbarWlrootsWindow::setParentWindow(LXQtTaskbarWlrootsWindow *parent) +{ + QObject::disconnect(parentWindowUnmappedConnection); + + if (parent) + { + parentWindow = parent->getWindowId(); + parentWindowUnmappedConnection = QObject::connect( + parent, &LXQtTaskbarWlrootsWindow::closed, this, [ this ] { + setParentWindow(nullptr); + }); + } + else + { + parentWindow = 0; + parentWindowUnmappedConnection = QMetaObject::Connection(); + } +} diff --git a/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.h b/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.h new file mode 100644 index 000000000..c79f174da --- /dev/null +++ b/panel/backends/wayland/wlroots/lxqttaskbarwlrwm.h @@ -0,0 +1,136 @@ +#pragma once + +#include +#include +#include + +#include "qwayland-wlr-foreign-toplevel-management-unstable-v1.h" +#include "wayland-wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" + +typedef quintptr WId; + +class LXQtTaskbarWlrootsWindow; + +class LXQtTaskbarWlrootsWindowManagment : public QWaylandClientExtensionTemplate, + public QtWayland::zwlr_foreign_toplevel_manager_v1 +{ + Q_OBJECT +public: + static constexpr int version = 16; + + LXQtTaskbarWlrootsWindowManagment(); + ~LXQtTaskbarWlrootsWindowManagment(); + + inline bool isShowingDesktop() const { return m_isShowingDesktop; } + +protected: + void zwlr_foreign_toplevel_manager_v1_toplevel(struct ::zwlr_foreign_toplevel_handle_v1 *toplevel); + void zwlr_foreign_toplevel_manager_v1_finished() {}; + +Q_SIGNALS: + void windowCreated(WId wid); + +private: + bool m_isShowingDesktop = false; +}; + +using WindowState = QtWayland::zwlr_foreign_toplevel_handle_v1::state; + +class WindowProperties { + public: + /** Title of the window */ + QString title = QString::fromUtf8( "untitled" ); + bool titleChanged = false; + + /** appId of the window */ + QString appId = QString::fromUtf8( "unidentified" ); + bool appIdChanged = false; + + /** List of outputs which the window is currently on */ + QList<::wl_output *> outputs; + bool outputsChanged = false; + + /** Is maximized */ + bool maximized = false; + bool maximizedChanged = false; + + /** Is minimized */ + bool minimized = false; + bool minimizedChanged = false; + + /** Is active */ + bool activated = false; + bool activatedChanged = false; + + /** Is fullscreen */ + bool fullscreen = false; + bool fullscreenChanged = false; + + /** Parent of this view, can be null */ + ::zwlr_foreign_toplevel_handle_v1 * parent = nullptr; + bool parentChanged = false; + + /** List of outputs from which window has left */ + QList<::wl_output *> outputsLeft; +}; + +class LXQtTaskbarWlrootsWindow : public QObject, + public QtWayland::zwlr_foreign_toplevel_handle_v1 +{ + Q_OBJECT +public: + LXQtTaskbarWlrootsWindow(::zwlr_foreign_toplevel_handle_v1 *id); + ~LXQtTaskbarWlrootsWindow(); + + inline WId getWindowId() const { return reinterpret_cast(this); } + + void activate(); + + QIcon icon; + WindowProperties windowState; + WId parentWindow = 0; + +Q_SIGNALS: + void titleChanged(); + void appIdChanged(); + void outputsChanged(); + + /** Individual state change signals */ + void maximizedChanged(); + void minimizedChanged(); + void activatedChanged(); + void fullscreenChanged(); + + void parentChanged(); + + /** Bulk state change signal */ + void stateChanged(); + + /** First state change signal: Before this, the window did not have a valid state */ + void windowReady(); + + /** All state changes have been sent. */ + void done(); + + /** Window closed signal */ + void closed(); + +protected: + void zwlr_foreign_toplevel_handle_v1_title(const QString &title); + void zwlr_foreign_toplevel_handle_v1_app_id(const QString &app_id); + void zwlr_foreign_toplevel_handle_v1_output_enter(struct ::wl_output *output); + void zwlr_foreign_toplevel_handle_v1_output_leave(struct ::wl_output *output); + void zwlr_foreign_toplevel_handle_v1_state(wl_array *state); + void zwlr_foreign_toplevel_handle_v1_done(); + void zwlr_foreign_toplevel_handle_v1_closed(); + void zwlr_foreign_toplevel_handle_v1_parent(struct ::zwlr_foreign_toplevel_handle_v1 *parent); + +private: + void setParentWindow(LXQtTaskbarWlrootsWindow *parent); + + QMetaObject::Connection parentWindowUnmappedConnection; + + WindowProperties m_pendingState; + + bool initDone = false; +}; diff --git a/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp new file mode 100644 index 000000000..241efd2b8 --- /dev/null +++ b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.cpp @@ -0,0 +1,516 @@ +#include "lxqttaskbarwlrwm.h" +#include "lxqtwmbackend_wlr.h" + +#include +#include +#include +#include + +// Function to search for a window in the vector +WId findWindow(const std::vector& windows, WId tgt) { + // Use std::find to locate the target window + auto it = std::find(windows.begin(), windows.end(), tgt); + + // Check if the window was found (iterator points to windows.end() if not found) + if (it != windows.end()) { + // If found, return the window ID by dereferencing the iterator + return *it; + } + + return 0; +} + +// Function to erase a window from the vector +void eraseWindow(std::vector& windows, WId tgt) { + // Use std::vector::iterator to find the window + auto it = std::find(windows.begin(), windows.end(), tgt); + + // Check if the window was found + if (it != windows.end()) { + // If found, erase the element pointed to by the iterator + windows.erase(it); + } +} + +LXQtTaskbarWlrootsBackend::LXQtTaskbarWlrootsBackend(QObject *parent) : + ILXQtAbstractWMInterface(parent) +{ + m_managment.reset(new LXQtTaskbarWlrootsWindowManagment); + + connect(m_managment.get(), &LXQtTaskbarWlrootsWindowManagment::windowCreated, this, &LXQtTaskbarWlrootsBackend::addWindow); +} + +bool LXQtTaskbarWlrootsBackend::supportsAction(WId, LXQtTaskBarBackendAction action) const +{ + switch (action) + { + case LXQtTaskBarBackendAction::Maximize: + return true; + + case LXQtTaskBarBackendAction::Minimize: + return true; + + case LXQtTaskBarBackendAction::FullScreen: + return true; + + default: + return false; + } + + return false; +} + +bool LXQtTaskbarWlrootsBackend::reloadWindows() +{ + const QVector wids = getCurrentWindows(); + + // Force removal and re-adding + for(WId windowId : wids) + { + emit windowRemoved(windowId); + } + for(WId windowId : wids) + { + emit windowAdded(windowId); + } + + return true; +} + +QVector LXQtTaskbarWlrootsBackend::getCurrentWindows() const +{ + QVector wids; + + for( WId wid: windows ){ + wids << wid; + } + + return wids; +} + +QString LXQtTaskbarWlrootsBackend::getWindowTitle(WId windowId) const +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return QString(); + + return window->windowState.title; +} + +bool LXQtTaskbarWlrootsBackend::applicationDemandsAttention(WId) const +{ + return false; +} + +QIcon LXQtTaskbarWlrootsBackend::getApplicationIcon(WId windowId, int devicePixels) const +{ + Q_UNUSED(devicePixels) + + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return QIcon(); + + return window->icon; +} + +QString LXQtTaskbarWlrootsBackend::getWindowClass(WId windowId) const +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return QString(); + return window->windowState.appId; +} + +LXQtTaskBarWindowLayer LXQtTaskbarWlrootsBackend::getWindowLayer(WId) const +{ + return LXQtTaskBarWindowLayer::Normal; +} + +bool LXQtTaskbarWlrootsBackend::setWindowLayer(WId, LXQtTaskBarWindowLayer) +{ + return false; +} + +LXQtTaskBarWindowState LXQtTaskbarWlrootsBackend::getWindowState(WId windowId) const +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return LXQtTaskBarWindowState::Normal; + + if(window->windowState.minimized) + return LXQtTaskBarWindowState::Minimized; + + if(window->windowState.maximized) + return LXQtTaskBarWindowState::Maximized; + + if(window->windowState.fullscreen) + return LXQtTaskBarWindowState::FullScreen; + + return LXQtTaskBarWindowState::Normal; +} + +bool LXQtTaskbarWlrootsBackend::setWindowState(WId windowId, LXQtTaskBarWindowState state, bool set) +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return false; + + switch (state) + { + case LXQtTaskBarWindowState::Minimized: + { + if ( set ) { + window->set_minimized(); + } + + else { + window->unset_minimized(); + } + + break; + } + case LXQtTaskBarWindowState::Maximized: + case LXQtTaskBarWindowState::MaximizedVertically: + case LXQtTaskBarWindowState::MaximizedHorizontally: + { + if ( set ) { + window->set_maximized(); + } + + else { + window->unset_maximized(); + } + + break; + } + case LXQtTaskBarWindowState::Normal: + { + if (set) + { + if ( window->windowState.maximized) { + window->unset_maximized(); + } + } + + break; + } + + case LXQtTaskBarWindowState::FullScreen: + { + if ( set ) { + window->set_fullscreen(nullptr); + } + + else { + window->unset_fullscreen(); + } + break; + } + + default: + return false; + } + + return true; +} + +bool LXQtTaskbarWlrootsBackend::isWindowActive(WId windowId) const +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return false; + + return activeWindow == windowId || window->windowState.activated; +} + +bool LXQtTaskbarWlrootsBackend::raiseWindow(WId windowId, bool onCurrentWorkSpace) +{ + Q_UNUSED(onCurrentWorkSpace) // Cannot be done on a generic wlroots-based compositor! + + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return false; + + window->activate(); + return true; +} + +bool LXQtTaskbarWlrootsBackend::closeWindow(WId windowId) +{ + LXQtTaskbarWlrootsWindow *window = getWindow(windowId); + if(!window) + return false; + + window->close(); + return true; +} + +WId LXQtTaskbarWlrootsBackend::getActiveWindow() const +{ + return activeWindow; +} + +int LXQtTaskbarWlrootsBackend::getWorkspacesCount() const +{ + return 1; +} + +QString LXQtTaskbarWlrootsBackend::getWorkspaceName(int) const +{ + return QStringLiteral("Desktop 1"); +} + +int LXQtTaskbarWlrootsBackend::getCurrentWorkspace() const +{ + return 1; +} + +bool LXQtTaskbarWlrootsBackend::setCurrentWorkspace(int) +{ + return false; +} + +int LXQtTaskbarWlrootsBackend::getWindowWorkspace(WId) const +{ + return 1; +} + +bool LXQtTaskbarWlrootsBackend::setWindowOnWorkspace(WId, int) +{ + return true; +} + +void LXQtTaskbarWlrootsBackend::moveApplicationToPrevNextMonitor(WId, bool, bool) +{ +} + +bool LXQtTaskbarWlrootsBackend::isWindowOnScreen(QScreen *, WId) const +{ + // TODO: Manage based on output-enter/output-leave + return true; +} + +bool LXQtTaskbarWlrootsBackend::setDesktopLayout(Qt::Orientation, int, int, bool) { + // Wlroots has no support for workspace as of 2024-August-20 + return false; +} + +void LXQtTaskbarWlrootsBackend::moveApplication(WId) +{ +} + +void LXQtTaskbarWlrootsBackend::resizeApplication(WId) +{ +} + +void LXQtTaskbarWlrootsBackend::refreshIconGeometry(WId, const QRect &) +{ + +} + +bool LXQtTaskbarWlrootsBackend::isAreaOverlapped(const QRect &) const +{ + return false; +} + +bool LXQtTaskbarWlrootsBackend::isShowingDesktop() const +{ + return m_managment->isShowingDesktop(); +} + +bool LXQtTaskbarWlrootsBackend::showDesktop(bool) +{ + return false; +} + +void LXQtTaskbarWlrootsBackend::addWindow(WId winId) +{ + if (findWindow(windows, winId) != 0 || transients.contains(winId)) + { + return; + } + + auto removeWindow = [winId, this] + { + eraseWindow(windows, winId); + lastActivated.remove(winId); + + if (activeWindow == winId) + { + activeWindow = 0; + emit activeWindowChanged(0); + } + + emit windowRemoved(winId); + }; + + LXQtTaskbarWlrootsWindow *window = getWindow( winId ); + if ( window == nullptr ) { + return; + } + + /** The window was closed. Remove from our lists */ + connect(window, &LXQtTaskbarWlrootsWindow::closed, this, removeWindow); + + /** */ + connect(window, &LXQtTaskbarWlrootsWindow::titleChanged, this, [winId, this] { + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::Title)); + }); + + connect(window, &LXQtTaskbarWlrootsWindow::appIdChanged, this, [winId, this] { + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::WindowClass)); + }); + + if (window->windowState.activated) { + LXQtTaskbarWlrootsWindow *effectiveActive = window; + while (effectiveActive->parentWindow) { + effectiveActive = getWindow(effectiveActive->parentWindow); + } + + lastActivated[effectiveActive->getWindowId()] = QTime::currentTime(); + activeWindow = effectiveActive->getWindowId(); + } + + connect(window, &LXQtTaskbarWlrootsWindow::activatedChanged, this, [window, this] { + WId effectiveWindow = window->getWindowId(); + + while (getWindow(effectiveWindow)->parentWindow) + { + effectiveWindow = getWindow(effectiveWindow)->parentWindow; + } + + if (window->windowState.activated) + { + lastActivated[effectiveWindow] = QTime::currentTime(); + + if (activeWindow != effectiveWindow) + { + activeWindow = effectiveWindow; + emit activeWindowChanged(activeWindow); + } + } + else + { + if (activeWindow == effectiveWindow) + { + activeWindow = 0; + emit activeWindowChanged(0); + } + } + }); + + connect(window, &LXQtTaskbarWlrootsWindow::parentChanged, this, [window, this] { + WId leader = window->parentWindow; + + /** Basically, check if this window is a transient */ + if (transients.remove(leader)) + { + if (leader) + { + // leader change. + transients.insert(window->getWindowId(), leader); + } + else + { + // lost a leader, add to regular windows list. + Q_ASSERT(findWindow(windows, leader) == 0); + + windows.push_back(leader); + } + } + + else if (leader) + { + eraseWindow(windows, window->getWindowId()); + lastActivated.remove(window->getWindowId()); + } + }); + + auto stateChanged = [window, this] { + emit windowPropertyChanged(window->getWindowId(), int(LXQtTaskBarWindowProperty::State)); + }; + + connect(window, &LXQtTaskbarWlrootsWindow::fullscreenChanged, this, stateChanged); + + connect(window, &LXQtTaskbarWlrootsWindow::maximizedChanged, this, stateChanged); + + connect(window, &LXQtTaskbarWlrootsWindow::minimizedChanged, this, stateChanged); + + // Handle transient. + if (WId leader = window->parentWindow) + { + transients.insert(winId, leader); + } + else + { + windows.push_back(winId); + } + + emit windowAdded( winId ); + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::WindowClass)); + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::Title)); + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::Icon)); + emit windowPropertyChanged(winId, int(LXQtTaskBarWindowProperty::State)); +} + +bool LXQtTaskbarWlrootsBackend::acceptWindow(WId window) const +{ + if(transients.contains(window)) + return false; + + return true; +} + +LXQtTaskbarWlrootsWindow *LXQtTaskbarWlrootsBackend::getWindow(WId windowId) const +{ + /** Easiest way is to convert the quintptr to the actual pointer */ + LXQtTaskbarWlrootsWindow *win = reinterpret_cast( windowId ); + if ( win ) { + return win; + } + + return nullptr; +} + + +int LXQtWMBackendWlrootsLibrary::getBackendScore(const QString& key) const +{ + if (key == QStringLiteral("wlroots")) + { + return 50; + } + + else if (key == QStringLiteral("wayfire")) + { + return 30; + } + + else if (key == QStringLiteral("sway")) + { + return 30; + } + + else if (key == QStringLiteral("hyprland")) + { + return 30; + } + + else if (key == QStringLiteral("labwc")) + { + return 30; + } + + else if (key == QStringLiteral("river")) + { + return 30; + } + + // Unsupported + return 0; +} + + +ILXQtAbstractWMInterface *LXQtWMBackendWlrootsLibrary::instance() const +{ + return new LXQtTaskbarWlrootsBackend(nullptr); +} diff --git a/panel/backends/xcb/lxqttaskbarbackend_x11.h b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.h similarity index 66% rename from panel/backends/xcb/lxqttaskbarbackend_x11.h rename to panel/backends/wayland/wlroots/lxqtwmbackend_wlr.h index 2478b3fff..b354b3cdb 100644 --- a/panel/backends/xcb/lxqttaskbarbackend_x11.h +++ b/panel/backends/wayland/wlroots/lxqtwmbackend_wlr.h @@ -1,20 +1,22 @@ -#ifndef LXQTTASKBARBACKEND_X11_H -#define LXQTTASKBARBACKEND_X11_H +#pragma once -#include "../ilxqttaskbarabstractbackend.h" +#include "../../ilxqtabstractwmiface.h" -//TODO: make PIMPL to forward declare NET::Properties, Display, xcb_connection_t -#include +#include +#include +#include -typedef struct _XDisplay Display; -struct xcb_connection_t; +class LXQtTaskbarWlrootsWindow; +class LXQtTaskbarWlrootsWindowManagment; +class LXQtWlrootsWaylandWorkspaceInfo; -class LXQtTaskbarX11Backend : public ILXQtTaskbarAbstractBackend + +class LXQtTaskbarWlrootsBackend : public ILXQtAbstractWMInterface { Q_OBJECT public: - explicit LXQtTaskbarX11Backend(QObject *parent = nullptr); + explicit LXQtTaskbarWlrootsBackend(QObject *parent = nullptr); // Backend virtual bool supportsAction(WId windowId, LXQtTaskBarBackendAction action) const override; @@ -55,7 +57,7 @@ class LXQtTaskbarX11Backend : public ILXQtTaskbarAbstractBackend virtual bool isWindowOnScreen(QScreen *screen, WId windowId) const override; - virtual bool setDesktopLayout(Qt::Orientation orientation, int rows, int columns, bool rightToLeft) override; + virtual bool setDesktopLayout(Qt::Orientation orientation, int rows, int columns, bool rightToLeft); // X11 Specific virtual void moveApplication(WId windowId) override; @@ -70,20 +72,32 @@ class LXQtTaskbarX11Backend : public ILXQtTaskbarAbstractBackend 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); + void addWindow(WId wid); + bool acceptWindow(WId wid) const; private: - Display *m_X11Display; - xcb_connection_t *m_xcbConnection; + /** Convert WId (i.e. quintptr into LXQtTaskbarWlrootsWindow*) */ + LXQtTaskbarWlrootsWindow *getWindow(WId windowId) const; + + std::unique_ptr m_managment; - QVector m_windows; + QHash lastActivated; + WId activeWindow = 0; + std::vector windows; + + // key=transient child, value=leader + QHash transients; }; -#endif // LXQTTASKBARBACKEND_X11_H + +class LXQtWMBackendWlrootsLibrary: public QObject, public ILXQtWMBackendLibrary +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "lxqt.org/Panel/WMInterface/1.0") + Q_INTERFACES(ILXQtWMBackendLibrary) +public: + int getBackendScore(const QString& key) const override; + + ILXQtAbstractWMInterface* instance() const override; +}; diff --git a/panel/backends/wayland/wlroots/wlr-foreign-toplevel-management-unstable-v1.xml b/panel/backends/wayland/wlroots/wlr-foreign-toplevel-management-unstable-v1.xml new file mode 100644 index 000000000..108133715 --- /dev/null +++ b/panel/backends/wayland/wlroots/wlr-foreign-toplevel-management-unstable-v1.xml @@ -0,0 +1,270 @@ + + + + Copyright © 2018 Ilia Bozhinov + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + The purpose of this protocol is to enable the creation of taskbars + and docks by providing them with a list of opened applications and + letting them request certain actions on them, like maximizing, etc. + + After a client binds the zwlr_foreign_toplevel_manager_v1, each opened + toplevel window will be sent via the toplevel event + + + + + This event is emitted whenever a new toplevel window is created. It + is emitted for all toplevels, regardless of the app that has created + them. + + All initial details of the toplevel(title, app_id, states, etc.) will + be sent immediately after this event via the corresponding events in + zwlr_foreign_toplevel_handle_v1. + + + + + + + Indicates the client no longer wishes to receive events for new toplevels. + However the compositor may emit further toplevel_created events, until + the finished event is emitted. + + The client must not send any more requests after this one. + + + + + + This event indicates that the compositor is done sending events to the + zwlr_foreign_toplevel_manager_v1. The server will destroy the object + immediately after sending this request, so it will become invalid and + the client should free any resources associated with it. + + + + + + + A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel + window. Each app may have multiple opened toplevels. + + Each toplevel has a list of outputs it is visible on, conveyed to the + client with the output_enter and output_leave events. + + + + + This event is emitted whenever the title of the toplevel changes. + + + + + + + This event is emitted whenever the app-id of the toplevel changes. + + + + + + + This event is emitted whenever the toplevel becomes visible on + the given output. A toplevel may be visible on multiple outputs. + + + + + + + This event is emitted whenever the toplevel stops being visible on + the given output. It is guaranteed that an entered-output event + with the same output has been emitted before this event. + + + + + + + Requests that the toplevel be maximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unmaximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be minimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unminimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Request that this toplevel be activated on the given seat. + There is no guarantee the toplevel will be actually activated. + + + + + + + The different states that a toplevel can have. These have the same meaning + as the states with the same names defined in xdg-toplevel + + + + + + + + + + + This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 + is created and each time the toplevel state changes, either because of a + compositor action or because of a request in this protocol. + + + + + + + + This event is sent after all changes in the toplevel state have been + sent. + + This allows changes to the zwlr_foreign_toplevel_handle_v1 properties + to be seen as atomic, even if they happen via multiple events. + + + + + + Send a request to the toplevel to close itself. The compositor would + typically use a shell-specific method to carry out this request, for + example by sending the xdg_toplevel.close event. However, this gives + no guarantees the toplevel will actually be destroyed. If and when + this happens, the zwlr_foreign_toplevel_handle_v1.closed event will + be emitted. + + + + + + The rectangle of the surface specified in this request corresponds to + the place where the app using this protocol represents the given toplevel. + It can be used by the compositor as a hint for some operations, e.g + minimizing. The client is however not required to set this, in which + case the compositor is free to decide some default value. + + If the client specifies more than one rectangle, only the last one is + considered. + + The dimensions are given in surface-local coordinates. + Setting width=height=0 removes the already-set rectangle. + + + + + + + + + + + + + + + + This event means the toplevel has been destroyed. It is guaranteed there + won't be any more events for this zwlr_foreign_toplevel_handle_v1. The + toplevel itself becomes inert so any requests will be ignored except the + destroy request. + + + + + + Destroys the zwlr_foreign_toplevel_handle_v1 object. + + This request should be called either when the client does not want to + use the toplevel anymore or after the closed event to finalize the + destruction of the object. + + + + + + + + Requests that the toplevel be fullscreened on the given output. If the + fullscreen state and/or the outputs the toplevel is visible on actually + change, this will be indicated by the state and output_enter/leave + events. + + The output parameter is only a hint to the compositor. Also, if output + is NULL, the compositor should decide which output the toplevel will be + fullscreened on, if at all. + + + + + + + Requests that the toplevel be unfullscreened. If the fullscreen state + actually changes, this will be indicated by the state event. + + + + + + + + This event is emitted whenever the parent of the toplevel changes. + + No event is emitted when the parent handle is destroyed by the client. + + + + + diff --git a/panel/backends/xcb/CMakeLists.txt b/panel/backends/xcb/CMakeLists.txt index 8b1378917..76e5c98a4 100644 --- a/panel/backends/xcb/CMakeLists.txt +++ b/panel/backends/xcb/CMakeLists.txt @@ -1 +1,24 @@ +set(PLATFORM_NAME xcb) +set(PREFIX_NAME wmbackend) +set(PROGRAM "lxqt-panel") +set(BACKEND "backend") +set(NAME ${PREFIX_NAME}_${PLATFORM_NAME}) +project(${PROGRAM}_${BACKEND}_${NAME}) + +set(PROG_SHARE_DIR ${CMAKE_INSTALL_FULL_DATAROOTDIR}/lxqt/${PROGRAM}/${BACKEND}) +set(PLUGIN_SHARE_DIR ${PROG_SHARE_DIR}/${BACKEND}/${NAME}) +#************************************************ + +if (NOT DEFINED PLUGIN_DIR) + set (PLUGIN_DIR ${CMAKE_INSTALL_FULL_LIBDIR}/${PROGRAM}) +endif (NOT DEFINED PLUGIN_DIR) + +set(QTX_LIBRARIES Qt6::Gui) + +set(SRC lxqtwmbackend_x11.h lxqtwmbackend_x11.cpp) + +add_library(${NAME} MODULE ${SRC}) # build dynamically loadable modules +install(TARGETS ${NAME} DESTINATION ${PLUGIN_DIR}/${BACKEND}) # install the *.so file + +target_link_libraries(${NAME} ${QTX_LIBRARIES} KF6::WindowSystem) diff --git a/panel/backends/xcb/lxqttaskbarbackend_x11.cpp b/panel/backends/xcb/lxqtwmbackend_x11.cpp similarity index 80% rename from panel/backends/xcb/lxqttaskbarbackend_x11.cpp rename to panel/backends/xcb/lxqtwmbackend_x11.cpp index fe0452776..3196197bc 100644 --- a/panel/backends/xcb/lxqttaskbarbackend_x11.cpp +++ b/panel/backends/xcb/lxqtwmbackend_x11.cpp @@ -1,4 +1,32 @@ -#include "lxqttaskbarbackend_x11.h" +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + + +#include "lxqtwmbackend_x11.h" #include #include @@ -16,28 +44,28 @@ #include #undef Bool -LXQtTaskbarX11Backend::LXQtTaskbarX11Backend(QObject *parent) - : ILXQtTaskbarAbstractBackend(parent) +LXQtWMBackendX11::LXQtWMBackendX11(QObject *parent) + : ILXQtAbstractWMInterface(parent) { auto *x11Application = qGuiApp->nativeInterface(); - Q_ASSERT_X(x11Application, "LXQtTaskbarX11Backend", "Constructed without X11 connection"); + Q_ASSERT_X(x11Application, "LXQtWMBackendX11", "Constructed without X11 connection"); m_X11Display = x11Application->display(); m_xcbConnection = x11Application->connection(); - connect(KX11Extras::self(), &KX11Extras::windowChanged, this, &LXQtTaskbarX11Backend::onWindowChanged); - connect(KX11Extras::self(), &KX11Extras::windowAdded, this, &LXQtTaskbarX11Backend::onWindowAdded); - connect(KX11Extras::self(), &KX11Extras::windowRemoved, this, &LXQtTaskbarX11Backend::onWindowRemoved); + connect(KX11Extras::self(), &KX11Extras::windowChanged, this, &LXQtWMBackendX11::onWindowChanged); + connect(KX11Extras::self(), &KX11Extras::windowAdded, this, &LXQtWMBackendX11::onWindowAdded); + connect(KX11Extras::self(), &KX11Extras::windowRemoved, this, &LXQtWMBackendX11::onWindowRemoved); - connect(KX11Extras::self(), &KX11Extras::numberOfDesktopsChanged, this, &ILXQtTaskbarAbstractBackend::workspacesCountChanged); - connect(KX11Extras::self(), &KX11Extras::currentDesktopChanged, this, &ILXQtTaskbarAbstractBackend::currentWorkspaceChanged); + connect(KX11Extras::self(), &KX11Extras::numberOfDesktopsChanged, this, &ILXQtAbstractWMInterface::workspacesCountChanged); + connect(KX11Extras::self(), &KX11Extras::currentDesktopChanged, this, &ILXQtAbstractWMInterface::currentWorkspaceChanged); - connect(KX11Extras::self(), &KX11Extras::activeWindowChanged, this, &ILXQtTaskbarAbstractBackend::activeWindowChanged); + connect(KX11Extras::self(), &KX11Extras::activeWindowChanged, this, &ILXQtAbstractWMInterface::activeWindowChanged); } /************************************************ * Model slots ************************************************/ -void LXQtTaskbarX11Backend::onWindowChanged(WId windowId, NET::Properties prop, NET::Properties2 prop2) +void LXQtWMBackendX11::onWindowChanged(WId windowId, NET::Properties prop, NET::Properties2 prop2) { if(!m_windows.contains(windowId)) { @@ -97,7 +125,7 @@ void LXQtTaskbarX11Backend::onWindowChanged(WId windowId, NET::Properties prop, emit windowPropertyChanged(windowId, int(LXQtTaskBarWindowProperty::Urgency)); } -void LXQtTaskbarX11Backend::onWindowAdded(WId windowId) +void LXQtWMBackendX11::onWindowAdded(WId windowId) { if(m_windows.contains(windowId)) return; @@ -108,7 +136,7 @@ void LXQtTaskbarX11Backend::onWindowAdded(WId windowId) addWindow_internal(windowId); } -void LXQtTaskbarX11Backend::onWindowRemoved(WId windowId) +void LXQtWMBackendX11::onWindowRemoved(WId windowId) { const int row = m_windows.indexOf(windowId); if(row == -1) @@ -122,7 +150,7 @@ void LXQtTaskbarX11Backend::onWindowRemoved(WId windowId) /************************************************ * Model private functions ************************************************/ -bool LXQtTaskbarX11Backend::acceptWindow(WId windowId) const +bool LXQtWMBackendX11::acceptWindow(WId windowId) const { QFlags ignoreList; ignoreList |= NET::DesktopMask; @@ -161,7 +189,7 @@ bool LXQtTaskbarX11Backend::acceptWindow(WId windowId) const return !NET::typeMatchesMask(info.windowType(NET::AllTypesMask), normalFlag); } -void LXQtTaskbarX11Backend::addWindow_internal(WId windowId, bool emitAdded) +void LXQtWMBackendX11::addWindow_internal(WId windowId, bool emitAdded) { m_windows.append(windowId); if(emitAdded) @@ -172,7 +200,7 @@ void LXQtTaskbarX11Backend::addWindow_internal(WId windowId, bool emitAdded) /************************************************ * Windows function ************************************************/ -bool LXQtTaskbarX11Backend::supportsAction(WId windowId, LXQtTaskBarBackendAction action) const +bool LXQtWMBackendX11::supportsAction(WId windowId, LXQtTaskBarBackendAction action) const { NET::Action x11Action; @@ -221,7 +249,7 @@ bool LXQtTaskbarX11Backend::supportsAction(WId windowId, LXQtTaskBarBackendActio return info.actionSupported(x11Action); } -bool LXQtTaskbarX11Backend::reloadWindows() +bool LXQtWMBackendX11::reloadWindows() { QVector oldWindows; qSwap(oldWindows, m_windows); @@ -254,37 +282,37 @@ bool LXQtTaskbarX11Backend::reloadWindows() return true; } -QVector LXQtTaskbarX11Backend::getCurrentWindows() const +QVector LXQtWMBackendX11::getCurrentWindows() const { return m_windows; } -QString LXQtTaskbarX11Backend::getWindowTitle(WId windowId) const +QString LXQtWMBackendX11::getWindowTitle(WId windowId) const { KWindowInfo info(windowId, NET::WMVisibleName | NET::WMName); QString title = info.visibleName().isEmpty() ? info.name() : info.visibleName(); return title; } -bool LXQtTaskbarX11Backend::applicationDemandsAttention(WId windowId) const +bool LXQtWMBackendX11::applicationDemandsAttention(WId windowId) const { WId appRootWindow = XDefaultRootWindow(m_X11Display); return NETWinInfo(m_xcbConnection, windowId, appRootWindow, NET::Properties{}, NET::WM2Urgency).urgency() || KWindowInfo{windowId, NET::WMState}.hasState(NET::DemandsAttention); } -QIcon LXQtTaskbarX11Backend::getApplicationIcon(WId windowId, int devicePixels) const +QIcon LXQtWMBackendX11::getApplicationIcon(WId windowId, int devicePixels) const { return KX11Extras::icon(windowId, devicePixels, devicePixels); } -QString LXQtTaskbarX11Backend::getWindowClass(WId windowId) const +QString LXQtWMBackendX11::getWindowClass(WId windowId) const { KWindowInfo info(windowId, NET::Properties(), NET::WM2WindowClass); return QString::fromUtf8(info.windowClassClass()); } -LXQtTaskBarWindowLayer LXQtTaskbarX11Backend::getWindowLayer(WId windowId) const +LXQtTaskBarWindowLayer LXQtWMBackendX11::getWindowLayer(WId windowId) const { NET::States state = KWindowInfo(windowId, NET::WMState).state(); if(state.testFlag(NET::KeepAbove)) @@ -294,7 +322,7 @@ LXQtTaskBarWindowLayer LXQtTaskbarX11Backend::getWindowLayer(WId windowId) const return LXQtTaskBarWindowLayer::Normal; } -bool LXQtTaskbarX11Backend::setWindowLayer(WId windowId, LXQtTaskBarWindowLayer layer) +bool LXQtWMBackendX11::setWindowLayer(WId windowId, LXQtTaskBarWindowLayer layer) { switch(layer) { @@ -317,7 +345,7 @@ bool LXQtTaskbarX11Backend::setWindowLayer(WId windowId, LXQtTaskBarWindowLayer return true; } -LXQtTaskBarWindowState LXQtTaskbarX11Backend::getWindowState(WId windowId) const +LXQtTaskBarWindowState LXQtWMBackendX11::getWindowState(WId windowId) const { KWindowInfo info(windowId,NET::WMState | NET::XAWMState); if(info.isMinimized()) @@ -340,7 +368,7 @@ LXQtTaskBarWindowState LXQtTaskbarX11Backend::getWindowState(WId windowId) const return LXQtTaskBarWindowState::Normal; } -bool LXQtTaskbarX11Backend::setWindowState(WId windowId, LXQtTaskBarWindowState state, bool set) +bool LXQtWMBackendX11::setWindowState(WId windowId, LXQtTaskBarWindowState state, bool set) { // NOTE: window activation is left to the caller @@ -393,12 +421,12 @@ bool LXQtTaskbarX11Backend::setWindowState(WId windowId, LXQtTaskBarWindowState return true; } -bool LXQtTaskbarX11Backend::isWindowActive(WId windowId) const +bool LXQtWMBackendX11::isWindowActive(WId windowId) const { return KX11Extras::activeWindow() == windowId; } -bool LXQtTaskbarX11Backend::raiseWindow(WId windowId, bool onCurrentWorkSpace) +bool LXQtWMBackendX11::raiseWindow(WId windowId, bool onCurrentWorkSpace) { if (onCurrentWorkSpace && getWindowState(windowId) == LXQtTaskBarWindowState::Minimized) { @@ -418,14 +446,14 @@ bool LXQtTaskbarX11Backend::raiseWindow(WId windowId, bool onCurrentWorkSpace) return true; } -bool LXQtTaskbarX11Backend::closeWindow(WId windowId) +bool LXQtWMBackendX11::closeWindow(WId windowId) { // FIXME: Why there is no such thing in KWindowSystem?? NETRootInfo(m_xcbConnection, NET::CloseWindow).closeWindowRequest(windowId); return true; } -WId LXQtTaskbarX11Backend::getActiveWindow() const +WId LXQtWMBackendX11::getActiveWindow() const { return KX11Extras::activeWindow(); } @@ -434,22 +462,22 @@ WId LXQtTaskbarX11Backend::getActiveWindow() const /************************************************ * Workspaces ************************************************/ -int LXQtTaskbarX11Backend::getWorkspacesCount() const +int LXQtWMBackendX11::getWorkspacesCount() const { return KX11Extras::numberOfDesktops(); } -QString LXQtTaskbarX11Backend::getWorkspaceName(int idx) const +QString LXQtWMBackendX11::getWorkspaceName(int idx) const { return KX11Extras::desktopName(idx); } -int LXQtTaskbarX11Backend::getCurrentWorkspace() const +int LXQtWMBackendX11::getCurrentWorkspace() const { return KX11Extras::currentDesktop(); } -bool LXQtTaskbarX11Backend::setCurrentWorkspace(int idx) +bool LXQtWMBackendX11::setCurrentWorkspace(int idx) { if(KX11Extras::currentDesktop() == idx) return true; @@ -458,19 +486,19 @@ bool LXQtTaskbarX11Backend::setCurrentWorkspace(int idx) return true; } -int LXQtTaskbarX11Backend::getWindowWorkspace(WId windowId) const +int LXQtWMBackendX11::getWindowWorkspace(WId windowId) const { KWindowInfo info(windowId, NET::WMDesktop); return info.desktop(); } -bool LXQtTaskbarX11Backend::setWindowOnWorkspace(WId windowId, int idx) +bool LXQtWMBackendX11::setWindowOnWorkspace(WId windowId, int idx) { KX11Extras::setOnDesktop(windowId, idx); return true; } -void LXQtTaskbarX11Backend::moveApplicationToPrevNextMonitor(WId windowId, bool next, bool raiseOnCurrentDesktop) +void LXQtWMBackendX11::moveApplicationToPrevNextMonitor(WId windowId, bool next, bool raiseOnCurrentDesktop) { KWindowInfo info(windowId, NET::WMDesktop); if (!info.isOnCurrentDesktop()) @@ -516,7 +544,7 @@ void LXQtTaskbarX11Backend::moveApplicationToPrevNextMonitor(WId windowId, bool } } -bool LXQtTaskbarX11Backend::isWindowOnScreen(QScreen *screen, WId windowId) const +bool LXQtWMBackendX11::isWindowOnScreen(QScreen *screen, WId windowId) const { //TODO: old code was: //return QApplication::desktop()->screenGeometry(parentTaskBar()).intersects(KWindowInfo(mWindow, NET::WMFrameExtents).frameGeometry()); @@ -528,7 +556,7 @@ bool LXQtTaskbarX11Backend::isWindowOnScreen(QScreen *screen, WId windowId) cons return screen->geometry().intersects(r); } -bool LXQtTaskbarX11Backend::setDesktopLayout(Qt::Orientation orientation, int rows, int columns, bool rightToLeft) +bool LXQtWMBackendX11::setDesktopLayout(Qt::Orientation orientation, int rows, int columns, bool rightToLeft) { NETRootInfo mDesktops(m_xcbConnection, NET::NumberOfDesktops | NET::CurrentDesktop | NET::DesktopNames, NET::WM2DesktopLayout); @@ -550,7 +578,7 @@ bool LXQtTaskbarX11Backend::setDesktopLayout(Qt::Orientation orientation, int ro /************************************************ * X11 Specific ************************************************/ -void LXQtTaskbarX11Backend::moveApplication(WId windowId) +void LXQtWMBackendX11::moveApplication(WId windowId) { KWindowInfo info(windowId, NET::WMDesktop); if (!info.isOnCurrentDesktop()) @@ -568,7 +596,7 @@ void LXQtTaskbarX11Backend::moveApplication(WId windowId) NETRootInfo(m_xcbConnection, NET::WMMoveResize).moveResizeRequest(windowId, X, Y, NET::Move); } -void LXQtTaskbarX11Backend::resizeApplication(WId windowId) +void LXQtWMBackendX11::resizeApplication(WId windowId) { KWindowInfo info(windowId, NET::WMDesktop); if (!info.isOnCurrentDesktop()) @@ -586,7 +614,7 @@ void LXQtTaskbarX11Backend::resizeApplication(WId windowId) NETRootInfo(m_xcbConnection, NET::WMMoveResize).moveResizeRequest(windowId, X, Y, NET::BottomRight); } -void LXQtTaskbarX11Backend::refreshIconGeometry(WId windowId, QRect const & geom) +void LXQtWMBackendX11::refreshIconGeometry(WId windowId, QRect const & geom) { // NOTE: This function announces where the task icon is, // such that X11 WMs can perform their related animations correctly. @@ -616,7 +644,7 @@ void LXQtTaskbarX11Backend::refreshIconGeometry(WId windowId, QRect const & geom info.setIconGeometry(nrect); } -bool LXQtTaskbarX11Backend::isAreaOverlapped(const QRect &area) const +bool LXQtWMBackendX11::isAreaOverlapped(const QRect &area) const { //TODO: reuse our m_windows cache? QFlags ignoreList; @@ -648,13 +676,30 @@ bool LXQtTaskbarX11Backend::isAreaOverlapped(const QRect &area) const return false; } -bool LXQtTaskbarX11Backend::isShowingDesktop() const +bool LXQtWMBackendX11::isShowingDesktop() const { return KWindowSystem::showingDesktop(); } -bool LXQtTaskbarX11Backend::showDesktop(bool value) +bool LXQtWMBackendX11::showDesktop(bool value) { KWindowSystem::setShowingDesktop(value); return true; } + +int LXQtWMBackendX11Library::getBackendScore(const QString &key) const +{ + Q_UNUSED(key) + + auto *x11Application = qGuiApp->nativeInterface(); + if(!x11Application) + return 0; + + // Generic X11 backend + return 80; +} + +ILXQtAbstractWMInterface *LXQtWMBackendX11Library::instance() const +{ + return new LXQtWMBackendX11; +} diff --git a/panel/backends/xcb/lxqtwmbackend_x11.h b/panel/backends/xcb/lxqtwmbackend_x11.h new file mode 100644 index 000000000..e53b232f2 --- /dev/null +++ b/panel/backends/xcb/lxqtwmbackend_x11.h @@ -0,0 +1,127 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2023 LXQt team + * Authors: + * Filippo Gentile + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + + +#ifndef LXQT_WM_BACKEND_X11_H +#define LXQT_WM_BACKEND_X11_H + +#include "../ilxqtabstractwmiface.h" + +#include + +typedef struct _XDisplay Display; +struct xcb_connection_t; + +class LXQtWMBackendX11 : public ILXQtAbstractWMInterface +{ + Q_OBJECT + +public: + explicit LXQtWMBackendX11(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; +}; + +class LXQtWMBackendX11Library: public QObject, public ILXQtWMBackendLibrary +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "lxqt.org/Panel/WMInterface/1.0") + Q_INTERFACES(ILXQtWMBackendLibrary) +public: + int getBackendScore(const QString& key) const override; + + ILXQtAbstractWMInterface* instance() const override; +}; + +#endif // LXQT_WM_BACKEND_X11_H diff --git a/panel/lxqtpanel.cpp b/panel/lxqtpanel.cpp index 545617bb4..a0839eb42 100644 --- a/panel/lxqtpanel.cpp +++ b/panel/lxqtpanel.cpp @@ -52,7 +52,7 @@ #include #include -#include "backends/ilxqttaskbarabstractbackend.h" +#include "backends/ilxqtabstractwmiface.h" #include @@ -281,18 +281,18 @@ LXQtPanel::LXQtPanel(const QString &configGroup, LXQt::Settings *settings, QWidg LXQtPanelApplication *a = reinterpret_cast(qApp); auto wmBackend = a->getWMBackend(); - connect(wmBackend, &ILXQtTaskbarAbstractBackend::windowAdded, this, [this] { + connect(wmBackend, &ILXQtAbstractWMInterface::windowAdded, this, [this] { if (mHidable && mHideOnOverlap && !mHidden) { mShowDelayTimer.stop(); hidePanel(); } }); - connect(wmBackend, &ILXQtTaskbarAbstractBackend::windowRemoved, this, [this] { + connect(wmBackend, &ILXQtAbstractWMInterface::windowRemoved, this, [this] { if (mHidable && mHideOnOverlap && mHidden && !isPanelOverlapped()) mShowDelayTimer.start(); }); - connect(wmBackend, &ILXQtTaskbarAbstractBackend::currentWorkspaceChanged, this, [this] { + connect(wmBackend, &ILXQtAbstractWMInterface::currentWorkspaceChanged, this, [this] { if (mHidable && mHideOnOverlap) { if (!mHidden) @@ -304,7 +304,7 @@ LXQtPanel::LXQtPanel(const QString &configGroup, LXQt::Settings *settings, QWidg mShowDelayTimer.start(); } }); - connect(wmBackend, &ILXQtTaskbarAbstractBackend::windowPropertyChanged, + connect(wmBackend, &ILXQtAbstractWMInterface::windowPropertyChanged, this, [this] (WId /* id */, int prop) { if (mHidable && mHideOnOverlap diff --git a/panel/lxqtpanelapplication.cpp b/panel/lxqtpanelapplication.cpp index c3b666620..1970204ac 100644 --- a/panel/lxqtpanelapplication.cpp +++ b/panel/lxqtpanelapplication.cpp @@ -38,28 +38,126 @@ #include #include -#include "backends/lxqttaskbardummybackend.h" -#include "backends/xcb/lxqttaskbarbackend_x11.h" +#include +#include +#include -ILXQtTaskbarAbstractBackend *createWMBackend() +#include "backends/lxqtdummywmbackend.h" + +static inline QMap getBackendScoreMap( QString compositor ) { - if(qGuiApp->nativeInterface()) - return new LXQtTaskbarX11Backend; + QStringList dirs; + dirs << QProcessEnvironment::systemEnvironment().value(QStringLiteral("LXQTPANEL_PLUGIN_PATH")).split(QStringLiteral(":")); + dirs << QStringLiteral(PLUGIN_DIR); + + QMap backendScoreMap; - qWarning() << "\n" - << "ERROR: Could not create a backend for window managment operations.\n" - << "Only X11 supported!\n" - << "Falling back to dummy backend. Some functions will not be available.\n" - << "\n"; + for(const QString& dir : std::as_const(dirs)) + { + QDir backendsDir(dir); + if ( QFile::exists( dir + QStringLiteral("/backend") ) ) { + backendsDir.cd(QLatin1String("backend")); + } - return new LXQtTaskBarDummyBackend; + const auto entryList = backendsDir.entryInfoList(QStringList() << QStringLiteral("*.so"), QDir::Files|QDir::System|QDir::Readable); + for(QFileInfo info: entryList) + { + QPluginLoader loader(info.absoluteFilePath()); + if(!loader.load()) + { + QString err = loader.errorString(); + qWarning() << "Backend error:" << err; + } + + QObject *plugin = loader.instance(); + if(!plugin) + continue; + + ILXQtWMBackendLibrary *backend = qobject_cast(plugin); + if(backend) + { + backendScoreMap[ info.fileName() ] = backend->getBackendScore( compositor ); + } + loader.unload(); + } + } + + return backendScoreMap; +} + +static inline QString getBackendFilePath( QString name ) +{ + // If we do not have a full library name, line lib_labwc_backend.so, + // then build a name based on default heuristic: libwmbackend_.so + if (!name.startsWith(QStringLiteral("lib")) || !name.endsWith(QStringLiteral(".so"))) + { + if ( !name.startsWith( QStringLiteral("libwmbackend_") ) ) + { + name = QString( QStringLiteral("libwmbackend_%1") ).arg( name ); + } + if ( !name.endsWith( QStringLiteral(".so") ) ) + { + name = QString( QStringLiteral("%1.so") ).arg( name ); + } + } + + QStringList dirs; + dirs << QProcessEnvironment::systemEnvironment().value(QStringLiteral("LXQTPANEL_PLUGIN_PATH")).split(QStringLiteral(":")); + dirs << QStringLiteral(PLUGIN_DIR); + + QMap backendScoreMap; + + for(const QString& dir : std::as_const(dirs)) + { + QDir backendsDir(dir); + if ( QFile::exists( dir + QStringLiteral("/backend") ) ) { + backendsDir.cd(QLatin1String("backend")); + } + + if ( backendsDir.exists( name ) ) + { + return backendsDir.absoluteFilePath( name ); + } + } + + return QString(); +} + +static inline bool testBackend( QString backendName ) +{ + QString backendPath = getBackendFilePath( backendName ); + + QPluginLoader loader(backendPath); + if(!loader.load()) + { + qWarning() << "Backend error:" << loader.errorString(); + return false; + } + + QObject *plugin = loader.instance(); + if(!plugin) { + qWarning() << "Failed to create the plugin instance"; + return false; + } + + ILXQtWMBackendLibrary *backend = qobject_cast(plugin); + bool okay = false; + if(backend) + { + okay = true; + } + + loader.unload(); + + return okay; } LXQtPanelApplicationPrivate::LXQtPanelApplicationPrivate(LXQtPanelApplication *q) - : mSettings(nullptr), - q_ptr(q) + : mSettings(nullptr) + , mWMBackend(nullptr) + , q_ptr(q) { - mWMBackend = createWMBackend(); + } @@ -90,6 +188,134 @@ ILXQtPanel::Position LXQtPanelApplicationPrivate::computeNewPanelPosition(const return static_cast (availablePosition); } +void LXQtPanelApplicationPrivate::loadBackend() +{ + /** + * 1. Get the XDG_CURRENT_DESKTOP. It's a colon separate list. + * 2. Get the preferredBackend. It's a comma separated list. + * 3. First attempt to match some value in XDG_CURRENT_DESKTOP with any value in preferredBackend. + * 4. If it matches, end of story. Else, we attempt to deduce the backend based on XDG_CURRENT_DESKTOP: + * a. X11 -> xcb + * b. kwin_wayland -> plasma + * c. wayfire -> wayfire + * d. wayland -> wlroots + * e. other -> dummy + */ + + // Get and split XDG_CURRENT_DESKTOP. + QStringList xdgCurrentDesktops = qEnvironmentVariable( "XDG_CURRENT_DESKTOP" ).split( QStringLiteral(":") ); + + // Get and split XDG_SESSION_TYPE. + QString xdgSessionType = qEnvironmentVariable( "XDG_SESSION_TYPE" ); + + // Get the preferred backends + QStringList preferredBackends = mSettings->value(QStringLiteral("preferred_backend")).toStringList(); + + // The preferred backend + QString preferredBackend; + + for ( QString xdgCurrentDesktop: xdgCurrentDesktops ) + { + for ( QString backend: preferredBackends ) + { + QStringList parts = backend.split(QStringLiteral(":")); + // Invalid format + if (parts.count() != 2) + { + continue; + } + + if ((parts[0] == xdgCurrentDesktop) && testBackend(parts[1])) + { + preferredBackend = parts[1]; + break; + } + } + } + + /** No special considerations. Attempt auto-detection of the platform */ + if ( preferredBackend.isEmpty() ) { + qDebug() << "No user preferences available. Attempting auto-detection."; + + // It's XCB/X11 + if ( xdgSessionType == QStringLiteral("x11") ) { + preferredBackend = QStringLiteral("xcb"); + } + + // It's wayland + else { + int bestScore = 0; + for ( QString xdgCurrentDesktop: xdgCurrentDesktops ) + { + QMap backendScoreMap = getBackendScoreMap( xdgCurrentDesktop ); + for( QString backend: backendScoreMap.keys() ) + { + if ( backendScoreMap[ backend ] > bestScore ) + { + bestScore = backendScoreMap[ backend ]; + // No need to call testBackend(). + // We can be sure the plugin can be loaded. + // Because we have a score. + preferredBackend = backend; + } + } + } + } + } + + if ( preferredBackend.isEmpty() && xdgCurrentDesktops.contains( QStringLiteral("wlroots") ) ) + { + qDebug() << "Specialized backend unavailable. Falling back to generic wlroots"; + preferredBackend = QStringLiteral("wlroots"); + } + + QPluginLoader loader; + + // We now have the preferred backend. + // We have taken into consideration, the user's choice. + // In case it was unavailable, a default one has been chosen. + if(!preferredBackend.isEmpty()) + { + loader.setFileName(getBackendFilePath(preferredBackend)); + if (loader.load()) + { + QObject *plugin = loader.instance(); + ILXQtWMBackendLibrary *backend = qobject_cast(plugin); + if(backend) + { + mWMBackend = backend->instance(); + } + else + { + // Plugin not valid + loader.unload(); + } + } + else + { + qWarning() << loader.errorString(); + } + } + + if(mWMBackend) + { + qDebug() << "\nPanel backend:" << preferredBackend << "\n"; + } + else + { + // If no backend can be found fall back to dummy backend + loader.unload(); + mWMBackend = new LXQtDummyWMBackend; + + qWarning() << "\n" + << "ERROR: Could not create a backend for window managment operations.\n" + << "Falling back to dummy backend. Some functions will not be available.\n" + << "\n"; + } + + mWMBackend->setParent(q_ptr); +} + LXQtPanelApplication::LXQtPanelApplication(int& argc, char** argv) : LXQt::Application(argc, argv, true), d_ptr(new LXQtPanelApplicationPrivate(this)) @@ -124,6 +350,8 @@ LXQtPanelApplication::LXQtPanelApplication(int& argc, char** argv) else d->mSettings = new LXQt::Settings(configFile, QSettings::IniFormat, this); + d->loadBackend(); + // This is a workaround for Qt 5 bug #40681. const auto allScreens = screens(); for(QScreen* screen : allScreens) @@ -309,7 +537,7 @@ bool LXQtPanelApplication::isPluginSingletonAndRunning(QString const & pluginId) return false; } -ILXQtTaskbarAbstractBackend *LXQtPanelApplication::getWMBackend() const +ILXQtAbstractWMInterface *LXQtPanelApplication::getWMBackend() const { Q_D(const LXQtPanelApplication); return d->mWMBackend; diff --git a/panel/lxqtpanelapplication.h b/panel/lxqtpanelapplication.h index 15c912884..9ad8e3141 100644 --- a/panel/lxqtpanelapplication.h +++ b/panel/lxqtpanelapplication.h @@ -37,7 +37,7 @@ class QScreen; class LXQtPanel; class LXQtPanelApplicationPrivate; -class ILXQtTaskbarAbstractBackend; +class ILXQtAbstractWMInterface; /*! * \brief The LXQtPanelApplication class inherits from LXQt::Application and @@ -91,7 +91,7 @@ class LXQtPanelApplication : public LXQt::Application */ bool isPluginSingletonAndRunning(QString const & pluginId) const; - ILXQtTaskbarAbstractBackend* getWMBackend() const; + ILXQtAbstractWMInterface* getWMBackend() const; public slots: /*! diff --git a/panel/lxqtpanelapplication_p.h b/panel/lxqtpanelapplication_p.h index db924bf62..609546d26 100644 --- a/panel/lxqtpanelapplication_p.h +++ b/panel/lxqtpanelapplication_p.h @@ -27,7 +27,7 @@ namespace LXQt { class Settings; } -class ILXQtTaskbarAbstractBackend; +class ILXQtAbstractWMInterface; class LXQtPanelApplicationPrivate { Q_DECLARE_PUBLIC(LXQtPanelApplication) @@ -37,10 +37,12 @@ class LXQtPanelApplicationPrivate { ~LXQtPanelApplicationPrivate() {}; LXQt::Settings *mSettings; - ILXQtTaskbarAbstractBackend *mWMBackend; + ILXQtAbstractWMInterface *mWMBackend; ILXQtPanel::Position computeNewPanelPosition(const LXQtPanel *p, const int screenNum); + void loadBackend(); + private: LXQtPanelApplication *const q_ptr; }; diff --git a/panel/resources/panel.conf b/panel/resources/panel.conf index 2c0799cd4..fe2e9d990 100644 --- a/panel/resources/panel.conf +++ b/panel/resources/panel.conf @@ -1,5 +1,8 @@ panels=panel1 +[General] +preferred_backend=KDE:kwin_wayland,KWIN:kwin_wayland + [panel1] plugins=fancymenu,desktopswitch,quicklaunch,taskbar,statusnotifier,tray,mount,volume,worldclock,showdesktop position=Bottom diff --git a/plugin-desktopswitch/desktopswitch.cpp b/plugin-desktopswitch/desktopswitch.cpp index ea2a0fce3..453594f3d 100644 --- a/plugin-desktopswitch/desktopswitch.cpp +++ b/plugin-desktopswitch/desktopswitch.cpp @@ -35,7 +35,7 @@ #include #include "../panel/lxqtpanelapplication.h" -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" #include @@ -75,11 +75,11 @@ DesktopSwitch::DesktopSwitch(const ILXQtPanelPluginStartupInfo &startupInfo) : connect(m_buttons, &QButtonGroup::idClicked, this, &DesktopSwitch::setDesktop); - connect(mBackend, &ILXQtTaskbarAbstractBackend::workspacesCountChanged, this, &DesktopSwitch::onNumberOfDesktopsChanged); - connect(mBackend, &ILXQtTaskbarAbstractBackend::currentWorkspaceChanged, this, &DesktopSwitch::onCurrentDesktopChanged); - connect(mBackend, &ILXQtTaskbarAbstractBackend::workspaceNameChanged, this, &DesktopSwitch::onDesktopNamesChanged); + connect(mBackend, &ILXQtAbstractWMInterface::workspacesCountChanged, this, &DesktopSwitch::onNumberOfDesktopsChanged); + connect(mBackend, &ILXQtAbstractWMInterface::currentWorkspaceChanged, this, &DesktopSwitch::onCurrentDesktopChanged); + connect(mBackend, &ILXQtAbstractWMInterface::workspaceNameChanged, this, &DesktopSwitch::onDesktopNamesChanged); - connect(mBackend, &ILXQtTaskbarAbstractBackend::windowPropertyChanged, this, &DesktopSwitch::onWindowChanged); + connect(mBackend, &ILXQtAbstractWMInterface::windowPropertyChanged, this, &DesktopSwitch::onWindowChanged); } void DesktopSwitch::registerShortcuts() diff --git a/plugin-desktopswitch/desktopswitch.h b/plugin-desktopswitch/desktopswitch.h index 182eb0ced..07f14ce8d 100644 --- a/plugin-desktopswitch/desktopswitch.h +++ b/plugin-desktopswitch/desktopswitch.h @@ -41,7 +41,7 @@ namespace LXQt { class GridLayout; } -class ILXQtTaskbarAbstractBackend; +class ILXQtAbstractWMInterface; class DesktopSwitchWidget: public QFrame { @@ -83,7 +83,7 @@ class DesktopSwitch : public QObject, public ILXQtPanelPlugin LXQt::GridLayout *mLayout; int mRows; bool mShowOnlyActive; - ILXQtTaskbarAbstractBackend *mBackend; + ILXQtAbstractWMInterface *mBackend; DesktopSwitchButton::LabelType mLabelType; void refresh(); diff --git a/plugin-desktopswitch/desktopswitchconfiguration.cpp b/plugin-desktopswitch/desktopswitchconfiguration.cpp index 33781f7d8..39fac7f47 100644 --- a/plugin-desktopswitch/desktopswitchconfiguration.cpp +++ b/plugin-desktopswitch/desktopswitchconfiguration.cpp @@ -28,7 +28,7 @@ #include "ui_desktopswitchconfiguration.h" #include "../panel/lxqtpanelapplication.h" -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" #include #include diff --git a/plugin-showdesktop/showdesktop.cpp b/plugin-showdesktop/showdesktop.cpp index fb69f6067..4fa14658d 100644 --- a/plugin-showdesktop/showdesktop.cpp +++ b/plugin-showdesktop/showdesktop.cpp @@ -33,7 +33,7 @@ #include "../panel/pluginsettings.h" #include "../panel/lxqtpanelapplication.h" -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" #define DEFAULT_SHORTCUT "Control+Alt+D" diff --git a/plugin-taskbar/lxqttaskbar.cpp b/plugin-taskbar/lxqttaskbar.cpp index fb41ae7e0..9f3d40484 100644 --- a/plugin-taskbar/lxqttaskbar.cpp +++ b/plugin-taskbar/lxqttaskbar.cpp @@ -50,7 +50,7 @@ #include "lxqttaskgroup.h" #include "../panel/pluginsettings.h" -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" #include "../panel/lxqtpanelapplication.h" using namespace LXQt; @@ -104,9 +104,9 @@ LXQtTaskBar::LXQtTaskBar(ILXQtPanelPlugin *plugin, QWidget *parent) : connect(mSignalMapper, &QSignalMapper::mappedInt, this, &LXQtTaskBar::activateTask); QTimer::singleShot(0, this, &LXQtTaskBar::registerShortcuts); - connect(mBackend, &ILXQtTaskbarAbstractBackend::windowPropertyChanged, this, &LXQtTaskBar::onWindowChanged); - connect(mBackend, &ILXQtTaskbarAbstractBackend::windowAdded, this, &LXQtTaskBar::onWindowAdded); - connect(mBackend, &ILXQtTaskbarAbstractBackend::windowRemoved, this, &LXQtTaskBar::onWindowRemoved); + connect(mBackend, &ILXQtAbstractWMInterface::windowPropertyChanged, this, &LXQtTaskBar::onWindowChanged); + connect(mBackend, &ILXQtAbstractWMInterface::windowAdded, this, &LXQtTaskBar::onWindowAdded); + connect(mBackend, &ILXQtAbstractWMInterface::windowRemoved, this, &LXQtTaskBar::onWindowRemoved); // Consider already fetched windows const auto initialWindows = mBackend->getCurrentWindows(); diff --git a/plugin-taskbar/lxqttaskbar.h b/plugin-taskbar/lxqttaskbar.h index 9f94a2958..c6d6a6d29 100644 --- a/plugin-taskbar/lxqttaskbar.h +++ b/plugin-taskbar/lxqttaskbar.h @@ -47,7 +47,7 @@ class LXQtTaskGroup; class LeftAlignedTextStyle; -class ILXQtTaskbarAbstractBackend; +class ILXQtAbstractWMInterface; namespace LXQt { class GridLayout; @@ -86,7 +86,7 @@ class LXQtTaskBar : public QFrame ILXQtPanel * panel() const; inline ILXQtPanelPlugin * plugin() const { return mPlugin; } - inline ILXQtTaskbarAbstractBackend *getBackend() const { return mBackend; } + inline ILXQtAbstractWMInterface *getBackend() const { return mBackend; } public slots: void settingsChanged(); @@ -158,7 +158,7 @@ private slots: QWidget *mPlaceHolder; LeftAlignedTextStyle *mStyle; - ILXQtTaskbarAbstractBackend *mBackend; + ILXQtAbstractWMInterface *mBackend; }; #endif // LXQTTASKBAR_H diff --git a/plugin-taskbar/lxqttaskbarconfiguration.cpp b/plugin-taskbar/lxqttaskbarconfiguration.cpp index 0dd528e51..0e441f51b 100644 --- a/plugin-taskbar/lxqttaskbarconfiguration.cpp +++ b/plugin-taskbar/lxqttaskbarconfiguration.cpp @@ -31,7 +31,7 @@ #include "ui_lxqttaskbarconfiguration.h" #include "../panel/lxqtpanelapplication.h" -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" LXQtTaskbarConfiguration::LXQtTaskbarConfiguration(PluginSettings *settings, QWidget *parent): LXQtPanelPluginConfigDialog(settings, parent), diff --git a/plugin-taskbar/lxqttaskbarproxymodel.cpp b/plugin-taskbar/lxqttaskbarproxymodel.cpp index fdcdfa4d4..e02317ff7 100644 --- a/plugin-taskbar/lxqttaskbarproxymodel.cpp +++ b/plugin-taskbar/lxqttaskbarproxymodel.cpp @@ -1,6 +1,6 @@ #include "lxqttaskbarproxymodel.h" -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" #include @@ -209,12 +209,12 @@ void LXQtTaskBarProxyModel::setGroupByWindowClass(bool newGroupByWindowClass) } -ILXQtTaskbarAbstractBackend *LXQtTaskBarProxyModel::backend() const +ILXQtAbstractWMInterface *LXQtTaskBarProxyModel::backend() const { return m_backend; } -void LXQtTaskBarProxyModel::setBackend(ILXQtTaskbarAbstractBackend *newBackend) +void LXQtTaskBarProxyModel::setBackend(ILXQtAbstractWMInterface *newBackend) { beginResetModel(); @@ -222,11 +222,11 @@ void LXQtTaskBarProxyModel::setBackend(ILXQtTaskbarAbstractBackend *newBackend) if(m_backend) { - disconnect(m_backend, &ILXQtTaskbarAbstractBackend::windowAdded, + disconnect(m_backend, &ILXQtAbstractWMInterface::windowAdded, this, &LXQtTaskBarProxyModel::onWindowAdded); - disconnect(m_backend, &ILXQtTaskbarAbstractBackend::windowRemoved, + disconnect(m_backend, &ILXQtAbstractWMInterface::windowRemoved, this, &LXQtTaskBarProxyModel::onWindowRemoved); - disconnect(m_backend, &ILXQtTaskbarAbstractBackend::windowPropertyChanged, + disconnect(m_backend, &ILXQtAbstractWMInterface::windowPropertyChanged, this, &LXQtTaskBarProxyModel::onWindowPropertyChanged); } @@ -234,11 +234,11 @@ void LXQtTaskBarProxyModel::setBackend(ILXQtTaskbarAbstractBackend *newBackend) if(m_backend) { - connect(m_backend, &ILXQtTaskbarAbstractBackend::windowAdded, + connect(m_backend, &ILXQtAbstractWMInterface::windowAdded, this, &LXQtTaskBarProxyModel::onWindowAdded); - connect(m_backend, &ILXQtTaskbarAbstractBackend::windowRemoved, + connect(m_backend, &ILXQtAbstractWMInterface::windowRemoved, this, &LXQtTaskBarProxyModel::onWindowRemoved); - connect(m_backend, &ILXQtTaskbarAbstractBackend::windowPropertyChanged, + connect(m_backend, &ILXQtAbstractWMInterface::windowPropertyChanged, this, &LXQtTaskBarProxyModel::onWindowPropertyChanged); // Reload current windows diff --git a/plugin-taskbar/lxqttaskbarproxymodel.h b/plugin-taskbar/lxqttaskbarproxymodel.h index 8bbb5ec49..c78c28f5b 100644 --- a/plugin-taskbar/lxqttaskbarproxymodel.h +++ b/plugin-taskbar/lxqttaskbarproxymodel.h @@ -6,7 +6,7 @@ #include "../panel/backends/lxqttaskbartypes.h" -class ILXQtTaskbarAbstractBackend; +class ILXQtAbstractWMInterface; class LXQtTaskBarProxyModelWindow { @@ -64,8 +64,8 @@ class LXQtTaskBarProxyModel : public QAbstractListModel QIcon getWindowIcon(int itemRow, int windowIdxInGroup, int devicePixels) const; - ILXQtTaskbarAbstractBackend *backend() const; - void setBackend(ILXQtTaskbarAbstractBackend *newBackend); + ILXQtAbstractWMInterface *backend() const; + void setBackend(ILXQtAbstractWMInterface *newBackend); bool groupByWindowClass() const; void setGroupByWindowClass(bool newGroupByWindowClass); @@ -90,7 +90,7 @@ private slots: } private: - ILXQtTaskbarAbstractBackend *m_backend; + ILXQtAbstractWMInterface *m_backend; QVector m_items; diff --git a/plugin-taskbar/lxqttaskbutton.cpp b/plugin-taskbar/lxqttaskbutton.cpp index c86e6af5f..3f6ea7dbf 100644 --- a/plugin-taskbar/lxqttaskbutton.cpp +++ b/plugin-taskbar/lxqttaskbutton.cpp @@ -50,7 +50,7 @@ #include #include -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" @@ -128,7 +128,7 @@ LXQtTaskButton::~LXQtTaskButton() = default; void LXQtTaskButton::updateText() { QString title = mBackend->getWindowTitle(mWindow); - setText(title.replace(QStringLiteral("&"), QStringLiteral("&&"))); + setTextExplicitly(title.replace(QStringLiteral("&"), QStringLiteral("&&"))); setToolTip(title); } @@ -314,6 +314,30 @@ QMimeData * LXQtTaskButton::mimeData() return mimedata; } +/*! + * \brief LXQtTaskButton::setTextExplicitly + * \param str + * + * This is needed to workaround flickering caused by KAcceleratorManager + * This class is hooked by KDE Integration and adds accelerators to button text + * (Adds some '&' characters) + * This triggers widget update but soon after text is reset to original value + * This triggers a KAcceleratorManager update which again adds accelerator + * This happens in loop + * + * TODO: investigate proper solution + */ +void LXQtTaskButton::setTextExplicitly(const QString &str) +{ + if(str == mExplicitlySetText) + { + return; + } + + mExplicitlySetText = str; + setText(mExplicitlySetText); +} + /************************************************ ************************************************/ @@ -688,6 +712,7 @@ void LXQtTaskButton::contextMenuEvent(QContextMenuEvent* event) menu->addSeparator(); a = menu->addAction(XdgIcon::fromTheme(QStringLiteral("process-stop")), tr("&Close")); connect(a, &QAction::triggered, this, &LXQtTaskButton::closeApplication); + menu->setGeometry(mParentTaskBar->panel()->calculatePopupWindowPos(mapToGlobal(event->pos()), menu->sizeHint())); mPlugin->willShowWindow(menu); menu->show(); diff --git a/plugin-taskbar/lxqttaskbutton.h b/plugin-taskbar/lxqttaskbutton.h index 9ccca36fa..7a3deeb13 100644 --- a/plugin-taskbar/lxqttaskbutton.h +++ b/plugin-taskbar/lxqttaskbutton.h @@ -41,7 +41,7 @@ class QPalette; class QMimeData; class LXQtTaskBar; -class ILXQtTaskbarAbstractBackend; +class ILXQtAbstractWMInterface; class LeftAlignedTextStyle : public QProxyStyle { @@ -122,9 +122,11 @@ public slots: inline ILXQtPanelPlugin * plugin() const { return mPlugin; } + void setTextExplicitly(const QString& str); + protected: //TODO: public getter instead? - ILXQtTaskbarAbstractBackend *mBackend; + ILXQtAbstractWMInterface *mBackend; private: void moveApplicationToPrevNextDesktop(bool next); @@ -138,6 +140,8 @@ public slots: int mIconSize; int mWheelDelta; + QString mExplicitlySetText; + // Timer for when draggind something into a button (the button's window // must be activated so that the use can continue dragging to the window QTimer * mDNDTimer; diff --git a/plugin-taskbar/lxqttaskgroup.cpp b/plugin-taskbar/lxqttaskgroup.cpp index 8317c034f..3aedc50f2 100644 --- a/plugin-taskbar/lxqttaskgroup.cpp +++ b/plugin-taskbar/lxqttaskgroup.cpp @@ -42,7 +42,7 @@ #include #include -#include "../panel/backends/ilxqttaskbarabstractbackend.h" +#include "../panel/backends/ilxqtabstractwmiface.h" /************************************************ @@ -57,7 +57,7 @@ LXQtTaskGroup::LXQtTaskGroup(const QString &groupName, WId window, LXQtTaskBar * Q_ASSERT(parent); setObjectName(groupName); - setText(groupName); + setTextExplicitly(groupName); connect(this, &LXQtTaskGroup::clicked, this, &LXQtTaskGroup::onClicked); connect(parent, &LXQtTaskBar::buttonRotationRefreshed, this, &LXQtTaskGroup::setAutoRotation); @@ -65,8 +65,8 @@ LXQtTaskGroup::LXQtTaskGroup(const QString &groupName, WId window, LXQtTaskBar * connect(parent, &LXQtTaskBar::buttonStyleRefreshed, this, &LXQtTaskGroup::setToolButtonsStyle); connect(parent, &LXQtTaskBar::showOnlySettingChanged, this, &LXQtTaskGroup::refreshVisibility); connect(parent, &LXQtTaskBar::popupShown, this, &LXQtTaskGroup::groupPopupShown); - connect(mBackend, &ILXQtTaskbarAbstractBackend::currentWorkspaceChanged, this, &LXQtTaskGroup::onDesktopChanged); - connect(mBackend, &ILXQtTaskbarAbstractBackend::activeWindowChanged, this, &LXQtTaskGroup::onActiveWindowChanged); + connect(mBackend, &ILXQtAbstractWMInterface::currentWorkspaceChanged, this, &LXQtTaskGroup::onDesktopChanged); + connect(mBackend, &ILXQtAbstractWMInterface::activeWindowChanged, this, &LXQtTaskGroup::onActiveWindowChanged); } /************************************************ @@ -336,7 +336,7 @@ void LXQtTaskGroup::regroup() if (button) { - setText(button->text()); + setTextExplicitly(button->text()); setToolTip(button->toolTip()); setWindowId(button->windowId()); } @@ -347,7 +347,7 @@ void LXQtTaskGroup::regroup() { mSingleButton = false; QString t = QString(QStringLiteral("%1 - %2 windows")).arg(mGroupName).arg(cont); - setText(t); + setTextExplicitly(t); setToolTip(parentTaskBar()->isShowGroupOnHover() ? QString() : t); } }