diff --git a/.github/workflows/qt5platform-plugins-archlinux-build.yml b/.github/workflows/qt5platform-plugins-archlinux-build.yml index b6404e33..76c0fd6e 100644 --- a/.github/workflows/qt5platform-plugins-archlinux-build.yml +++ b/.github/workflows/qt5platform-plugins-archlinux-build.yml @@ -30,7 +30,7 @@ jobs: libxkbcommon libxkbcommon-x11 \ libxi libxrender libsm libice cairo \ dbus libglvnd fontconfig glib2 mtdev systemd \ - kwayland5 \ + kwayland5 deepin-wayland-protocols \ dtkcommon - uses: actions/checkout@v4 diff --git a/archlinux/PKGBUILD b/archlinux/PKGBUILD index 62a493fb..3345b787 100644 --- a/archlinux/PKGBUILD +++ b/archlinux/PKGBUILD @@ -12,7 +12,7 @@ license=('GPL3') provides=('deepin-qt5platform-plugins') conflicts=('deepin-qt5platform-plugins') depends=('cairo' 'qt5-wayland' 'qt5-x11extras' 'qt5-base' 'dwayland') -makedepends=('git' 'expac' 'qt5-xcb-private-headers' 'libglvnd' 'ninja' 'cmake' 'libxcb' 'gtest' 'gmock' 'extra-cmake-modules') +makedepends=('git' 'expac' 'qt5-xcb-private-headers' 'libglvnd' 'ninja' 'cmake' 'libxcb' 'gtest' 'gmock' 'extra-cmake-modules' 'deepin-wayland-protocols') groups=('deepin-git') source=("${sourcetars[@]}") sha512sums=('SKIP') diff --git a/debian/control b/debian/control index 8a8b2645..dddfc8f8 100644 --- a/debian/control +++ b/debian/control @@ -37,6 +37,7 @@ Build-Depends: debhelper-compat (= 12), libcairo2-dev, libkf5wayland-dev , libdwayland-dev , + deepin-wayland-protocols , libwayland-dev, libxcb-xinput-dev, libxcb-util-dev | libxcb-util0-dev, diff --git a/src/global.h b/src/global.h index 9ed8a42b..e5f03005 100644 --- a/src/global.h +++ b/src/global.h @@ -107,6 +107,9 @@ DEFINE_CONST_CHAR(supportForSplittingWindow); DEFINE_CONST_CHAR(sendEndStartupNotifition); DEFINE_CONST_CHAR(splitWindowOnScreenByType); DEFINE_CONST_CHAR(supportForSplittingWindowByType); +DEFINE_CONST_CHAR(supportSplitMenu); +DEFINE_CONST_CHAR(showSplitMenu); +DEFINE_CONST_CHAR(hideSplitMenu); // others DEFINE_CONST_CHAR(WmWindowTypes); diff --git a/wayland/dwayland/CMakeLists.txt b/wayland/dwayland/CMakeLists.txt index cb1c9ddc..ccc55d50 100644 --- a/wayland/dwayland/CMakeLists.txt +++ b/wayland/dwayland/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +# SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd. # # SPDX-License-Identifier: LGPL-3.0-or-later @@ -12,11 +12,20 @@ endif() find_package(PkgConfig REQUIRED) pkg_check_modules(WAYLAND REQUIRED wayland-client wayland-cursor IMPORTED_TARGET) pkg_check_modules(X11 REQUIRED xkbcommon IMPORTED_TARGET) +find_package(ECM REQUIRED) +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_MODULE_PATH}) +find_package(WaylandScanner REQUIRED) +find_package(DeepinWaylandProtocols REQUIRED CONFIG) add_definitions(-DQT5DWAYLANDPLUGIN_LIBRARY -DQT_DEPRECATED_WARNINGS) include(${CMAKE_CURRENT_LIST_DIR}/dwayland.cmake) +ecm_add_wayland_client_protocol(wayland_SRC + PROTOCOL ${DEEPIN_WAYLAND_PROTOCOLS_DIR}/client-management.xml + BASENAME deepin-client-management +) + add_library(${PROJECT_NAME} MODULE main.cpp ${wayland_SRC}) target_compile_definitions(${PROJECT_NAME} diff --git a/wayland/dwayland/dclientmanagement.cpp b/wayland/dwayland/dclientmanagement.cpp new file mode 100644 index 00000000..07ac95ff --- /dev/null +++ b/wayland/dwayland/dclientmanagement.cpp @@ -0,0 +1,187 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "dclientmanagement.h" + +#include "dwaylandintegration.h" +#include "wayland-deepin-client-management-client-protocol.h" + +#define private public +#include +#undef private + +#include + +#include + +DPP_BEGIN_NAMESPACE + +#ifndef QT_DEBUG +Q_LOGGING_CATEGORY(lcWaylandSplitMenu, "dtk.qpa.wayland.splitmenu", QtInfoMsg) +#else +Q_LOGGING_CATEGORY(lcWaylandSplitMenu, "dtk.qpa.wayland.splitmenu") +#endif + +DClientManagement *DClientManagement::instance() +{ + static DClientManagement *management = new DClientManagement; + return management; +} + +bool DClientManagement::ensureBound() +{ + if (m_management) + return true; + + auto *display = DWaylandIntegration::instance()->display(); + if (!display) { + qCDebug(lcWaylandSplitMenu) << "Cannot bind client management without a Wayland display"; + return false; + } + + const auto globals = display->globals(); + for (const auto &global : globals) { + if (global.interface != QLatin1String("com_deepin_client_management")) + continue; + + m_management = static_cast( + wl_registry_bind(global.registry, global.id, + &com_deepin_client_management_interface, + qMin(global.version, 1u))); + if (!m_management) { + qCWarning(lcWaylandSplitMenu) << "Failed to bind com_deepin_client_management"; + return false; + } + + static const com_deepin_client_management_listener listener = { + DClientManagement::windowStates, + DClientManagement::windowFromPoint, + DClientManagement::allWindowId, + DClientManagement::specificWindowState, + }; + com_deepin_client_management_add_listener(m_management, &listener, this); + qCDebug(lcWaylandSplitMenu) << "Bound com_deepin_client_management, registry id:" + << global.id << "version:" << qMin(global.version, 1u); + return true; + } + + qCDebug(lcWaylandSplitMenu) << "com_deepin_client_management is not advertised"; + return false; +} + +bool DClientManagement::isValid() +{ + return ensureBound(); +} + +void DClientManagement::showSplitMenu(WId wid, const QRect &buttonRect) +{ + if (!wid || !buttonRect.isValid()) { + qCDebug(lcWaylandSplitMenu) << "Ignore invalid SplitMenu show request, wid:" << wid + << "button rect:" << buttonRect; + return; + } + + if (!ensureBound()) { + qCDebug(lcWaylandSplitMenu) << "Cannot show SplitMenu: client management is unavailable"; + return; + } + + m_pendingButtonRect = buttonRect; + m_showPending = true; + m_windowId = wid; + m_pendingRequests.enqueue(++m_requestSerial); + com_deepin_client_management_get_window_from_point(m_management); + wl_display_flush(DWaylandIntegration::instance()->display()->wl_display()); + qCDebug(lcWaylandSplitMenu) << "Requested window id for SplitMenu, Qt wid:" << wid + << "button rect:" << buttonRect; +} + +void DClientManagement::hideSplitMenu(WId wid, bool delay) +{ + if (!wid || wid != m_windowId) + return; + m_showPending = false; + ++m_requestSerial; + if (!ensureBound()) { + qCDebug(lcWaylandSplitMenu) << "Cannot hide SplitMenu: client management is unavailable"; + return; + } + + com_deepin_client_management_hide_split_menu(m_management, delay); + wl_display_flush(DWaylandIntegration::instance()->display()->wl_display()); + qCDebug(lcWaylandSplitMenu) << "Sent SplitMenu hide request, Qt wid:" << wid + << "delay:" << delay; +} + +void DClientManagement::handleWindowFromPoint(quint32 windowId) +{ + if (m_pendingRequests.isEmpty() || m_pendingRequests.dequeue() != m_requestSerial) + return; + if (!m_showPending || !windowId || !m_management) { + qCDebug(lcWaylandSplitMenu) << "Ignore window-from-point result, pending:" << m_showPending + << "window id:" << windowId + << "management valid:" << bool(m_management); + return; + } + + m_showPending = false; + com_deepin_client_management_show_split_menu( + m_management, + m_pendingButtonRect.x(), m_pendingButtonRect.y(), + m_pendingButtonRect.width(), m_pendingButtonRect.height(), + windowId); + wl_display_flush(DWaylandIntegration::instance()->display()->wl_display()); + qCDebug(lcWaylandSplitMenu) << "Sent SplitMenu show request, compositor window id:" + << windowId << "button rect:" << m_pendingButtonRect; +} + +void DClientManagement::windowStates(void *data, com_deepin_client_management *management, + quint32 count, wl_array *states) +{ + Q_UNUSED(data) + Q_UNUSED(management) + Q_UNUSED(count) + Q_UNUSED(states) +} + +void DClientManagement::windowFromPoint(void *data, com_deepin_client_management *management, + quint32 windowId) +{ + Q_UNUSED(management) + static_cast(data)->handleWindowFromPoint(windowId); +} + +void DClientManagement::allWindowId(void *data, com_deepin_client_management *management, + wl_array *idArray) +{ + Q_UNUSED(data) + Q_UNUSED(management) + Q_UNUSED(idArray) +} + +void DClientManagement::specificWindowState( + void *data, com_deepin_client_management *management, + qint32 pid, quint32 windowId, const char *resourceName, + qint32 x, qint32 y, qint32 width, qint32 height, + qint32 isMinimized, qint32 isFullscreen, qint32 isActive, + qint32 splitable, const char *uuid) +{ + Q_UNUSED(data) + Q_UNUSED(management) + Q_UNUSED(pid) + Q_UNUSED(windowId) + Q_UNUSED(resourceName) + Q_UNUSED(x) + Q_UNUSED(y) + Q_UNUSED(width) + Q_UNUSED(height) + Q_UNUSED(isMinimized) + Q_UNUSED(isFullscreen) + Q_UNUSED(isActive) + Q_UNUSED(splitable) + Q_UNUSED(uuid) +} + +DPP_END_NAMESPACE diff --git a/wayland/dwayland/dclientmanagement.h b/wayland/dwayland/dclientmanagement.h new file mode 100644 index 00000000..b2e572e0 --- /dev/null +++ b/wayland/dwayland/dclientmanagement.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef DCLIENTMANAGEMENT_H +#define DCLIENTMANAGEMENT_H + +#include "global.h" + +#include +#include +#include + +struct com_deepin_client_management; +struct wl_array; + +DPP_BEGIN_NAMESPACE + +class DClientManagement +{ +public: + static DClientManagement *instance(); + + bool isValid(); + void showSplitMenu(WId wid, const QRect &buttonRect); + void hideSplitMenu(WId wid, bool delay); + +private: + DClientManagement() = default; + + bool ensureBound(); + void handleWindowFromPoint(quint32 windowId); + + static void windowStates(void *data, com_deepin_client_management *management, + quint32 count, wl_array *states); + static void windowFromPoint(void *data, com_deepin_client_management *management, + quint32 windowId); + static void allWindowId(void *data, com_deepin_client_management *management, + wl_array *idArray); + static void specificWindowState(void *data, com_deepin_client_management *management, + qint32 pid, quint32 windowId, const char *resourceName, + qint32 x, qint32 y, qint32 width, qint32 height, + qint32 isMinimized, qint32 isFullscreen, qint32 isActive, + qint32 splitable, const char *uuid); + + com_deepin_client_management *m_management = nullptr; + QRect m_pendingButtonRect; + bool m_showPending = false; + quint64 m_requestSerial = 0; + QQueue m_pendingRequests; + WId m_windowId = 0; +}; + +DPP_END_NAMESPACE + +#endif // DCLIENTMANAGEMENT_H diff --git a/wayland/dwayland/dwaylandinterfacehook.cpp b/wayland/dwayland/dwaylandinterfacehook.cpp index be7abec2..8cadf7b9 100755 --- a/wayland/dwayland/dwaylandinterfacehook.cpp +++ b/wayland/dwayland/dwaylandinterfacehook.cpp @@ -8,6 +8,7 @@ #include "dhighdpi.h" #include "dxsettings.h" #include "dnotitlebarwindowhelper_wl.h" +#include "dclientmanagement.h" #include #include @@ -46,7 +47,10 @@ static QFunctionPointer getFunction(const QByteArray &function) {splitWindowOnScreen, reinterpret_cast(&DWaylandInterfaceHook::splitWindowOnScreen)}, {supportForSplittingWindow, reinterpret_cast(&DWaylandInterfaceHook::supportForSplittingWindow)}, {splitWindowOnScreenByType, reinterpret_cast(&DWaylandInterfaceHook::splitWindowOnScreenByType)}, - {supportForSplittingWindowByType, reinterpret_cast(&DWaylandInterfaceHook::supportForSplittingWindowByType)} + {supportForSplittingWindowByType, reinterpret_cast(&DWaylandInterfaceHook::supportForSplittingWindowByType)}, + {supportSplitMenu, reinterpret_cast(&DWaylandInterfaceHook::supportSplitMenu)}, + {showSplitMenu, reinterpret_cast(&DWaylandInterfaceHook::showSplitMenu)}, + {hideSplitMenu, reinterpret_cast(&DWaylandInterfaceHook::hideSplitMenu)} }; return functionCache.value(function); } @@ -249,4 +253,21 @@ bool DWaylandInterfaceHook::supportForSplittingWindowByType(quint32 wid, quint32 return propertyValue >= 0 && static_cast(propertyValue) >= screenSplittingType; } +bool DWaylandInterfaceHook::supportSplitMenu(WId wid) +{ + auto *window = fromQtWinId(wid); + return window && window->handle() && supportForSplittingWindow(wid) + && DClientManagement::instance()->isValid(); +} + +void DWaylandInterfaceHook::showSplitMenu(WId wid, const QRect &buttonRect) +{ + DClientManagement::instance()->showSplitMenu(wid, buttonRect); +} + +void DWaylandInterfaceHook::hideSplitMenu(WId wid, bool delay) +{ + DClientManagement::instance()->hideSplitMenu(wid, delay); +} + DPP_END_NAMESPACE diff --git a/wayland/dwayland/dwaylandinterfacehook.h b/wayland/dwayland/dwaylandinterfacehook.h index 3ade8ae7..ae2762b6 100755 --- a/wayland/dwayland/dwaylandinterfacehook.h +++ b/wayland/dwayland/dwaylandinterfacehook.h @@ -14,6 +14,7 @@ QT_BEGIN_NAMESPACE class QObject; class QWindow; class QPointF; +class QRect; class QVariant; class QPlatformNativeInterface; QT_END_NAMESPACE @@ -55,6 +56,9 @@ class DWaylandInterfaceHook static void splitWindowOnScreenByType(WId wid, quint32 position, quint32 type); static bool supportForSplittingWindow(WId wid); static bool supportForSplittingWindowByType(quint32 wid, quint32 screenSplittingType); + static bool supportSplitMenu(WId wid); + static void showSplitMenu(WId wid, const QRect &buttonRect); + static void hideSplitMenu(WId wid, bool delay); }; DPP_END_NAMESPACE diff --git a/xcb/dplatformnativeinterfacehook.cpp b/xcb/dplatformnativeinterfacehook.cpp index d9af2ac3..85372787 100644 --- a/xcb/dplatformnativeinterfacehook.cpp +++ b/xcb/dplatformnativeinterfacehook.cpp @@ -79,7 +79,10 @@ static QFunctionPointer getFunction(const QByteArray &function) {supportForSplittingWindow, reinterpret_cast(&Utility::supportForSplittingWindow)}, {sendEndStartupNotifition, reinterpret_cast(&DPlatformIntegration::sendEndStartupNotifition)}, {splitWindowOnScreenByType, reinterpret_cast(&Utility::splitWindowOnScreenByType)}, - {supportForSplittingWindowByType, reinterpret_cast(&Utility::supportForSplittingWindowByType)} + {supportForSplittingWindowByType, reinterpret_cast(&Utility::supportForSplittingWindowByType)}, + {supportSplitMenu, reinterpret_cast(&Utility::supportSplitMenu)}, + {showSplitMenu, reinterpret_cast(&Utility::showSplitMenu)}, + {hideSplitMenu, reinterpret_cast(&Utility::hideSplitMenu)} }; return functionCache.value(function); diff --git a/xcb/utility.h b/xcb/utility.h index 6c93d717..010349f8 100644 --- a/xcb/utility.h +++ b/xcb/utility.h @@ -75,6 +75,9 @@ class Utility static void splitWindowOnScreenByType(quint32 WId, quint32 position, quint32 type); static bool supportForSplittingWindow(quint32 WId); static bool supportForSplittingWindowByType(quint32 WId, quint32 screenSplittingType); + static bool supportSplitMenu(WId wid); + static void showSplitMenu(WId wid, const QRect &buttonRect); + static void hideSplitMenu(WId wid, bool delay); struct BlurArea { qint32 x; diff --git a/xcb/utility_x11.cpp b/xcb/utility_x11.cpp index 06386bf0..b100a0de 100644 --- a/xcb/utility_x11.cpp +++ b/xcb/utility_x11.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #else @@ -48,6 +49,12 @@ QT_END_NAMESPACE DPP_BEGIN_NAMESPACE +#ifndef QT_DEBUG +Q_LOGGING_CATEGORY(lcDxcbSplitMenu, "dtk.qpa.dxcb.splitmenu", QtInfoMsg) +#else +Q_LOGGING_CATEGORY(lcDxcbSplitMenu, "dtk.qpa.dxcb.splitmenu") +#endif + QImage Utility::dropShadow(const QPixmap &px, qreal radius, const QColor &color) { if (px.isNull()) @@ -500,12 +507,72 @@ bool Utility::supportForSplittingWindowByType(quint32 WId, quint32 screenSplitti auto propAtom = internAtom("_DEEPIN_NET_SUPPORTED"); QByteArray data = windowProperty(WId, propAtom, XCB_ATOM_CARDINAL, 4); - if (const char *cdata = data.constData()) - return *(reinterpret_cast(cdata)) >= screenSplittingType; + if (!data.isEmpty()) + return static_cast(data.at(0)) >= screenSplittingType; return false; } +bool Utility::supportSplitMenu(WId wid) +{ + const bool hasToggleAtom = internAtom("_WM_TOGGLE_SPLIT_MENU", true) != XCB_ATOM_NONE; + const bool splitable = wid && supportForSplittingWindow(wid); + const bool supported = hasToggleAtom && splitable && DXcbWMSupport::instance()->hasComposite(); + qCDebug(lcDxcbSplitMenu) << "SplitMenu support check, wid:" << wid + << "toggle atom:" << hasToggleAtom + << "splitable:" << splitable + << "supported:" << supported; + return supported; +} + +void Utility::showSplitMenu(WId wid, const QRect &buttonRect) +{ + if (!wid || !buttonRect.isValid()) { + qCDebug(lcDxcbSplitMenu) << "Ignore invalid SplitMenu show request, wid:" << wid + << "button rect:" << buttonRect; + return; + } + + xcb_client_message_event_t event{}; + event.response_type = XCB_CLIENT_MESSAGE; + event.type = internAtom("_WM_TOGGLE_SPLIT_MENU", false); + event.window = wid; + event.format = 32; + event.data.data32[0] = 1; + event.data.data32[1] = buttonRect.x(); + event.data.data32[2] = buttonRect.y(); + event.data.data32[3] = buttonRect.width(); + event.data.data32[4] = buttonRect.height(); + + xcb_send_event(QX11Info::connection(), false, QX11Info::appRootWindow(QX11Info::appScreen()), + SubstructureNotifyMask, reinterpret_cast(&event)); + xcb_flush(QX11Info::connection()); + qCDebug(lcDxcbSplitMenu) << "Sent SplitMenu show request, wid:" << wid + << "button rect:" << buttonRect; +} + +void Utility::hideSplitMenu(WId wid, bool delay) +{ + if (!wid) { + qCDebug(lcDxcbSplitMenu) << "Ignore SplitMenu hide request without a window"; + return; + } + + xcb_client_message_event_t event{}; + event.response_type = XCB_CLIENT_MESSAGE; + event.type = internAtom("_WM_TOGGLE_SPLIT_MENU", false); + event.window = wid; + event.format = 32; + event.data.data32[0] = 0; + event.data.data32[1] = delay; + + xcb_send_event(QX11Info::connection(), false, QX11Info::appRootWindow(QX11Info::appScreen()), + SubstructureNotifyMask, reinterpret_cast(&event)); + xcb_flush(QX11Info::connection()); + qCDebug(lcDxcbSplitMenu) << "Sent SplitMenu hide request, wid:" << wid + << "delay:" << delay; +} + bool Utility::setEnableBlurWindow(const quint32 WId, bool enable) { if (!DXcbWMSupport::instance()->hasBlurWindow() || !DXcbWMSupport::instance()->isKwin())