From eb3bde4eb261b89c2f5a14d4059aef2e61f17ac0 Mon Sep 17 00:00:00 2001 From: rewine Date: Thu, 6 Aug 2026 11:17:01 +0800 Subject: [PATCH 1/2] refactor: move qtwaylandscanner from src/modules/tools to waylib/tools Move the qtwaylandscanner tool and its CMake functions from src/modules/tools/ to waylib/tools/, and update all src/modules/ CMakeLists.txt to remove references to the locally-generated protocol .c files that are no longer needed. --- .../SKILL.md | 4 +- src/modules/CMakeLists.txt | 1 - src/modules/activation/CMakeLists.txt | 3 +- src/modules/app-id-resolver/CMakeLists.txt | 3 +- src/modules/dde-shell/CMakeLists.txt | 3 +- src/modules/ddm/CMakeLists.txt | 3 +- src/modules/foreign-toplevel/CMakeLists.txt | 3 +- src/modules/input-manager/CMakeLists.txt | 3 +- .../keyboard-state-notify/CMakeLists.txt | 3 +- src/modules/output-manager/CMakeLists.txt | 3 +- src/modules/personalization/CMakeLists.txt | 3 +- src/modules/prelaunch-splash/CMakeLists.txt | 3 +- src/modules/screensaver/CMakeLists.txt | 3 +- src/modules/shortcut/CMakeLists.txt | 3 +- src/modules/virtual-output/CMakeLists.txt | 3 +- src/modules/wallpaper-color/CMakeLists.txt | 3 +- src/modules/wallpaper/CMakeLists.txt | 6 +-- src/modules/window-management/CMakeLists.txt | 3 +- .../wine-window-management/CMakeLists.txt | 3 +- src/modules/wine-window-state/CMakeLists.txt | 3 +- waylib/CMakeLists.txt | 1 + {src/modules => waylib}/tools/CMakeLists.txt | 43 ++++++++++--------- .../tools/qtwaylandscanner.cpp | 0 23 files changed, 44 insertions(+), 62 deletions(-) rename {src/modules => waylib}/tools/CMakeLists.txt (57%) rename {src/modules => waylib}/tools/qtwaylandscanner.cpp (100%) diff --git a/.agents/skills/treeland-private-wayland-protocol/SKILL.md b/.agents/skills/treeland-private-wayland-protocol/SKILL.md index 3b7cb3c965..f65c8a7558 100644 --- a/.agents/skills/treeland-private-wayland-protocol/SKILL.md +++ b/.agents/skills/treeland-private-wayland-protocol/SKILL.md @@ -28,7 +28,7 @@ Complete these four things correctly: ## Read First 1. The target module `CMakeLists.txt` -2. `src/modules/tools/CMakeLists.txt` +2. `waylib/tools/CMakeLists.txt` 3. Generated files in the build directory 4. `src/seat/helper.cpp` @@ -354,7 +354,7 @@ For new private protocols, prefer `appidresolver`, `prelaunch-splash`, or `scree - `src/modules/app-id-resolver/appidresolver.cpp` - `src/modules/prelaunch-splash/prelaunchsplash.cpp` - `src/modules/screensaver/screensaverinterfacev1.cpp` -- `src/modules/tools/CMakeLists.txt` +- `waylib/tools/CMakeLists.txt` - `src/seat/helper.cpp` ## Output Requirements diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index c38198f658..5590aff22c 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -24,7 +24,6 @@ link_libraries( # ws_generate); compiling it again here would duplicate global symbols # (xdg_*_interface) across the two shared libraries. -add_subdirectory(tools) add_subdirectory(foreign-toplevel) add_subdirectory(output-manager) add_subdirectory(personalization) diff --git a/src/modules/activation/CMakeLists.txt b/src/modules/activation/CMakeLists.txt index e747d3aec3..993f6fdfbd 100644 --- a/src/modules/activation/CMakeLists.txt +++ b/src/modules/activation/CMakeLists.txt @@ -1,6 +1,6 @@ pkg_get_variable(WAYLAND_PROTOCOLS_DATADIR wayland-protocols pkgdatadir) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${WAYLAND_PROTOCOLS_DATADIR}/staging/xdg-activation/xdg-activation-v1.xml BASENAME xdg-activation-v1 ) @@ -11,7 +11,6 @@ impl_treeland( SOURCE ${CMAKE_SOURCE_DIR}/src/modules/activation/activationmanagerinterfacev1.h ${CMAKE_SOURCE_DIR}/src/modules/activation/activationmanagerinterfacev1.cpp - ${CMAKE_BINARY_DIR}/src/modules/activation/wayland-xdg-activation-v1-server-protocol.c INCLUDE $ ) diff --git a/src/modules/app-id-resolver/CMakeLists.txt b/src/modules/app-id-resolver/CMakeLists.txt index 3cc096c320..597f4aa2e5 100644 --- a/src/modules/app-id-resolver/CMakeLists.txt +++ b/src/modules/app-id-resolver/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-app-id-resolver-v1.xml BASENAME treeland-app-id-resolver-v1 ) @@ -11,7 +11,6 @@ impl_treeland( SOURCE appidresolver.h appidresolver.cpp - ${CMAKE_BINARY_DIR}/src/modules/app-id-resolver/wayland-treeland-app-id-resolver-v1-server-protocol.c INCLUDE $ LINK diff --git a/src/modules/dde-shell/CMakeLists.txt b/src/modules/dde-shell/CMakeLists.txt index d5a65b813c..cf6e720080 100644 --- a/src/modules/dde-shell/CMakeLists.txt +++ b/src/modules/dde-shell/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-dde-shell-v1.xml BASENAME treeland-dde-shell-v1 ) @@ -13,7 +13,6 @@ impl_treeland( ${CMAKE_SOURCE_DIR}/src/modules/dde-shell/ddeshellattached.h ${CMAKE_SOURCE_DIR}/src/modules/dde-shell/ddeshellmanagerinterfacev1.cpp ${CMAKE_SOURCE_DIR}/src/modules/dde-shell/ddeshellattached.cpp - ${CMAKE_BINARY_DIR}/src/modules/dde-shell/wayland-treeland-dde-shell-v1-server-protocol.c INCLUDE $ ) diff --git a/src/modules/ddm/CMakeLists.txt b/src/modules/ddm/CMakeLists.txt index 769d803fa4..64ac819f57 100644 --- a/src/modules/ddm/CMakeLists.txt +++ b/src/modules/ddm/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-ddm-v1.xml BASENAME treeland-ddm-v1 ) @@ -11,7 +11,6 @@ impl_treeland( SOURCE ${CMAKE_SOURCE_DIR}/src/modules/ddm/ddminterfacev1.h ${CMAKE_SOURCE_DIR}/src/modules/ddm/ddminterfacev1.cpp - ${CMAKE_BINARY_DIR}/src/modules/ddm/wayland-treeland-ddm-v1-server-protocol.c INCLUDE $ ) diff --git a/src/modules/foreign-toplevel/CMakeLists.txt b/src/modules/foreign-toplevel/CMakeLists.txt index e7610e41ce..2747d29874 100644 --- a/src/modules/foreign-toplevel/CMakeLists.txt +++ b/src/modules/foreign-toplevel/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-foreign-toplevel-manager-v1.xml BASENAME treeland-foreign-toplevel-manager-v1 ) @@ -13,7 +13,6 @@ impl_treeland( ${CMAKE_SOURCE_DIR}/src/modules/foreign-toplevel/foreigntoplevelhandlev1.h ${CMAKE_SOURCE_DIR}/src/modules/foreign-toplevel/foreigntoplevelmanagerv1.h ${CMAKE_SOURCE_DIR}/src/modules/foreign-toplevel/foreigntoplevelmanagerv1.cpp - ${CMAKE_BINARY_DIR}/src/modules/foreign-toplevel/wayland-treeland-foreign-toplevel-manager-v1-server-protocol.c INCLUDE $ ) diff --git a/src/modules/input-manager/CMakeLists.txt b/src/modules/input-manager/CMakeLists.txt index 9788d84e9f..6277000f0d 100644 --- a/src/modules/input-manager/CMakeLists.txt +++ b/src/modules/input-manager/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-input-manager-unstable-v1.xml BASENAME treeland-input-manager-unstable-v1 ) @@ -11,7 +11,6 @@ impl_treeland( SOURCE ${CMAKE_SOURCE_DIR}/src/modules/input-manager/inputmanagerinterfacev1.h ${CMAKE_SOURCE_DIR}/src/modules/input-manager/inputmanagerinterfacev1.cpp - ${CMAKE_BINARY_DIR}/src/modules/input-manager/wayland-treeland-input-manager-unstable-v1-server-protocol.c INCLUDE $ ) diff --git a/src/modules/keyboard-state-notify/CMakeLists.txt b/src/modules/keyboard-state-notify/CMakeLists.txt index 16a2d241ac..ef24dfb214 100644 --- a/src/modules/keyboard-state-notify/CMakeLists.txt +++ b/src/modules/keyboard-state-notify/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-keyboard-state-notify-unstable-v1.xml BASENAME treeland-keyboard-state-notify-unstable-v1 ) @@ -11,7 +11,6 @@ impl_treeland( SOURCE ${CMAKE_SOURCE_DIR}/src/modules/keyboard-state-notify/keyboardstatenotifymanagerinterfacev1.h ${CMAKE_SOURCE_DIR}/src/modules/keyboard-state-notify/keyboardstatenotifymanagerinterfacev1.cpp - ${CMAKE_BINARY_DIR}/src/modules/keyboard-state-notify/wayland-treeland-keyboard-state-notify-unstable-v1-server-protocol.c INCLUDE $ ) diff --git a/src/modules/output-manager/CMakeLists.txt b/src/modules/output-manager/CMakeLists.txt index fdc01223af..158e1c435a 100644 --- a/src/modules/output-manager/CMakeLists.txt +++ b/src/modules/output-manager/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-output-manager-v1.xml BASENAME treeland-output-manager-v1 ) @@ -11,7 +11,6 @@ impl_treeland( SOURCE ${CMAKE_SOURCE_DIR}/src/modules/output-manager/outputmanagement.h ${CMAKE_SOURCE_DIR}/src/modules/output-manager/outputmanagement.cpp - ${CMAKE_BINARY_DIR}/src/modules/output-manager/wayland-treeland-output-manager-v1-server-protocol.c INCLUDE $ ) diff --git a/src/modules/personalization/CMakeLists.txt b/src/modules/personalization/CMakeLists.txt index 2690afe2e9..22eb920fde 100644 --- a/src/modules/personalization/CMakeLists.txt +++ b/src/modules/personalization/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-personalization-manager-v1.xml BASENAME treeland-personalization-manager-v1 ) @@ -13,7 +13,6 @@ set(PUBLIC_HEADERS set(SRCS ${CMAKE_SOURCE_DIR}/src/modules/personalization/personalizationmanagerinterfacev1.cpp - ${CMAKE_BINARY_DIR}/src/modules/personalization/wayland-treeland-personalization-manager-v1-server-protocol.c ) impl_treeland( diff --git a/src/modules/prelaunch-splash/CMakeLists.txt b/src/modules/prelaunch-splash/CMakeLists.txt index db0a6b39d8..8dcc232ab9 100644 --- a/src/modules/prelaunch-splash/CMakeLists.txt +++ b/src/modules/prelaunch-splash/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols 0.5.5 REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-prelaunch-splash-v2.xml BASENAME treeland-prelaunch-splash-v2 ) @@ -11,7 +11,6 @@ impl_treeland( SOURCE prelaunchsplash.h prelaunchsplash.cpp - ${CMAKE_BINARY_DIR}/src/modules/prelaunch-splash/wayland-treeland-prelaunch-splash-v2-server-protocol.c INCLUDE $ LINK diff --git a/src/modules/screensaver/CMakeLists.txt b/src/modules/screensaver/CMakeLists.txt index 7016bd1cad..6795d226c6 100644 --- a/src/modules/screensaver/CMakeLists.txt +++ b/src/modules/screensaver/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols 0.5.3 REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-screensaver-v1.xml BASENAME treeland-screensaver-v1 ) @@ -11,7 +11,6 @@ impl_treeland( SOURCE ${CMAKE_SOURCE_DIR}/src/modules/screensaver/screensaverinterfacev1.h ${CMAKE_SOURCE_DIR}/src/modules/screensaver/screensaverinterfacev1.cpp - ${CMAKE_BINARY_DIR}/src/modules/screensaver/wayland-treeland-screensaver-v1-server-protocol.c INCLUDE $ ) diff --git a/src/modules/shortcut/CMakeLists.txt b/src/modules/shortcut/CMakeLists.txt index fa0401c50a..a451ed359f 100644 --- a/src/modules/shortcut/CMakeLists.txt +++ b/src/modules/shortcut/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-shortcut-manager-v2.xml BASENAME treeland-shortcut-manager-v2 ) @@ -15,7 +15,6 @@ impl_treeland( ${CMAKE_SOURCE_DIR}/src/modules/shortcut/shortcutrunner.cpp ${CMAKE_SOURCE_DIR}/src/modules/shortcut/shortcutcontroller.h ${CMAKE_SOURCE_DIR}/src/modules/shortcut/shortcutcontroller.cpp - ${CMAKE_BINARY_DIR}/src/modules/shortcut/wayland-treeland-shortcut-manager-v2-server-protocol.c INCLUDE $ LINK diff --git a/src/modules/virtual-output/CMakeLists.txt b/src/modules/virtual-output/CMakeLists.txt index 3640cd46ee..6a14b568bd 100644 --- a/src/modules/virtual-output/CMakeLists.txt +++ b/src/modules/virtual-output/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-virtual-output-manager-v1.xml BASENAME treeland-virtual-output-manager-v1 ) @@ -11,7 +11,6 @@ impl_treeland( SOURCE ${CMAKE_SOURCE_DIR}/src/modules/virtual-output/virtualoutputmanagerinterfacev1.h ${CMAKE_SOURCE_DIR}/src/modules/virtual-output/virtualoutputmanagerinterfacev1.cpp - ${CMAKE_BINARY_DIR}/src/modules/virtual-output/wayland-treeland-virtual-output-manager-v1-server-protocol.c LINK Wlroots::wlroots Waylib::WaylibServer diff --git a/src/modules/wallpaper-color/CMakeLists.txt b/src/modules/wallpaper-color/CMakeLists.txt index 57f8bf1369..5ce74e9c28 100644 --- a/src/modules/wallpaper-color/CMakeLists.txt +++ b/src/modules/wallpaper-color/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-wallpaper-color-v1.xml BASENAME treeland-wallpaper-color-v1 ) @@ -11,7 +11,6 @@ impl_treeland( SOURCE ${CMAKE_SOURCE_DIR}/src/modules/wallpaper-color/wallpapercolorinterfacev1.h ${CMAKE_SOURCE_DIR}/src/modules/wallpaper-color/wallpapercolorinterfacev1.cpp - ${CMAKE_BINARY_DIR}/src/modules/wallpaper-color/wayland-treeland-wallpaper-color-v1-server-protocol.c LINK Wlroots::wlroots Waylib::WaylibServer diff --git a/src/modules/wallpaper/CMakeLists.txt b/src/modules/wallpaper/CMakeLists.txt index f472902f6c..0bd88a7b98 100644 --- a/src/modules/wallpaper/CMakeLists.txt +++ b/src/modules/wallpaper/CMakeLists.txt @@ -1,11 +1,11 @@ find_package(TreelandProtocols REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-wallpaper-manager-unstable-v1.xml BASENAME treeland-wallpaper-manager-unstable-v1 ) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-wallpaper-shell-unstable-v1.xml BASENAME treeland-wallpaper-shell-unstable-v1 ) @@ -20,8 +20,6 @@ impl_treeland( ${CMAKE_SOURCE_DIR}/src/modules/wallpaper/wallpapershellinterfacev1.cpp ${CMAKE_SOURCE_DIR}/src/modules/wallpaper/wallpapernotifierinterfacev1.h ${CMAKE_SOURCE_DIR}/src/modules/wallpaper/wallpapernotifierinterfacev1.cpp - ${CMAKE_BINARY_DIR}/src/modules/wallpaper/wayland-treeland-wallpaper-manager-unstable-v1-server-protocol.c - ${CMAKE_BINARY_DIR}/src/modules/wallpaper/wayland-treeland-wallpaper-shell-unstable-v1-server-protocol.c INCLUDE $ ) diff --git a/src/modules/window-management/CMakeLists.txt b/src/modules/window-management/CMakeLists.txt index 86c03f895e..f12084a646 100644 --- a/src/modules/window-management/CMakeLists.txt +++ b/src/modules/window-management/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-window-management-v1.xml BASENAME treeland-window-management-v1 ) @@ -11,7 +11,6 @@ impl_treeland( SOURCE ${CMAKE_SOURCE_DIR}/src/modules/window-management/windowmanagementinterfacev1.h ${CMAKE_SOURCE_DIR}/src/modules/window-management/windowmanagementinterfacev1.cpp - ${CMAKE_BINARY_DIR}/src/modules/window-management/wayland-treeland-window-management-v1-server-protocol.c LINK Wlroots::wlroots Waylib::WaylibServer diff --git a/src/modules/wine-window-management/CMakeLists.txt b/src/modules/wine-window-management/CMakeLists.txt index 5a2b8c289f..cfd9e3deb7 100644 --- a/src/modules/wine-window-management/CMakeLists.txt +++ b/src/modules/wine-window-management/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-wine-window-management-unstable-v1.xml BASENAME treeland-wine-window-management-v1 ) @@ -11,7 +11,6 @@ impl_treeland( SOURCE winewindowmanagement.h winewindowmanagement.cpp - ${CMAKE_BINARY_DIR}/src/modules/wine-window-management/wayland-treeland-wine-window-management-v1-server-protocol.c INCLUDE $ LINK diff --git a/src/modules/wine-window-state/CMakeLists.txt b/src/modules/wine-window-state/CMakeLists.txt index 1857cd969e..daedc7564d 100644 --- a/src/modules/wine-window-state/CMakeLists.txt +++ b/src/modules/wine-window-state/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(TreelandProtocols REQUIRED) -local_qtwayland_server_protocol_treeland(libtreeland +waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-wine-window-state-unstable-v1.xml BASENAME treeland-wine-window-state-v1 ) @@ -11,7 +11,6 @@ impl_treeland( SOURCE winewindowstate.h winewindowstate.cpp - ${CMAKE_BINARY_DIR}/src/modules/wine-window-state/wayland-treeland-wine-window-state-v1-server-protocol.c INCLUDE $ LINK diff --git a/waylib/CMakeLists.txt b/waylib/CMakeLists.txt index e4b3ff7f20..fa2394c620 100644 --- a/waylib/CMakeLists.txt +++ b/waylib/CMakeLists.txt @@ -45,6 +45,7 @@ include(CMakePackageConfigHelpers) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") include(PackageVersionHelper) +add_subdirectory(tools) add_subdirectory(src) if(BUILD_EXAMPLES) add_subdirectory(examples) diff --git a/src/modules/tools/CMakeLists.txt b/waylib/tools/CMakeLists.txt similarity index 57% rename from src/modules/tools/CMakeLists.txt rename to waylib/tools/CMakeLists.txt index f95c4db347..237eda2a1d 100644 --- a/src/modules/tools/CMakeLists.txt +++ b/waylib/tools/CMakeLists.txt @@ -1,4 +1,4 @@ -project(qtwaylandscanner_treeland CXX) +project(qtwaylandscanner_waylib CXX) if (PROJECT_IS_TOP_LEVEL) cmake_minimum_required(VERSION 3.16) @@ -6,9 +6,9 @@ if (PROJECT_IS_TOP_LEVEL) find_package(Qt6 CONFIG REQUIRED Core) endif() -if(CMAKE_CROSSCOMPILING AND QTWAYLANDSCANNER_TREELAND_EXECUTABLE) - add_executable(qtwaylandscanner_treeland IMPORTED GLOBAL) - set_target_properties(qtwaylandscanner_treeland PROPERTIES IMPORTED_LOCATION ${QTWAYLANDSCANNER_TREELAND_EXECUTABLE}) +if(CMAKE_CROSSCOMPILING AND QTWAYLANDSCANNER_EXECUTABLE) + add_executable(qtwaylandscanner_waylib IMPORTED GLOBAL) + set_target_properties(qtwaylandscanner_waylib PROPERTIES IMPORTED_LOCATION ${QTWAYLANDSCANNER_EXECUTABLE}) elseif(CMAKE_CROSSCOMPILING) # search native tooling prefix set(NATIVE_PREFIX "" CACHE STRING "CMAKE_PREFIX_PATH for native Qt libraries") @@ -16,36 +16,36 @@ elseif(CMAKE_CROSSCOMPILING) string(SUBSTRING ${idx} NATIVE_PREFIX) endif() - message(STATUS "Building qtwaylandscanner_treeland against ${NATIVE_PREFIX}") + message(STATUS "Building qtwaylandscanner_waylib against ${NATIVE_PREFIX}") include(ExternalProject) - ExternalProject_Add(native_qtwaylandscanner_treeland + ExternalProject_Add(native_qtwaylandscanner_waylib SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CMAKE_ARGS -DECM_DIR=${ECM_DIR} -DCMAKE_PREFIX_PATH=${NATIVE_PREFIX} -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR} -DQT_MAJOR_VERSION=${QT_MAJOR_VERSION} INSTALL_COMMAND "" - BUILD_BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/native_qtwaylandscanner_treeland-prefix/src/native_qtwaylandscanner_treeland-build/qtwaylandscanner_treeland + BUILD_BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/native_qtwaylandscanner_waylib-prefix/src/native_qtwaylandscanner_waylib-build/qtwaylandscanner_waylib ) - add_executable(qtwaylandscanner_treeland IMPORTED GLOBAL) - add_dependencies(qtwaylandscanner_treeland native_qtwaylandscanner_treeland) - set_target_properties(qtwaylandscanner_treeland PROPERTIES IMPORTED_LOCATION - ${CMAKE_CURRENT_BINARY_DIR}/native_qtwaylandscanner_treeland-prefix/src/native_qtwaylandscanner_treeland-build/qtwaylandscanner_treeland) + add_executable(qtwaylandscanner_waylib IMPORTED GLOBAL) + add_dependencies(qtwaylandscanner_waylib native_qtwaylandscanner_waylib) + set_target_properties(qtwaylandscanner_waylib PROPERTIES IMPORTED_LOCATION + ${CMAKE_CURRENT_BINARY_DIR}/native_qtwaylandscanner_waylib-prefix/src/native_qtwaylandscanner_waylib-build/qtwaylandscanner_waylib) else() # host build remove_definitions(-DQT_NO_CAST_FROM_ASCII) - add_executable(qtwaylandscanner_treeland ${CMAKE_SOURCE_DIR}/src/modules/tools/qtwaylandscanner.cpp) - target_link_libraries(qtwaylandscanner_treeland Qt::Core) + add_executable(qtwaylandscanner_waylib ${CMAKE_CURRENT_SOURCE_DIR}/qtwaylandscanner.cpp) + target_link_libraries(qtwaylandscanner_waylib Qt6::Core) endif() -function(local_qtwayland_server_protocol_treeland target) +function(waylib_generate_qtwayland_server_protocol target) # Parse arguments set(options PRIVATE_CODE) set(oneValueArgs PROTOCOL BASENAME PREFIX) cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "" ${ARGN}) if(ARGS_UNPARSED_ARGUMENTS) - message(FATAL_ERROR "Unknown keywords given to local_qtwayland_server_protocol_treeland(): \"${ARGS_UNPARSED_ARGUMENTS}\"") + message(FATAL_ERROR "Unknown keywords given to waylib_generate_qtwayland_server_protocol(): \"${ARGS_UNPARSED_ARGUMENTS}\"") endif() set(_prefix "${ARGS_PREFIX}") @@ -61,19 +61,19 @@ function(local_qtwayland_server_protocol_treeland target) set(_header "${CMAKE_CURRENT_BINARY_DIR}/qwayland-server-${ARGS_BASENAME}.h") set(_code "${CMAKE_CURRENT_BINARY_DIR}/qwayland-server-${ARGS_BASENAME}.cpp") - set_source_files_properties(${_header} ${_code} GENERATED) + set_source_files_properties(${server_header} ${server_code} ${_header} ${_code} GENERATED) add_custom_command(OUTPUT "${_header}" - COMMAND qtwaylandscanner_treeland server-header ${_infile} "" ${_prefix} > ${_header} - DEPENDS ${_infile} qtwaylandscanner_treeland VERBATIM) + COMMAND qtwaylandscanner_waylib server-header ${_infile} "" ${_prefix} > ${_header} + DEPENDS ${_infile} qtwaylandscanner_waylib VERBATIM) add_custom_command(OUTPUT "${_code}" - COMMAND qtwaylandscanner_treeland server-code ${_infile} "" ${_prefix} > ${_code} - DEPENDS ${_infile} ${_header} qtwaylandscanner_treeland VERBATIM) + COMMAND qtwaylandscanner_waylib server-code ${_infile} "" ${_prefix} > ${_code} + DEPENDS ${_infile} ${_header} qtwaylandscanner_waylib VERBATIM) set_property(SOURCE ${_header} ${_code} PROPERTY SKIP_AUTOMOC ON) - target_sources(${target} PRIVATE "${_code}") + target_sources(${target} PRIVATE "${server_code}" "${_code}") set(_gen_target generate_protocols_${target}_${ARGS_BASENAME}) @@ -83,3 +83,4 @@ function(local_qtwayland_server_protocol_treeland target) add_dependencies(${target} ${_gen_target}) endfunction() + diff --git a/src/modules/tools/qtwaylandscanner.cpp b/waylib/tools/qtwaylandscanner.cpp similarity index 100% rename from src/modules/tools/qtwaylandscanner.cpp rename to waylib/tools/qtwaylandscanner.cpp From f5c47f83233b23e355811b86f7f681cffc6ac43d Mon Sep 17 00:00:00 2001 From: rewine Date: Thu, 6 Aug 2026 14:48:36 +0800 Subject: [PATCH 2/2] feat(wayland): implement cross-process subsurface protocol and rendering integration Add treeland_cross_subsurface_unstable_v1 protocol implementation in waylib with full WSurfaceItem rendering integration. Protocol layer (waylib/src/server/protocols/): - WCrossSubsurfaceManagerV1: manages exported surfaces and remote subsurface relationships via token-based addressing - Export surface with UUID token, create remote subsurface by referencing parent token from another client - Full z-order management (place_above/place_below), position tracking, mapping cascade for nested subsurfaces - Cycle detection via isDescendantOf() - On-demand WSurface wrapper creation for surfaces without shell roles - Desynchronized mode only (per protocol spec) Rendering layer (waylib/src/server/qtquick/wsurfaceitem.*): - WSurfaceItem listens to manager signals for its surface - Creates child WSurfaceItems in dedicated SubsurfaceContainers - Handles add/remove/position/order/mapped/unmapped transitions - Container visibility and surfaceSizeRatio propagation Wire-up (src/seat/helper.cpp): - Attach manager to WServer and set on WSurfaceItem statically Protocol XML sourced from find_package(TreelandProtocols REQUIRED) instead of bundling in waylib source tree. PMS: TASK-393795 --- .github/workflows/waylib-archlinux-build.yml | 12 + .github/workflows/waylib-debian-build.yml | 13 + .github/workflows/waylib-deepin-build.yml | 13 + REUSE.toml | 1 + src/modules/capture/capture.cpp | 17 +- src/seat/helper.cpp | 2 + waylib/debian/control | 3 +- waylib/src/server/CMakeLists.txt | 13 + waylib/src/server/kernel/WSubsurface | 1 + .../src/server/kernel/private/wsubsurface_p.h | 31 + waylib/src/server/kernel/private/wsurface_p.h | 20 +- waylib/src/server/kernel/wsubsurface.cpp | 96 +++ waylib/src/server/kernel/wsubsurface.h | 71 ++ waylib/src/server/kernel/wsurface.cpp | 282 +++++-- waylib/src/server/kernel/wsurface.h | 20 +- .../protocols/wremotesubsurfacemanagerv1.cpp | 765 ++++++++++++++++++ .../protocols/wremotesubsurfacemanagerv1.h | 35 + .../server/qtquick/private/wsurfaceitem_p.h | 32 +- waylib/src/server/qtquick/wsurfaceitem.cpp | 429 +++++----- waylib/src/server/wayliblogging.cpp | 1 + waylib/src/server/wayliblogging.h | 1 + waylib/tools/CMakeLists.txt | 3 - 22 files changed, 1558 insertions(+), 303 deletions(-) create mode 100644 waylib/src/server/kernel/WSubsurface create mode 100644 waylib/src/server/kernel/private/wsubsurface_p.h create mode 100644 waylib/src/server/kernel/wsubsurface.cpp create mode 100644 waylib/src/server/kernel/wsubsurface.h create mode 100644 waylib/src/server/protocols/wremotesubsurfacemanagerv1.cpp create mode 100644 waylib/src/server/protocols/wremotesubsurfacemanagerv1.h diff --git a/.github/workflows/waylib-archlinux-build.yml b/.github/workflows/waylib-archlinux-build.yml index 484f162f3d..db686c1f22 100644 --- a/.github/workflows/waylib-archlinux-build.yml +++ b/.github/workflows/waylib-archlinux-build.yml @@ -40,6 +40,18 @@ jobs: xorg-xwayland pacman -S --noconfirm --noprogressbar fakeroot meson sudo + - name: Build and Install treeland-protocols + run: | + echo "Building treeland-protocols from source..." + git clone https://github.com/linuxdeepin/treeland-protocols.git --depth 1 + cd treeland-protocols + cmake -B build \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_BUILD_TYPE=Release + cmake --build build + cmake --install build + echo "✅ treeland-protocols built and installed from source" + - uses: actions/checkout@v4 - name: Configure and Build waylib with ci preset diff --git a/.github/workflows/waylib-debian-build.yml b/.github/workflows/waylib-debian-build.yml index 0a22cfaa8a..7b72e8f955 100644 --- a/.github/workflows/waylib-debian-build.yml +++ b/.github/workflows/waylib-debian-build.yml @@ -40,6 +40,19 @@ jobs: rm -f /etc/apt/sources.list.d/deepin-community.list apt-get update + # Build and install treeland-protocols locally (required build dependency) + echo "🔧 Building treeland-protocols from source" + WAYLIB_DIR="$PWD" + cd /tmp + if [ ! -d treeland-protocols ]; then + git clone --depth=1 --branch master --single-branch https://github.com/linuxdeepin/treeland-protocols.git + fi + cd treeland-protocols + mk-build-deps --install --remove --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control + dpkg-buildpackage -uc -us -b + apt-get install -y ../*.deb || dpkg -i ../*.deb || apt-get -f install -y + cd "$WAYLIB_DIR" + mk-build-deps --install --remove --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control dpkg-buildpackage -uc -us -b diff --git a/.github/workflows/waylib-deepin-build.yml b/.github/workflows/waylib-deepin-build.yml index 70a2d99375..8996ac883c 100644 --- a/.github/workflows/waylib-deepin-build.yml +++ b/.github/workflows/waylib-deepin-build.yml @@ -36,6 +36,19 @@ jobs: # Install wlr-protocols package directly (ignore auth for this one operation) apt-get install -y --allow-unauthenticated wlr-protocols || true + # Build and install treeland-protocols locally (required build dependency) + echo "🔧 Building treeland-protocols from source" + WAYLIB_DIR="$PWD" + cd /tmp + if [ ! -d treeland-protocols ]; then + git clone --depth=1 --branch master --single-branch https://github.com/linuxdeepin/treeland-protocols.git + fi + cd treeland-protocols + mk-build-deps --install --remove --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control + dpkg-buildpackage -uc -us -b + apt-get install -y ../*.deb || dpkg -i ../*.deb || apt-get -f install -y + cd "$WAYLIB_DIR" + mk-build-deps --install --remove --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control - name: Build waylib deb package diff --git a/REUSE.toml b/REUSE.toml index e38864854e..8b1c976cf0 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -178,6 +178,7 @@ path = [ "waylib/src/server/kernel/WServerInterface", "waylib/src/server/kernel/WSurface", "waylib/src/server/kernel/WSurfaceLayout", + "waylib/src/server/kernel/WSubsurface", "waylib/src/server/kernel/WTexture", "waylib/src/server/kernel/WXdgShell", "waylib/src/server/kernel/WXdgSurface", diff --git a/src/modules/capture/capture.cpp b/src/modules/capture/capture.cpp index 5410733707..b6ebea087f 100644 --- a/src/modules/capture/capture.cpp +++ b/src/modules/capture/capture.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -27,6 +28,7 @@ #include #include +#include #include static inline QRectF scaledRect(const QRectF &rect, qreal devicePixelRatio) @@ -445,6 +447,17 @@ void CaptureManagerV1::freezeAllCapturedSurface(bool freeze, WSurface *mask) { // Exclude cursor surface item and the mask Q_ASSERT(m_outputRenderWindow); + const auto isMaskOrSubsurface = [mask](WSurface *surface) { + if (!mask) + return false; + if (surface == mask) + return true; + const auto subsurfaces = mask->subsurfaces(); + return std::any_of(subsurfaces.cbegin(), subsurfaces.cend(), [surface](WSubsurface *subsurface) { + return subsurface->surface() == surface; + }); + }; + QQueue nodes; nodes.enqueue(m_outputRenderWindow->contentItem()); while (!nodes.isEmpty()) { @@ -453,9 +466,7 @@ void CaptureManagerV1::freezeAllCapturedSurface(bool freeze, WSurface *mask) if (auto cursor = qobject_cast(node->parentItem())) { if (freeze) m_frozenCursorPos = cursor->position(); // Just store position for cursor - } else if (!mask - || (content->surface() != mask - && !mask->subsurfaces().contains(content->surface()))) { + } else if (!isMaskOrSubsurface(content->surface())) { content->setLive(!freeze); } else if (content->surface() == mask) { auto surfaceItem = closestSurfaceItem(content); diff --git a/src/seat/helper.cpp b/src/seat/helper.cpp index 6f74c9d8c5..af540cda8c 100644 --- a/src/seat/helper.cpp +++ b/src/seat/helper.cpp @@ -88,6 +88,7 @@ #include #include #include +#include #include #include #include @@ -2079,6 +2080,7 @@ void Helper::init(Treeland::Treeland *treeland) this, &Helper::onOutputTestOrApply); + m_server->attach(); m_server->attach(); wlr_fractional_scale_manager_v1_create(m_server->handle(), WLR_FRACTIONAL_SCALE_V1_VERSION); wlr_data_control_manager_v1_create(m_server->handle()); diff --git a/waylib/debian/control b/waylib/debian/control index a2a866fdc2..4a3e819108 100644 --- a/waylib/debian/control +++ b/waylib/debian/control @@ -49,7 +49,8 @@ Build-Depends: cmake, libxcb-shm0-dev, libxcb-xfixes0-dev, libxcb-xinput-dev, - xwayland + xwayland, + treeland-protocols, Standards-Version: 3.9.8 Package: libwaylib diff --git a/waylib/src/server/CMakeLists.txt b/waylib/src/server/CMakeLists.txt index 6baa7b6435..6a96653fc4 100644 --- a/waylib/src/server/CMakeLists.txt +++ b/waylib/src/server/CMakeLists.txt @@ -63,6 +63,8 @@ pkg_search_module(XKBCOMMON REQUIRED IMPORTED_TARGET xkbcommon) pkg_search_module(XCB REQUIRED IMPORTED_TARGET xcb) pkg_search_module(EGL REQUIRED IMPORTED_TARGET egl) +find_package(TreelandProtocols REQUIRED) + add_compile_definitions(WLR_PRIVATE=) ws_generate( @@ -143,6 +145,7 @@ set(SOURCES kernel/wevent.cpp kernel/wserver.cpp kernel/wsurface.cpp + kernel/wsubsurface.cpp kernel/wtoplevelsurface.cpp kernel/wwaylandresource.cpp @@ -236,6 +239,7 @@ set(SOURCES protocols/wsessionlockmanager.cpp protocols/wsessionlock.cpp protocols/wsessionlocksurface.cpp + protocols/wremotesubsurfacemanagerv1.cpp ) set(HEADERS @@ -257,6 +261,7 @@ set(HEADERS kernel/wevent.h kernel/wserver.h kernel/wsurface.h + kernel/wsubsurface.h kernel/wtypes.h kernel/woutputlayout.h kernel/wxcursorimage.h @@ -273,6 +278,7 @@ set(HEADERS kernel/WEvent kernel/WInputEvent kernel/WSurface + kernel/WSubsurface qtquick/wsurfaceitem.h qtquick/WSurfaceItem @@ -332,6 +338,7 @@ set(HEADERS protocols/WInputPopupSurface protocols/wcursorshapemanagerv1.h protocols/WCursorShapeManagerV1 + protocols/wremotesubsurfacemanagerv1.h protocols/woutputmanagerv1.h protocols/WOutputManagerV1 protocols/wlayershell.h @@ -361,6 +368,7 @@ set(PRIVATE_HEADERS kernel/private/wglobal_p.h kernel/private/wwaylandresource_p.h kernel/private/wsurface_p.h + kernel/private/wsubsurface_p.h kernel/private/wprivateaccessor_p.h qtquick/private/woutputviewport_p.h qtquick/private/wquickcoordmapper_p.h @@ -404,6 +412,11 @@ endif() add_library(${TARGET} SHARED ${SOURCES}) add_library(Waylib::WaylibServer ALIAS ${TARGET}) +waylib_generate_qtwayland_server_protocol(${TARGET} + PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-remote-subsurface-unstable-v1.xml + BASENAME treeland-remote-subsurface-unstable-v1 +) + qt_add_qml_module(${TARGET} URI Waylib.Server VERSION "1.0" # Qt bug: The major version must be larger than 0 diff --git a/waylib/src/server/kernel/WSubsurface b/waylib/src/server/kernel/WSubsurface new file mode 100644 index 0000000000..d1305471a6 --- /dev/null +++ b/waylib/src/server/kernel/WSubsurface @@ -0,0 +1 @@ +#include "wsubsurface.h" diff --git a/waylib/src/server/kernel/private/wsubsurface_p.h b/waylib/src/server/kernel/private/wsubsurface_p.h new file mode 100644 index 0000000000..55826c76eb --- /dev/null +++ b/waylib/src/server/kernel/private/wsubsurface_p.h @@ -0,0 +1,31 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "private/wglobal_p.h" +#include "wsubsurface.h" + +#include + +WAYLIB_SERVER_BEGIN_NAMESPACE + +class Q_DECL_HIDDEN WSubsurfacePrivate : public WObjectPrivate +{ +public: + WSubsurfacePrivate(WSubsurface *q, + WSubsurface::Type type, + WSurface *parentSurface, + WSurface *surface); + + W_DECLARE_PUBLIC(WSubsurface) + + WSubsurface::Type type; + WSubsurface::Place place = WSubsurface::Place::Above; + QPointF position; + bool mapped = false; + QPointer parentSurface; + QPointer surface; +}; + +WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/kernel/private/wsurface_p.h b/waylib/src/server/kernel/private/wsurface_p.h index 4d487f65ec..e554a40f61 100644 --- a/waylib/src/server/kernel/private/wsurface_p.h +++ b/waylib/src/server/kernel/private/wsurface_p.h @@ -39,12 +39,27 @@ class Q_DECL_HIDDEN WSurfacePrivate : public WWaylandResourcePrivate { void updatePreferredBufferScale(); void preferredBufferScaleChange(); - WSurface *ensureSubsurface(wlr_subsurface *subsurface); + WSubsurface *ensureSubsurface(wlr_subsurface *subsurface); + WSubsurface *addRemoteSubsurface(wlr_surface *childHandle); + void removeSubsurface(WSubsurface *subsurface); + void releaseSubsurface(WSubsurface *subsurface); + void updateStandardSubsurfaces(); + void setSubsurfaceOrder(const QList &remoteBelow, + const QList &remoteAbove); + void rebuildTotalSubsurfaces(); void setHasSubsurface(bool newHasSubsurface); - void updateHasSubsurface(); W_DECLARE_PUBLIC(WSurface) + friend class WRemoteSubsurfaceManagerV1Private; + + // Logical subsurface relations (standard + remote) exposed via WSurface API. + QList standardBelow; + QList standardAbove; + QList remoteBelow; + QList remoteAbove; + QList subsurfaces; + QPointer subsurface; bool hasSubsurface = false; uint32_t preferredBufferScale = 1; uint32_t explicitPreferredBufferScale = 0; @@ -52,7 +67,6 @@ class Q_DECL_HIDDEN WSurfacePrivate : public WWaylandResourcePrivate { bool needsFrame = false; WBufferUnlockPtr buffer; QList outputs; - QList subSurfaces; WOutput *framePacingOutput = nullptr; QMetaObject::Connection frameDoneConnection; QPoint bufferOffset; diff --git a/waylib/src/server/kernel/wsubsurface.cpp b/waylib/src/server/kernel/wsubsurface.cpp new file mode 100644 index 0000000000..8d0549b774 --- /dev/null +++ b/waylib/src/server/kernel/wsubsurface.cpp @@ -0,0 +1,96 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "wsubsurface.h" + +#include "private/wsubsurface_p.h" +#include "wsurface.h" + +WAYLIB_SERVER_BEGIN_NAMESPACE + +WSubsurfacePrivate::WSubsurfacePrivate(WSubsurface *q, + WSubsurface::Type type, + WSurface *parentSurface, + WSurface *surface) + : WObjectPrivate(q) + , type(type) + , mapped(surface && surface->mapped()) + , parentSurface(parentSurface) + , surface(surface) +{ +} + +WSubsurface::WSubsurface(Type type, + WSurface *parentSurface, + WSurface *surface) + : QObject(nullptr) + , WObject(*new WSubsurfacePrivate(this, type, parentSurface, surface)) +{ +} + +WSubsurface::~WSubsurface() = default; + +WSubsurface::Type WSubsurface::type() const +{ + W_DC(WSubsurface); + return d->type; +} + +WSubsurface::Place WSubsurface::place() const +{ + W_DC(WSubsurface); + return d->place; +} + +QPointF WSubsurface::position() const +{ + W_DC(WSubsurface); + return d->position; +} + +bool WSubsurface::isMapped() const +{ + W_DC(WSubsurface); + return d->mapped; +} + +WSurface *WSubsurface::parentSurface() const +{ + W_DC(WSubsurface); + return d->parentSurface; +} + +WSurface *WSubsurface::surface() const +{ + W_DC(WSubsurface); + return d->surface; +} + +void WSubsurface::setPlace(Place place) +{ + W_D(WSubsurface); + if (d->place == place) + return; + d->place = place; + Q_EMIT placeChanged(place); +} + +void WSubsurface::setPosition(const QPointF &position) +{ + W_D(WSubsurface); + if (d->position == position) + return; + d->position = position; + Q_EMIT positionChanged(position); +} + +void WSubsurface::setMapped(bool mapped) +{ + W_D(WSubsurface); + if (d->mapped == mapped) + return; + d->mapped = mapped; + Q_EMIT mappedChanged(mapped); +} + +WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/kernel/wsubsurface.h b/waylib/src/server/kernel/wsubsurface.h new file mode 100644 index 0000000000..0e15b08360 --- /dev/null +++ b/waylib/src/server/kernel/wsubsurface.h @@ -0,0 +1,71 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "wglobal.h" + +#include +#include +#include + +WAYLIB_SERVER_BEGIN_NAMESPACE + +class WSurface; +class WSurfacePrivate; +class WSubsurfacePrivate; +class RemoteSubsurfaceContext; + +class WAYLIB_SERVER_EXPORT WSubsurface : public QObject, public WObject +{ + Q_OBJECT + W_DECLARE_PRIVATE(WSubsurface) + Q_PROPERTY(Type type READ type CONSTANT FINAL) + Q_PROPERTY(Place place READ place NOTIFY placeChanged FINAL) + Q_PROPERTY(QPointF position READ position NOTIFY positionChanged FINAL) + Q_PROPERTY(bool mapped READ isMapped NOTIFY mappedChanged FINAL) + Q_PROPERTY(WSurface *parentSurface READ parentSurface CONSTANT FINAL) + Q_PROPERTY(WSurface *surface READ surface CONSTANT FINAL) + +public: + enum class Type { + Standard, + Remote, + }; + Q_ENUM(Type) + + enum class Place { + Below, + Above, + }; + Q_ENUM(Place) + + ~WSubsurface() override; + + Type type() const; + Place place() const; + QPointF position() const; + bool isMapped() const; + + WSurface *parentSurface() const; + WSurface *surface() const; + +Q_SIGNALS: + void positionChanged(const QPointF &position); + void placeChanged(Place place); + void mappedChanged(bool mapped); + +private: + WSubsurface(Type type, + WSurface *parentSurface, + WSurface *surface); + + void setPlace(Place place); + void setPosition(const QPointF &position); + void setMapped(bool mapped); + + friend class WSurfacePrivate; + friend class RemoteSubsurfaceContext; +}; + +WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/kernel/wsurface.cpp b/waylib/src/server/kernel/wsurface.cpp index bc610f1c65..f9b3108f51 100644 --- a/waylib/src/server/kernel/wsurface.cpp +++ b/waylib/src/server/kernel/wsurface.cpp @@ -6,6 +6,8 @@ #include "wseat.h" #include "private/wsurface_p.h" #include "woutput.h" +#include "wsubsurface.h" +#include "wayliblogging.h" #include @@ -45,8 +47,7 @@ void WSurfacePrivate::on_commit() if (m_handle->current.committed & WLR_SURFACE_STATE_OFFSET) updateBufferOffset(); - if (hasSubsurface) // Will make to true when wlr_surface::new_subsurface - updateHasSubsurface(); + updateStandardSubsurfaces(); Q_EMIT q->commit(m_handle->current.committed); } @@ -56,16 +57,7 @@ void WSurfacePrivate::init() W_Q(WSurface); connect(); updateBuffer(); - updateHasSubsurface(); - - wlr_surface *surface = m_handle; - wlr_subsurface *subsurface; - wl_list_for_each(subsurface, &surface->current.subsurfaces_below, current.link) { - Q_EMIT q->newSubsurface(ensureSubsurface(subsurface)); - } - wl_list_for_each(subsurface, &surface->current.subsurfaces_above, current.link) { - Q_EMIT q->newSubsurface(ensureSubsurface(subsurface)); - } + updateStandardSubsurfaces(); } void WSurfacePrivate::connect() @@ -76,11 +68,11 @@ void WSurfacePrivate::connect() q->listeners()->add(&m_handle->events.unmap, q, &WSurface::mappedChanged); q->listeners()->add(&m_handle->events.new_subsurface, q, [q, this] (wlr_subsurface *sub) { - setHasSubsurface(true); - auto surface = ensureSubsurface(sub); - Q_EMIT q->newSubsurface(surface); - for (auto output : std::as_const(outputs)) - surface->enterOutput(output); + auto *subsurface = ensureSubsurface(sub); + updateStandardSubsurfaces(); + + for (auto *output : std::as_const(outputs)) + subsurface->surface()->enterOutput(output); }); } @@ -168,24 +160,172 @@ void WSurfacePrivate::preferredBufferScaleChange() Q_EMIT q->preferredBufferScaleChanged(); } -WSurface *WSurfacePrivate::ensureSubsurface(wlr_subsurface *subsurface) +WSubsurface *WSurfacePrivate::ensureSubsurface(wlr_subsurface *subsurfaceHandle) { - if (auto surface = WSurface::fromHandle(subsurface->surface)) - return surface; + W_Q(WSurface); + + for (auto *subsurface : std::as_const(subsurfaces)) { + if (subsurface->surface()->handle() == subsurfaceHandle->surface) { + return subsurface; + } + } - auto surface = new WSurface(subsurface->surface); - // The parent surface created this wrapper, so it releases it when the - // native subsurface is destroyed (owner rule; no self-deletion in - // WSurface). Register on the child's listeners list so the callback is - // detached automatically when the WSurface is destroyed. - surface->listeners(q_ptr)->add(&subsurface->surface->events.destroy, this, - [this, surface](void *) { - subSurfaces.removeOne(surface); - delete surface; + auto *childSurface = new WSurface(subsurfaceHandle->surface); + auto *subsurface = new WSubsurface(WSubsurface::Type::Standard, q, childSurface); + WSubsurface::Place place = WSubsurface::Place::Above; + wlr_subsurface *current; + wl_list_for_each(current, &m_handle->current.subsurfaces_below, current.link) { + if (current == subsurfaceHandle) { + place = WSubsurface::Place::Below; + break; + } + } + subsurface->setPlace(place); + subsurface->setPosition(QPointF(subsurfaceHandle->current.x, subsurfaceHandle->current.y)); + subsurfaces.append(subsurface); + childSurface->d_func()->subsurface = subsurface; + + // Own this destroy listener on the child wrapper so deleting that wrapper + // during the callback also detaches the listener before wlroots asserts + // the signal list is empty at the end of native teardown. + childSurface->listeners()->add(&subsurfaceHandle->events.destroy, + [this, subsurface](void *) { + releaseSubsurface(subsurface); }); - subSurfaces.append(surface); + QObject::connect(childSurface, &WSurface::mappedChanged, subsurface, [subsurface] { + if (auto *child = subsurface->surface()) + subsurface->setMapped(child->mapped()); + }); + + setHasSubsurface(true); + Q_EMIT q->subsurfaceAdded(subsurface); + return subsurface; +} + +WSubsurface *WSurfacePrivate::addRemoteSubsurface(wlr_surface *childHandle) +{ + W_Q(WSurface); + Q_ASSERT(childHandle); + auto *childSurface = new WSurface(childHandle); + + auto *subsurface = new WSubsurface(WSubsurface::Type::Remote, q, childSurface); + remoteAbove.append(subsurface); + childSurface->d_func()->subsurface = subsurface; + // A remote subsurface has no native wlr_subsurface destroy event. Its + // wl_surface may be destroyed directly when the child client disconnects, + // so release the wrapper before wlroots asserts that surface listeners are + // gone. + childSurface->listeners()->add(&childHandle->events.destroy, + [this, subsurface](void *) { + releaseSubsurface(subsurface); + }); + rebuildTotalSubsurfaces(); + Q_EMIT q->subsurfaceAdded(subsurface); + return subsurface; +} + +void WSurfacePrivate::removeSubsurface(WSubsurface *subsurface) +{ + W_Q(WSurface); + if (!subsurface) + return; + + auto &targetList = (subsurface->type() == WSubsurface::Type::Remote) + ? (subsurface->place() == WSubsurface::Place::Below ? remoteBelow : remoteAbove) + : (subsurface->place() == WSubsurface::Place::Below ? standardBelow : standardAbove); + + if (!targetList.removeOne(subsurface)) { + qCCritical(lcWlSurface) << "Failed to remove subsurface from target list:" << subsurface; + return; + } + + if (auto *child = subsurface->surface()) { + child->d_func()->subsurface = nullptr; + } + rebuildTotalSubsurfaces(); + Q_EMIT q->subsurfaceRemoved(subsurface); +} + +void WSurfacePrivate::releaseSubsurface(WSubsurface *subsurface) +{ + if (!subsurface) + return; + + auto *childSurface = subsurface->surface(); + + removeSubsurface(subsurface); + delete subsurface; + + delete childSurface; +} + +void WSurfacePrivate::updateStandardSubsurfaces() +{ + if (subsurfaces.isEmpty() + && wl_list_empty(&m_handle->current.subsurfaces_below) + && wl_list_empty(&m_handle->current.subsurfaces_above)) { + return; + } - return surface; + setSubsurfaceOrder(this->remoteBelow, this->remoteAbove); +} + +void WSurfacePrivate::setSubsurfaceOrder(const QList &newRemoteBelow, + const QList &newRemoteAbove) +{ + W_Q(WSurface); + + // 1. Process Remote subsurfaces + auto filterRemote = [q](const QList &list, WSubsurface::Place place) { + QList result; + for (auto *sub : list) { + if (!sub || sub->parentSurface() != q || sub->type() != WSubsurface::Type::Remote) + continue; + sub->setPlace(place); + result.append(sub); + } + return result; + }; + + this->remoteBelow = filterRemote(newRemoteBelow, WSubsurface::Place::Below); + this->remoteAbove = filterRemote(newRemoteAbove, WSubsurface::Place::Above); + + // 2. Snapshot and process native Standard subsurfaces + QList> positionUpdates; + auto processStandard = [this, &positionUpdates](wl_list *head, WSubsurface::Place place, QList &outList) { + outList.clear(); + QList nativeList; + wlr_subsurface *nativeSubsurface; + wl_list_for_each(nativeSubsurface, head, current.link) { + nativeList.append(nativeSubsurface); + } + for (auto *native : std::as_const(nativeList)) { + auto *subsurface = ensureSubsurface(native); + subsurface->setPlace(place); + positionUpdates.append({subsurface, QPointF(native->current.x, native->current.y)}); + outList.append(subsurface); + } + }; + + processStandard(&m_handle->current.subsurfaces_below, WSubsurface::Place::Below, this->standardBelow); + processStandard(&m_handle->current.subsurfaces_above, WSubsurface::Place::Above, this->standardAbove); + + rebuildTotalSubsurfaces(); + + for (const auto &[subsurface, position] : std::as_const(positionUpdates)) + subsurface->setPosition(position); +} + +void WSurfacePrivate::rebuildTotalSubsurfaces() +{ + const QList ordered = standardBelow + remoteBelow + standardAbove + remoteAbove; + const bool orderChanged = (subsurfaces != ordered); + if (orderChanged) + subsurfaces = ordered; + + setHasSubsurface(!subsurfaces.isEmpty()); + if (orderChanged) + Q_EMIT q_func()->subsurfaceOrderChanged(); } void WSurfacePrivate::setHasSubsurface(bool newHasSubsurface) @@ -197,12 +337,6 @@ void WSurfacePrivate::setHasSubsurface(bool newHasSubsurface) Q_EMIT q_func()->hasSubsurfaceChanged(); } -void WSurfacePrivate::updateHasSubsurface() -{ - setHasSubsurface(handle() && (!wl_list_empty(&m_handle->current.subsurfaces_above) - || !wl_list_empty(&m_handle->current.subsurfaces_below))); -} - WSurface::WSurface(wlr_surface *handle) : WSurface(*new WSurfacePrivate(this, handle)) { @@ -236,12 +370,11 @@ WSurface::~WSurface() for (auto *o : outs) leaveOutput(o); - // Subsurfaces are created by this surface, so release them here (owner - // rule; no self-deletion in WSurface). - QList subs; - subs.swap(d->subSurfaces); - for (auto *sub : std::as_const(subs)) - delete sub; + // Release all remaining subsurfaces. Their dedicated child wrappers are + // reclaimed in releaseSubsurface(). + const auto subs = d->subsurfaces; + for (auto *sub : subs) + d->releaseSubsurface(sub); } wlr_surface *WSurface::handle() const @@ -336,11 +469,11 @@ void WSurface::enterOutput(WOutput *output) auto surface = d->handle(); wlr_subsurface *subsurface; wl_list_for_each(subsurface, &surface->current.subsurfaces_below, current.link) { - d->ensureSubsurface(subsurface)->enterOutput(output); + d->ensureSubsurface(subsurface)->surface()->enterOutput(output); } wl_list_for_each(subsurface, &surface->current.subsurfaces_above, current.link) { - d->ensureSubsurface(subsurface)->enterOutput(output); + d->ensureSubsurface(subsurface)->surface()->enterOutput(output); } Q_EMIT outputEntered(output); @@ -360,11 +493,11 @@ void WSurface::leaveOutput(WOutput *output) auto surface = d->handle(); wlr_subsurface *subsurface; wl_list_for_each(subsurface, &surface->current.subsurfaces_below, current.link) { - d->ensureSubsurface(subsurface)->leaveOutput(output); + d->ensureSubsurface(subsurface)->surface()->leaveOutput(output); } wl_list_for_each(subsurface, &surface->current.subsurfaces_above, current.link) { - d->ensureSubsurface(subsurface)->leaveOutput(output); + d->ensureSubsurface(subsurface)->surface()->leaveOutput(output); } Q_EMIT outputLeave(output); @@ -384,7 +517,8 @@ WOutput *WSurface::framePacingOutput() const bool WSurface::isSubsurface() const { - return wlr_subsurface_try_from_wlr_surface(handle()) != nullptr; + W_DC(WSurface); + return d->subsurface != nullptr; } bool WSurface::hasSubsurface() const @@ -393,22 +527,32 @@ bool WSurface::hasSubsurface() const return d->hasSubsurface; } -QList WSurface::subsurfaces() const +const QList &WSurface::subsurfaces() const { - auto d = const_cast(this)->d_func(); - QList subsurfaeList; + W_DC(WSurface); + return d->subsurfaces; +} - auto surface = d->handle(); - wlr_subsurface *subsurface; - wl_list_for_each(subsurface, &surface->current.subsurfaces_below, current.link) { - subsurfaeList.append(d->ensureSubsurface(subsurface)); +QList WSurface::subsurfacesBelow() const +{ + QList result; + const auto &subsurfaces = this->subsurfaces(); + for (auto *subsurface : subsurfaces) { + if (subsurface->place() == WSubsurface::Place::Below) + result.append(subsurface); } + return result; +} - wl_list_for_each(subsurface, &surface->current.subsurfaces_above, current.link) { - subsurfaeList.append(d->ensureSubsurface(subsurface)); +QList WSurface::subsurfacesAbove() const +{ + QList result; + const auto &subsurfaces = this->subsurfaces(); + for (auto *subsurface : subsurfaces) { + if (subsurface->place() == WSubsurface::Place::Above) + result.append(subsurface); } - - return subsurfaeList; + return result; } uint32_t WSurface::preferredBufferScale() const @@ -449,6 +593,26 @@ void WSurface::unmap() wlr_surface_unmap(d->handle()); } +WSubsurface *WSurface::addRemoteSubsurface(wlr_surface *childHandle) +{ + W_D(WSurface); + return d->addRemoteSubsurface(childHandle); +} + +void WSurface::removeSubsurface(WSubsurface *subsurface) +{ + W_D(WSurface); + d->releaseSubsurface(subsurface); +} + +void WSurface::setRemoteSubsurfaceOrder(const QList &below, + const QList &above) +{ + W_D(WSurface); + d->setSubsurfaceOrder(below, above); +} + + bool WSurface::needsFrame() const { W_DC(WSurface); diff --git a/waylib/src/server/kernel/wsurface.h b/waylib/src/server/kernel/wsurface.h index bb86e886c7..933ba853a9 100644 --- a/waylib/src/server/kernel/wsurface.h +++ b/waylib/src/server/kernel/wsurface.h @@ -12,11 +12,13 @@ #include Q_MOC_INCLUDE("woutput.h") +Q_MOC_INCLUDE("wsubsurface.h") WAYLIB_SERVER_BEGIN_NAMESPACE class WServer; class WOutput; +class WSubsurface; class WSurfacePrivate; class WAYLIB_SERVER_EXPORT WSurface : public QObject, public WWaylandResource { @@ -26,7 +28,7 @@ class WAYLIB_SERVER_EXPORT WSurface : public QObject, public WWaylandResource Q_PROPERTY(bool isSubsurface READ isSubsurface) Q_PROPERTY(bool hasSubsurface READ hasSubsurface NOTIFY hasSubsurfaceChanged) Q_PROPERTY(bool needsFrame READ needsFrame) - Q_PROPERTY(QList subsurfaces READ subsurfaces NOTIFY newSubsurface) + Q_PROPERTY(QList subsurfaces READ subsurfaces NOTIFY subsurfaceOrderChanged) Q_PROPERTY(uint32_t preferredBufferScale READ preferredBufferScale WRITE setPreferredBufferScale RESET resetPreferredBufferScale NOTIFY preferredBufferScaleChanged FINAL) QML_NAMED_ELEMENT(WaylandSurface) QML_UNCREATABLE("Only create in C++") @@ -52,7 +54,9 @@ class WAYLIB_SERVER_EXPORT WSurface : public QObject, public WWaylandResource bool isSubsurface() const; bool hasSubsurface() const; - QList subsurfaces() const; + const QList &subsurfaces() const; + QList subsurfacesBelow() const; + QList subsurfacesAbove() const; uint32_t preferredBufferScale() const; void setPreferredBufferScale(uint32_t newPreferredBufferScale); @@ -75,7 +79,9 @@ public Q_SLOTS: void mappedChanged(); void bufferOffsetChanged(); void hasSubsurfaceChanged(); - void newSubsurface(WSurface *subsurface); + void subsurfaceAdded(WSubsurface *subsurface); + void subsurfaceRemoved(WSubsurface *subsurface); + void subsurfaceOrderChanged(); // Emitted from the destructor while the object is still usable. void beforeDestroy(); void preferredBufferScaleChanged(); @@ -89,6 +95,14 @@ public Q_SLOTS: // Owned by the creator (seat/shell/...): released with `delete` from // the native destroy callback, never with deleteLater(). using QObject::deleteLater; + +private: + WSubsurface *addRemoteSubsurface(wlr_surface *childHandle); + void removeSubsurface(WSubsurface *subsurface); + void setRemoteSubsurfaceOrder(const QList &below, + const QList &above); + + friend class WRemoteSubsurfaceManagerV1Private; }; WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/protocols/wremotesubsurfacemanagerv1.cpp b/waylib/src/server/protocols/wremotesubsurfacemanagerv1.cpp new file mode 100644 index 0000000000..17c21c5854 --- /dev/null +++ b/waylib/src/server/protocols/wremotesubsurfacemanagerv1.cpp @@ -0,0 +1,765 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "wremotesubsurfacemanagerv1.h" + +#include "private/wglobal_p.h" +#include "qwayland-server-treeland-remote-subsurface-unstable-v1.h" +#include "wayliblogging.h" +#include "wserver.h" +#include "wsubsurface.h" +#include "wsurface.h" + +extern "C" { +#include +} + +#include +#include +#include + +WAYLIB_SERVER_BEGIN_NAMESPACE + +// --------------------------------------------------------------------------- +// Forward declarations +// --------------------------------------------------------------------------- + +class WRemoteSubsurfaceManagerV1Private; +class ExportedSurfaceContext; +class RemoteSubsurfaceContext; + +// --------------------------------------------------------------------------- +// wlroots role for remote subsurfaces (defined after RemoteSubsurfaceContext) +// --------------------------------------------------------------------------- + +static void remote_subsurface_role_commit(struct wlr_surface *surface); +static void remote_subsurface_role_destroy(struct wlr_surface *surface); +static const wlr_surface_role remoteSubsurfaceRole = { + .name = "treeland_remote_subsurface_v1", + .no_object = false, + .client_commit = nullptr, + .commit = remote_subsurface_role_commit, + .map = nullptr, + .unmap = nullptr, + .destroy = remote_subsurface_role_destroy, +}; + +// --------------------------------------------------------------------------- +// ExportedSurfaceContext — one per exported wl_surface +// --------------------------------------------------------------------------- + +class ExportedSurfaceContext + : public QObject + , public QtWaylandServer::treeland_exported_surface_v1 +{ + Q_OBJECT +public: + ExportedSurfaceContext(WRemoteSubsurfaceManagerV1Private *manager, + wlr_surface *surface, + const QString &token, + wl_client *client, + uint32_t version, + uint32_t id) + : QObject() + , QtWaylandServer::treeland_exported_surface_v1(client, id, version) + , m_manager(manager) + , m_surface(surface) + , m_token(token) + { + } + + ~ExportedSurfaceContext() override = default; + + wlr_surface *surface() const + { + return m_surface; + } + + + QString token() const + { + return m_token; + } + + // Back-pointer to the RemoteSubsurfaceContext where this context acts as child. + // Set by addChildToParent / removeChildFromParent in the manager. + RemoteSubsurfaceContext *remoteSubsurface = nullptr; + + // Remote children ordered relative to this exported parent surface. + QList belowChildren; + QList aboveChildren; + +protected: + void destroy(Resource *resource) override + { + wl_resource_destroy(resource->handle); + } + + void destroy_resource(Resource *) override; + void create_remote_subsurface(Resource *resource, + uint32_t id, + const QString &parent_token) override; + +private: + WRemoteSubsurfaceManagerV1Private *m_manager; + wlr_surface *m_surface = nullptr; + QString m_token; +}; + +// --------------------------------------------------------------------------- +// RemoteSubsurfaceContext — one per parent-child relationship +// --------------------------------------------------------------------------- + +class RemoteSubsurfaceContext + : public QObject + , public QtWaylandServer::treeland_remote_subsurface_v1 +{ + Q_OBJECT +public: + RemoteSubsurfaceContext(WRemoteSubsurfaceManagerV1Private *manager, + ExportedSurfaceContext *childExported, + ExportedSurfaceContext *parentExported, + wl_client *client, + uint32_t version, + uint32_t id) + : QObject() + , QtWaylandServer::treeland_remote_subsurface_v1(client, id, version) + , m_manager(manager) + , m_child(childExported) + , m_parent(parentExported) + { + } + + ~RemoteSubsurfaceContext() override; + + ExportedSurfaceContext *childExported() const + { + return m_child; + } + + ExportedSurfaceContext *parentExported() const + { + return m_parent; + } + + WSubsurface *subsurface() const { return m_subsurface; } + void setSubsurface(WSubsurface *s) { m_subsurface = s; } + + void invalidate() { m_manager = nullptr; } + + void recheckMapping(); + void cascadeUnmap(); + +protected: + void destroy(Resource *resource) override + { + wl_resource_destroy(resource->handle); + } + + void destroy_resource(Resource *) override; + void set_position(Resource *resource, int32_t x, int32_t y) override; + void place_above(Resource *resource, const QString &sibling_token) override; + void place_below(Resource *resource, const QString &sibling_token) override; + +private: + WRemoteSubsurfaceManagerV1Private *m_manager; + ExportedSurfaceContext *m_child = nullptr; + ExportedSurfaceContext *m_parent = nullptr; + QPointer m_subsurface; +}; + +// --------------------------------------------------------------------------- +// WRemoteSubsurfaceManagerV1Private +// --------------------------------------------------------------------------- + +static inline WSurface *wsurfaceFrom(const ExportedSurfaceContext *ctx) +{ + auto *wlr = ctx ? ctx->surface() : nullptr; + return wlr ? WSurface::fromHandle(wlr) : nullptr; +} + +static inline QStringView shortToken(const QString &token) +{ + return QStringView(token).left(8); +} + +class WRemoteSubsurfaceManagerV1Private + : public WObjectPrivate + , public QtWaylandServer::treeland_subsurface_manager_v1 +{ + Q_DECLARE_PUBLIC(WRemoteSubsurfaceManagerV1) + +public: + explicit WRemoteSubsurfaceManagerV1Private(WRemoteSubsurfaceManagerV1 *q) + : WObjectPrivate(q) + , QtWaylandServer::treeland_subsurface_manager_v1() + { + } + + wl_global *globalHandle() const + { + return m_global; + } + + // Lookup + ExportedSurfaceContext *findExportedByToken(const QString &token) const + { + return m_tokens.value(token, nullptr); + } + + bool isResourceExported(struct ::wl_resource *surfaceResource) const + { + auto *ws = WSurface::fromHandle(wlr_surface_from_resource(surfaceResource)); + return ws && ws->isSubsurface(); + } + + // Registry + void registerExported(ExportedSurfaceContext *ctx) + { + m_tokens.insert(ctx->token(), ctx); + } + + void unregisterExported(ExportedSurfaceContext *ctx) + { + m_tokens.remove(ctx->token()); + } + + // Cycle detection + bool isDescendantOf(ExportedSurfaceContext *candidate, ExportedSurfaceContext *root) const + { + if (!candidate || !root) + return false; + auto *curr = candidate; + while (curr) { + if (curr == root) + return true; + curr = curr->remoteSubsurface ? curr->remoteSubsurface->parentExported() : nullptr; + } + return false; + } + + // Children list management (z-order, below vs above parent) + QList childrenOf(ExportedSurfaceContext *parent) const + { + if (!parent) + return {}; + QList res = parent->belowChildren; + res.append(parent->aboveChildren); + return res; + } + + void addChildToParent(RemoteSubsurfaceContext *remote) + { + auto *parent = remote->parentExported(); + parent->aboveChildren.append(remote); + remote->childExported()->remoteSubsurface = remote; + + auto *parentSurface = wsurfaceFrom(parent); + auto *childWlr = remote->childExported()->surface(); + if (!parentSurface || !childWlr) + return; + + remote->setSubsurface(parentSurface->addRemoteSubsurface(childWlr)); + } + + void removeChildFromOrder(RemoteSubsurfaceContext *remote) + { + auto *parent = remote->parentExported(); + if (!parent) + return; + parent->belowChildren.removeOne(remote); + parent->aboveChildren.removeOne(remote); + } + + void removeChildFromParent(RemoteSubsurfaceContext *remote) + { + removeChildFromOrder(remote); + + auto *child = remote->childExported(); + if (child && child->remoteSubsurface == remote) + child->remoteSubsurface = nullptr; + + if (auto *subsurface = remote->subsurface()) { + if (auto *parentSurface = subsurface->parentSurface()) + parentSurface->removeSubsurface(subsurface); + remote->setSubsurface(nullptr); + } + } + + void syncRemoteSubsurfaceOrder(ExportedSurfaceContext *parent) + { + auto *parentSurface = wsurfaceFrom(parent); + if (!parentSurface) + return; + + QList below; + QList above; + for (auto *remote : parent->belowChildren) { + if (auto *subsurface = remote->subsurface()) + below.append(subsurface); + } + for (auto *remote : parent->aboveChildren) { + if (auto *subsurface = remote->subsurface()) + above.append(subsurface); + } + parentSurface->setRemoteSubsurfaceOrder(below, above); + } + + void placeChildAboveParentTop(RemoteSubsurfaceContext *remote) + { + auto *parent = remote->parentExported(); + removeChildFromOrder(remote); + parent->aboveChildren.append(remote); + syncRemoteSubsurfaceOrder(parent); + } + + void placeChildAboveParentBottom(RemoteSubsurfaceContext *remote) + { + auto *parent = remote->parentExported(); + removeChildFromOrder(remote); + parent->aboveChildren.prepend(remote); + syncRemoteSubsurfaceOrder(parent); + } + + void placeChildBelowParentTop(RemoteSubsurfaceContext *remote) + { + auto *parent = remote->parentExported(); + removeChildFromOrder(remote); + parent->belowChildren.append(remote); + syncRemoteSubsurfaceOrder(parent); + } + + void placeChildAboveSibling(RemoteSubsurfaceContext *remote, + RemoteSubsurfaceContext *siblingRemote) + { + auto *parent = remote->parentExported(); + removeChildFromOrder(remote); + + auto &belowList = parent->belowChildren; + int idxBelow = belowList.indexOf(siblingRemote); + if (idxBelow >= 0) { + belowList.insert(idxBelow + 1, remote); + } else { + auto &aboveList = parent->aboveChildren; + int idxAbove = aboveList.indexOf(siblingRemote); + aboveList.insert(idxAbove >= 0 ? idxAbove + 1 : aboveList.size(), remote); + } + + syncRemoteSubsurfaceOrder(parent); + } + + void placeChildBelowSibling(RemoteSubsurfaceContext *remote, + RemoteSubsurfaceContext *siblingRemote) + { + auto *parent = remote->parentExported(); + removeChildFromOrder(remote); + + auto &belowList = parent->belowChildren; + int idxBelow = belowList.indexOf(siblingRemote); + if (idxBelow >= 0) { + belowList.insert(idxBelow, remote); + } else { + auto &aboveList = parent->aboveChildren; + int idxAbove = aboveList.indexOf(siblingRemote); + aboveList.insert(idxAbove > 0 ? idxAbove : 0, remote); + } + + syncRemoteSubsurfaceOrder(parent); + } + + // Sibling lookup: find RemoteSubsurfaceContext for a sibling token under same parent + RemoteSubsurfaceContext *findSibling(RemoteSubsurfaceContext *remote, + const QString &siblingToken) const + { + auto *siblingCtx = findExportedByToken(siblingToken); + if (!siblingCtx) + return nullptr; + for (auto *child : childrenOf(remote->parentExported())) { + if (child->childExported() == siblingCtx) + return child; + } + return nullptr; + } + + static void safeDestroyRemote(RemoteSubsurfaceContext *remote) + { + if (!remote) + return; + if (auto *res = remote->resource()) { + if (res->handle) { + wl_resource_destroy(res->handle); + return; + } + } + delete remote; + } + + static void safeDestroyExported(ExportedSurfaceContext *ctx) + { + if (!ctx) + return; + if (auto *res = ctx->resource()) { + if (res->handle) { + wl_resource_destroy(res->handle); + return; + } + } + delete ctx; + } + + // Cleanup + void cleanupExportedContext(ExportedSurfaceContext *ctx) + { + QList allRemotes = childrenOf(ctx); + if (ctx->remoteSubsurface) + allRemotes.append(ctx->remoteSubsurface); + + // Do not emit unmapped signals here: client teardown can already have + // destroyed one of the WSurface wrappers. + for (auto *remote : std::as_const(allRemotes)) { + removeChildFromParent(remote); + // Prevent RemoteSubsurfaceContext::destroy_resource() from trying + // to detach the same parent-child relationship a second time when + // safeDestroyRemote() destroys the role resource below. + remote->invalidate(); + } + + for (auto *remote : std::as_const(allRemotes)) + safeDestroyRemote(remote); + + unregisterExported(ctx); + } + + // Track parent state changes so the child's mapping is re-evaluated. + // The child's own commits are handled by the wlroots role commit callback. + void trackCommits(RemoteSubsurfaceContext *remote) + { + auto *parentSurface = wsurfaceFrom(remote->parentExported()); + if (parentSurface) { + QObject::connect(parentSurface, &WSurface::commit, remote, [remote] { + remote->recheckMapping(); + }); + QObject::connect(parentSurface, &WSurface::mappedChanged, remote, [remote] { + remote->recheckMapping(); + }); + } + } + +protected: + void destroy(Resource *resource) override + { + wl_resource_destroy(resource->handle); + } + + void export_surface(Resource *resource, + uint32_t id, + struct ::wl_resource *surfaceResource) override + { + if (isResourceExported(surfaceResource)) { + wl_resource_post_error(resource->handle, + error_bad_surface, + "The wl_surface has already been exported through this manager"); + return; + } + + auto *wlrSurface = wlr_surface_from_resource(surfaceResource); + if (!wlrSurface) { + wl_resource_post_error(resource->handle, + error_bad_surface, + "Invalid wl_surface resource"); + return; + } + + const QString token = QUuid::createUuid().toString(QUuid::WithoutBraces); + qCDebug(lcWlRemoteSubsurface) << "Exported surface" << shortToken(token); + + auto *context = new ExportedSurfaceContext(this, + wlrSurface, + token, + resource->client(), + resource->version(), + id); + + registerExported(context); + context->send_surface_token(token); + + auto *ws = WSurface::fromHandle(wlrSurface); + if (ws) { + QObject::connect(ws, + &WSurface::beforeDestroy, + context, + [this, context]() { + if (m_tokens.contains(context->token())) + safeDestroyExported(context); + }); + } + } + +private: + QHash m_tokens; +}; + +// --------------------------------------------------------------------------- +// ExportedSurfaceContext implementation +// --------------------------------------------------------------------------- + +void ExportedSurfaceContext::destroy_resource(Resource *) +{ + if (m_manager) + m_manager->cleanupExportedContext(this); + delete this; +} + +void ExportedSurfaceContext::create_remote_subsurface(Resource *resource, + uint32_t id, + const QString &parent_token) +{ + if (parent_token.isEmpty()) { + send_parent_rejected(reject_reason_invalid_token, parent_token); + return; + } + + // Check if this surface already has a remote subsurface relationship + if (remoteSubsurface) { + send_parent_rejected(reject_reason_invalid_token, parent_token); + return; + } + + auto *parentContext = m_manager->findExportedByToken(parent_token); + if (!parentContext) { + send_parent_rejected(reject_reason_invalid_token, parent_token); + return; + } + + if (parentContext == this) { + send_parent_rejected(reject_reason_cyclic, parent_token); + return; + } + + if (m_manager->isDescendantOf(parentContext, this)) { + send_parent_rejected(reject_reason_cyclic, parent_token); + return; + } + + auto *childWlrSurface = m_surface; + if (!childWlrSurface) { + // No valid wlr_surface — leave the new remote object inert. + qCWarning(lcWlRemoteSubsurface) + << "create_remote_subsurface: child" << shortToken(m_token) << "has no wlr_surface"; + return; + } + + if (!wlr_surface_set_role(childWlrSurface, + &remoteSubsurfaceRole, + resource->handle, + 0 /* bad_surface */)) { + // Role conflict — wlr_surface_set_role already posted the error. + // The new treeland_remote_subsurface_v1 object is inert. + return; + } + + auto *remote = new RemoteSubsurfaceContext(m_manager, + this, + parentContext, + resource->client(), + resource->version(), + id); + + wlr_surface_set_role_object(childWlrSurface, remote->resource()->handle); + + m_manager->addChildToParent(remote); + m_manager->trackCommits(remote); + qCDebug(lcWlRemoteSubsurface) << "Remote subsurface created: child" << shortToken(m_token) + << "→ parent" << shortToken(parent_token); + remote->recheckMapping(); +} + +// --------------------------------------------------------------------------- +// RemoteSubsurfaceContext implementation +// --------------------------------------------------------------------------- + +RemoteSubsurfaceContext::~RemoteSubsurfaceContext() = default; + +// --------------------------------------------------------------------------- +// wlroots role definition (after RemoteSubsurfaceContext is complete) +// --------------------------------------------------------------------------- + +static void remote_subsurface_role_commit(struct wlr_surface *surface) +{ + auto *roleRes = QtWaylandServer::treeland_remote_subsurface_v1::Resource::fromResource( + surface->role_resource); + if (!roleRes || !roleRes->object()) + return; + auto *ctx = static_cast(roleRes->object()); + ctx->recheckMapping(); +} + +static void remote_subsurface_role_destroy(struct wlr_surface *surface) +{ + // Don't delete the RemoteSubsurfaceContext here. + // wlroots already called wlr_surface_unmap(); QtWayland will call + // destroy_resource() which does the full Qt/QHash cleanup. + Q_UNUSED(surface); +} + +void RemoteSubsurfaceContext::recheckMapping() +{ + if (!m_manager) + return; + + auto *childQw = m_child ? m_child->surface() : nullptr; + + bool parentMapped = false; + auto *parentWs = wsurfaceFrom(m_parent); + parentMapped = parentWs && parentWs->mapped(); + + bool childHasBuffer = childQw && wlr_surface_has_buffer(childQw); + bool shouldBeMapped = parentMapped && childHasBuffer; + + bool currentlyMapped = m_subsurface && m_subsurface->isMapped(); + + if (shouldBeMapped && !currentlyMapped) { + if (childQw) + wlr_surface_map(childQw); + if (m_subsurface) + m_subsurface->setMapped(true); + qCDebug(lcWlRemoteSubsurface) << "Mapped: child" << shortToken(m_child->token()) << "parent" + << shortToken(m_parent->token()); + // Recursively recheck children — they may now be eligible for mapping + for (auto *grandchild : m_manager->childrenOf(m_child)) { + grandchild->recheckMapping(); + } + } else if (!shouldBeMapped && currentlyMapped) { + if (childQw) + wlr_surface_unmap(childQw); + cascadeUnmap(); + } +} + +void RemoteSubsurfaceContext::cascadeUnmap() +{ + if (!m_subsurface || !m_subsurface->isMapped() || !m_manager) + return; + m_subsurface->setMapped(false); + qCDebug(lcWlRemoteSubsurface) << "Unmapped: child" << shortToken(m_child->token()) << "parent" + << shortToken(m_parent->token()); + + // Recursively unmap all remote children of this child + for (auto *grandchild : m_manager->childrenOf(m_child)) { + grandchild->cascadeUnmap(); + } +} + +void RemoteSubsurfaceContext::set_position(Resource *resource, int32_t x, int32_t y) +{ + Q_UNUSED(resource); + QPointF pos(x, y); + if (m_subsurface) + m_subsurface->setPosition(pos); +} + +void RemoteSubsurfaceContext::place_above([[maybe_unused]] Resource *resource, + const QString &sibling_token) +{ + if (sibling_token.isEmpty()) { + m_manager->placeChildAboveParentTop(this); + return; + } + + auto *siblingCtx = m_manager->findExportedByToken(sibling_token); + if (!siblingCtx) { + send_invalid_sibling(sibling_token); + return; + } + + if (siblingCtx == m_parent) { + m_manager->placeChildAboveParentBottom(this); + return; + } + + auto *siblingRemote = m_manager->findSibling(this, sibling_token); + if (!siblingRemote) { + send_invalid_sibling(sibling_token); + return; + } + + m_manager->placeChildAboveSibling(this, siblingRemote); +} + +void RemoteSubsurfaceContext::place_below([[maybe_unused]] Resource *resource, + const QString &sibling_token) +{ + if (sibling_token.isEmpty()) { + send_invalid_sibling(sibling_token); + return; + } + + auto *siblingCtx = m_manager->findExportedByToken(sibling_token); + if (!siblingCtx) { + send_invalid_sibling(sibling_token); + return; + } + + if (siblingCtx == m_parent) { + m_manager->placeChildBelowParentTop(this); + return; + } + + auto *siblingRemote = m_manager->findSibling(this, sibling_token); + if (!siblingRemote) { + send_invalid_sibling(sibling_token); + return; + } + + m_manager->placeChildBelowSibling(this, siblingRemote); +} + +void RemoteSubsurfaceContext::destroy_resource(Resource *) +{ + if (m_manager) { + cascadeUnmap(); + // This is the direct role-object teardown path. Exported-surface + // cleanup may also reach this object, but it invalidates m_manager + // before destroying the resource so this detach only runs once. + m_manager->removeChildFromParent(this); + } + delete this; +} + +// --------------------------------------------------------------------------- +// WRemoteSubsurfaceManagerV1 — public API +// --------------------------------------------------------------------------- + +WRemoteSubsurfaceManagerV1::WRemoteSubsurfaceManagerV1() + : WObject(*new WRemoteSubsurfaceManagerV1Private(this)) +{ +} + +WRemoteSubsurfaceManagerV1::~WRemoteSubsurfaceManagerV1() = default; + +void WRemoteSubsurfaceManagerV1::create(WServer *server) +{ + W_D(WRemoteSubsurfaceManagerV1); + d->init(server->handle(), InterfaceVersion); +} + +void WRemoteSubsurfaceManagerV1::destroy([[maybe_unused]] WServer *server) +{ + W_D(WRemoteSubsurfaceManagerV1); + d->globalRemove(); +} + +wl_global *WRemoteSubsurfaceManagerV1::global() const +{ + W_DC(WRemoteSubsurfaceManagerV1); + return d->globalHandle(); +} + +QByteArrayView WRemoteSubsurfaceManagerV1::interfaceName() const +{ + return QtWaylandServer::treeland_subsurface_manager_v1::interfaceName(); +} + +WAYLIB_SERVER_END_NAMESPACE + +#include "wremotesubsurfacemanagerv1.moc" diff --git a/waylib/src/server/protocols/wremotesubsurfacemanagerv1.h b/waylib/src/server/protocols/wremotesubsurfacemanagerv1.h new file mode 100644 index 0000000000..d385f178fb --- /dev/null +++ b/waylib/src/server/protocols/wremotesubsurfacemanagerv1.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include + +#include + +WAYLIB_SERVER_BEGIN_NAMESPACE + +class WRemoteSubsurfaceManagerV1Private; + +class WAYLIB_SERVER_EXPORT WRemoteSubsurfaceManagerV1 + : public QObject + , public WObject + , public WServerInterface +{ + Q_OBJECT + W_DECLARE_PRIVATE(WRemoteSubsurfaceManagerV1) + +public: + explicit WRemoteSubsurfaceManagerV1(); + ~WRemoteSubsurfaceManagerV1() override; + + static constexpr int InterfaceVersion = 1; + QByteArrayView interfaceName() const override; + +protected: + void create(WServer *server) override; + void destroy(WServer *server) override; + wl_global *global() const override; +}; + +WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/qtquick/private/wsurfaceitem_p.h b/waylib/src/server/qtquick/private/wsurfaceitem_p.h index 0e0f802397..0fd02f7743 100644 --- a/waylib/src/server/qtquick/private/wsurfaceitem_p.h +++ b/waylib/src/server/qtquick/private/wsurfaceitem_p.h @@ -4,17 +4,13 @@ #pragma once #include "wsurfaceitem.h" +#include "wsubsurface.h" #include "wsurface.h" -#include "wglobal.h" -#include "wscoplistener.h" #include #include -#include -#include #include #include -#include WAYLIB_SERVER_BEGIN_NAMESPACE @@ -37,23 +33,18 @@ class Q_DECL_HIDDEN WSurfaceItemPrivate : public QQuickItemPrivate void initForSurface(); void initForDelegate(); - void onHasSubsurfaceChanged(); void updateSubsurfaceItem(); void onPaddingsChanged(); void updateContentPosition(); - WSurfaceItem *ensureSubsurfaceItem(wlr_subsurface *subsurface, QQuickItem *parent); - - struct SubsurfaceDestroyListener { - wlr_subsurface *subsurface = nullptr; - WScopedListener listener; - }; - - std::unique_ptr surfaceListenerOwner; - // Subsurface destroy listeners, keyed by the native subsurface; there is - // no WObject wrapper for wlr_subsurface to attach them to. - std::vector subsurfaceDestroyListeners; + WSurfaceItem *ensureSubsurfaceItem(WSubsurface *subsurface); + void removeSubsurfaceItem(WSubsurface *subsurface); void updateSubsurfaceContainers(); - void connectSubsurfaceContainerSignals(SubsurfaceContainer *container); + void updateSubsurfaceContainer(WSubsurface::Place place, + const QList &subsurfaces, + QPointer &container); + void cleanupSubsurfaceContainers(); + void reorderSubsurfaceItems(); + void updateSubsurfacePositions(); void resizeSurfaceToItemSize(const QSize &itemSize, const QSize &sizeDiff); void updateEventItem(bool forceDestroy); @@ -86,12 +77,11 @@ class Q_DECL_HIDDEN WSurfaceItemPrivate : public QQuickItemPrivate QQmlComponent *delegate = nullptr; bool delegateIsDirty = false; QQuickItem *eventItem = nullptr; - QPointer belowSubsurfaceContainer = nullptr; - QPointer aboveSubsurfaceContainer = nullptr; + QPointer belowSubsurfaceContainer; + QPointer aboveSubsurfaceContainer; WSurfaceItem::ResizeMode resizeMode = WSurfaceItem::SizeFromSurface; WSurfaceItem::Flags surfaceFlags; QMarginsF paddings; - QList subsurfaces; qreal surfaceSizeRatio = 1.0; bool live = true; bool subsurfacesVisible = true; diff --git a/waylib/src/server/qtquick/wsurfaceitem.cpp b/waylib/src/server/qtquick/wsurfaceitem.cpp index d0c4ffefa6..953a032a45 100644 --- a/waylib/src/server/qtquick/wsurfaceitem.cpp +++ b/waylib/src/server/qtquick/wsurfaceitem.cpp @@ -10,7 +10,6 @@ #include "wseat.h" #include "wsgtextureprovider.h" #include "wsurface.h" -#include "wscoplistener.h" #include "wsurfaceitem_p.h" #include "wayliblogging.h" @@ -207,19 +206,10 @@ class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate public: WSurfaceItemContentPrivate([[maybe_unused]] WSurfaceItemContent *qq){} - std::unique_ptr surfaceListenerOwner; - void cleanTextureProvider(); void invalidate() { W_Q(WSurfaceItemContent); - // Detach from the wlr_surface signals before the wrapper is gone - // (wlroots asserts empty listener lists on surface destroy). - // Disconnect unconditionally: setSurface() clears d->surface before - // calling invalidate(), so the guard below would skip this and leave - // the old wlr_surface's commit listener attached. - if (surface && surfaceListenerOwner) - surface->removeListeners(surfaceListenerOwner.get()); if (surface) { surface->disconnect(q); if (textureProvider) { @@ -246,13 +236,13 @@ class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate QObject::connect(surface, &WSurface::beforeDestroy, q, [this] { invalidate(); }); - if (!surfaceListenerOwner) - surfaceListenerOwner = std::make_unique(); - surface->listeners(surfaceListenerOwner.get())->add(&surface->handle()->events.commit, this, &WSurfaceItemContentPrivate::updateSurfaceState); + QObject::connect(surface, &WSurface::commit, q, [this] { + updateSurfaceState(); + }); Q_ASSERT(!updateTextureConnection); updateTextureConnection = QObject::connect(surface, &WSurface::commit, - q, [q, this] (quint32 committedState) { + q, [q, this] (quint32 committedState) { const bool bufferChanged = committedState & WLR_SURFACE_STATE_BUFFER; if (bufferChanged) { @@ -807,8 +797,13 @@ void WSurfaceItem::setFlags(const Flags &newFlags) content->setLive(!newFlags.testFlag(NonLive)); } - for (auto sub : std::as_const(d->subsurfaces)) - sub->setFlags(newFlags); + if (d->surface) { + for (auto *subsurface : d->surface->subsurfaces()) { + if (auto *item = subsurface->getAttachedData(this)) { + item->setFlags(newFlags); + } + } + } Q_EMIT flagsChanged(); } @@ -874,8 +869,13 @@ void WSurfaceItem::setDelegate(QQmlComponent *newDelegate) d->initForDelegate(); if (flags() & DelegateForSubsurface) { - for (auto sub : std::as_const(d->subsurfaces)) - sub->setDelegate(newDelegate); + if (d->surface) { + for (auto *subsurface : d->surface->subsurfaces()) { + if (auto *item = subsurface->getAttachedData(this)) { + item->setDelegate(newDelegate); + } + } + } } Q_EMIT delegateChanged(); @@ -1006,40 +1006,37 @@ void WSurfaceItem::releaseResources() d->beforeRequestResizeSurfaceStateSeq = 0; - // Detach from the wlr_surface signals before the wrapper is gone - // (wlroots asserts empty listener lists on surface destroy). - // Disconnect unconditionally: setSurface() clears d->surface before - // calling releaseResources(), so the guard below would skip this and - // leave the old wlr_surface's commit listener attached. - if (d->surface && d->surfaceListenerOwner) - d->surface->removeListeners(d->surfaceListenerOwner.get()); if (d->surface) { d->surface->disconnect(this); } if (!d->surfaceFlags.testFlag(DontCacheLastBuffer)) { - // Keep subsurface items alive as part of this item's cached last buffer. - // They must leave d->subsurfaces, otherwise later bounding-rect updates may - // treat cached/detached items as live subsurfaces. Once removed, the - // queued auto-destroy handler below will skip deleteLater(). - const auto subsurfaces = d->subsurfaces; - for (auto item : subsurfaces) { - d->subsurfaces.removeOne(item); - item->releaseResources(); - auto surface = item->surface(); - if (auto sub = surface ? wlr_subsurface_try_from_wlr_surface(surface->handle()) : nullptr) { - d->subsurfaceDestroyListeners.erase( - std::remove_if(d->subsurfaceDestroyListeners.begin(), d->subsurfaceDestroyListeners.end(), - [sub](const auto &entry) { return entry.subsurface == sub; }), - d->subsurfaceDestroyListeners.end()); + if (d->surface) { + // Keep subsurface items alive as part of this item's cached last buffer. + // They must leave d->subsurfaces, otherwise later bounding-rect updates may + // treat cached/detached items as live subsurfaces. Once removed, the + // queued auto-destroy handler below will skip deleteLater(). + const auto subsurfaces = d->surface->subsurfaces(); + for (auto *subsurface : subsurfaces) { + if (auto *item = subsurface->getAttachedData(this)) { + subsurface->removeAttachedData(this); + subsurface->disconnect(this); + item->releaseResources(); + } } } } else { - QList tmp; - tmp.swap(d->subsurfaces); - for (auto item : std::as_const(tmp)) - item->deleteLater(); + if (d->surface) { + for (auto *subsurface : d->surface->subsurfaces()) { + if (auto *item = subsurface->getAttachedData(this)) { + subsurface->removeAttachedData(this); + Q_EMIT subsurfaceRemoved(item); + item->deleteLater(); + } + } + } } + d->cleanupSubsurfaceContainers(); if (auto content = d->getItemContent()) content->d_func()->invalidate(); @@ -1208,23 +1205,38 @@ void WSurfaceItemPrivate::initForSurface() { Q_Q(WSurfaceItem); - // clean subsurfaces, if cacheLastBuffer is enabled will cache - // the previous WSurface's subsurfaces. - QList tmp; - tmp.swap(subsurfaces); - for (auto item : std::as_const(tmp)) - item->deleteLater(); + if (surface) { + for (auto *subsurface : surface->subsurfaces()) { + if (auto *item = subsurface->getAttachedData(q)) { + subsurface->removeAttachedData(q); + subsurface->disconnect(q); + Q_EMIT q->subsurfaceRemoved(item); + item->deleteLater(); + } + } + } + cleanupSubsurfaceContainers(); if (!surfaceState) surfaceState.reset(new SurfaceState()); - QObject::connect(surface, &WSurface::beforeDestroy, q, &WSurfaceItem::releaseResources, Qt::DirectConnection); - QObject::connect(surface, &WSurface::hasSubsurfaceChanged, q, [this]{ onHasSubsurfaceChanged(); }); - if (!surfaceListenerOwner) - surfaceListenerOwner = std::make_unique(); - surface->listeners(surfaceListenerOwner.get())->add(&surface->handle()->events.commit, q, &WSurfaceItem::onSurfaceCommit); + QObject::connect(surface, &WSurface::beforeDestroy, q, + &WSurfaceItem::releaseResources, Qt::DirectConnection); + QObject::connect(surface, &WSurface::subsurfaceAdded, q, [this](WSubsurface *subsurface) { + ensureSubsurfaceItem(subsurface); + reorderSubsurfaceItems(); + updateBoundingRect(); + }); + QObject::connect(surface, &WSurface::subsurfaceRemoved, q, [this](WSubsurface *subsurface) { + removeSubsurfaceItem(subsurface); + }); + QObject::connect(surface, &WSurface::subsurfaceOrderChanged, q, [this] { + reorderSubsurfaceItems(); + }); + QObject::connect(surface, &WSurface::commit, q, &WSurfaceItem::onSurfaceCommit); - onHasSubsurfaceChanged(); + for (auto *subsurface : surface->subsurfaces()) + ensureSubsurfaceItem(subsurface); updateEventItem(false); q->onSurfaceCommit(); } @@ -1296,42 +1308,14 @@ void WSurfaceItemPrivate::initForDelegate() Q_EMIT q->contentItemChanged(); } -void WSurfaceItemPrivate::onHasSubsurfaceChanged() -{ - auto *wlrSurface = surface->handle(); - Q_ASSERT(wlrSurface); - if (surface->hasSubsurface()) - updateSubsurfaceItem(); -} - void WSurfaceItemPrivate::updateSubsurfaceItem() { - auto surface = this->surface->handle(); Q_ASSERT(surface); Q_ASSERT(contentContainer); - updateSubsurfaceContainers(); - auto updateForContainer = [this](wl_list *subsurfaceList, QQuickItem *container) { - wlr_subsurface *subsurface; - QQuickItem *prev = nullptr; - wl_list_for_each(subsurface, subsurfaceList, current.link) { - auto qwSubsurface = subsurface; - WSurface *surface = WSurface::fromHandle(subsurface->surface); - if (!surface) - continue; - WSurfaceItem *item = ensureSubsurfaceItem(qwSubsurface, container); - item->setSurfaceSizeRatio(surfaceSizeRatio); - Q_ASSERT(item->parentItem() == container); - if (prev) { - Q_ASSERT(prev->parentItem() == item->parentItem()); - item->stackAfter(prev); - } - prev = item; - const QPointF pos = contentContainer->position() + QPointF(subsurface->current.x, subsurface->current.y) / surfaceSizeRatio; - item->setPosition(pos); - } - }; - updateForContainer(&surface->current.subsurfaces_below, belowSubsurfaceContainer); - updateForContainer(&surface->current.subsurfaces_above, aboveSubsurfaceContainer); + for (auto *subsurface : surface->subsurfaces()) + ensureSubsurfaceItem(subsurface); + reorderSubsurfaceItems(); + updateSubsurfacePositions(); updateBoundingRect(); } @@ -1356,144 +1340,164 @@ void WSurfaceItemPrivate::updateContentPosition() updateBoundingRect(); } -WSurfaceItem *WSurfaceItemPrivate::ensureSubsurfaceItem(wlr_subsurface *subsurface, QQuickItem *parent) +WSurfaceItem *WSurfaceItemPrivate::ensureSubsurfaceItem(WSubsurface *subsurface) { Q_Q(WSurfaceItem); - Q_ASSERT(subsurface); - WSurface *subsurfaceSurface = WSurface::fromHandle(subsurface->surface); - Q_ASSERT(subsurfaceSurface); - for (int i = 0; i < subsurfaces.count(); ++i) { - auto surfaceItem = subsurfaces.at(i); - WSurface *surface = surfaceItem->d_func()->surface.get(); + if (auto *item = subsurface->getAttachedData(q)) + return item; + + // Keep the item owned by the parent surface item; its visual parent is + // assigned to the below/above container during subsurface reordering. + auto *item = new WSurfaceItem(q); + item->setDelegate(delegate); + item->setFlags(surfaceFlags); + item->setSurface(subsurface->surface()); + item->setSurfaceSizeRatio(surfaceSizeRatio); + item->setSmooth(q->smooth()); + item->setVisible(subsurface->isMapped()); + subsurface->setAttachedData(q, item); + + QObject::connect(item, &QObject::destroyed, subsurface, [subsurface, item, q] { + if (subsurface->getAttachedData(q) == item) + subsurface->removeAttachedData(q); + }); + QObject::connect(q, &WSurfaceItem::smoothChanged, item, &WSurfaceItem::setSmooth); + QObject::connect(item, &WSurfaceItem::boundingRectChanged, q, [this] { + updateBoundingRect(); + }); + QObject::connect(subsurface, &WSubsurface::positionChanged, q, [this, subsurface] { + updateSubsurfacePositions(); + updateBoundingRect(); + }); + QObject::connect(subsurface, &WSubsurface::mappedChanged, q, [this, subsurface, q](bool mapped) { + if (auto *item = subsurface->getAttachedData(q)) + item->setVisible(mapped); + }); - if (surface && surface == subsurfaceSurface) { - if (surfaceItem->parentItem() == parent) { - return surfaceItem; - } + Q_EMIT q->subsurfaceAdded(item); + return item; +} - // Moving between above/below containers emits removed on the old - // container and added on the new one. Those signals keep - // d->subsurfaces in sync, so preserve the item and only update its - // QObject/visual parents. - surfaceItem->setParent(parent); - surfaceItem->setParentItem(parent); - // Keep external observers' add/remove sequence balanced on container moves. - Q_EMIT q->subsurfaceAdded(surfaceItem); - return surfaceItem; - } - } +void WSurfaceItemPrivate::removeSubsurfaceItem(WSubsurface *subsurface) +{ + WSurfaceItem *item = subsurface->getAttachedData(q_func()); + if (!item) + return; - auto surfaceItem = new WSurfaceItem(parent); - // Delay destroy WSurfaceItem, because if the cause of destroy is because the parent - // surface destroy, and the parent WSurfaceItem::cacheLastBuffer maybe enabled, - // will disable this connection at parent WSurfaceItem::releaseResources to save the - // parent WSurface last frame, the last frame contents should include its subsurfaces's - // contents. - QPointer surfaceItemGuard(surfaceItem); - WSurfaceItemPrivate::SubsurfaceDestroyListener entry { subsurface, {} }; - entry.listener.init(&subsurface->events.destroy, q, [this, surfaceItemGuard, subsurface] (void *) { - // Drop this listener entry first, before checking the guard: - // wlr_subsurface destroy asserts the destroy listener list is - // empty right after emitting, so the entry must go even when the - // child item was already deleted. Self-erase is safe from inside - // this callback (the closure outlives the emission). - for (auto it = subsurfaceDestroyListeners.begin(); it != subsurfaceDestroyListeners.end(); ++it) { - if (it->subsurface == subsurface) { - subsurfaceDestroyListeners.erase(it); - break; - } - } - auto surfaceItem = surfaceItemGuard.data(); - if (!surfaceItem) - return; - // d->subsurfaces is the single live-subsurface registry for the parent. The - // queued handler only owns deletion while the item is still in that registry: - // if another path removed it first, it is either cached with the parent or - // already scheduled for deletion. - if (subsurfaces.removeOne(surfaceItem)) - surfaceItem->deleteLater(); - }); - subsurfaceDestroyListeners.push_back(std::move(entry)); - surfaceItem->setDelegate(delegate); - surfaceItem->setFlags(surfaceFlags); - surfaceItem->setSurface(subsurfaceSurface); - surfaceItem->setSmooth(q->smooth()); - QObject::connect(q, &WSurfaceItem::smoothChanged, surfaceItem, &WSurfaceItem::setSmooth); - QObject::connect(surfaceItem, &WSurfaceItem::boundingRectChanged, q, [this] { - updateBoundingRect(); - }); - Q_EMIT q->subsurfaceAdded(surfaceItem); - - return surfaceItem; -} - -void WSurfaceItemPrivate::connectSubsurfaceContainerSignals(SubsurfaceContainer *container) -{ - QObject::connect(container, - &SubsurfaceContainer::subsurfaceAdded, - q_func(), - [this](WSurfaceItem *item) { - // Container child add/remove is the synchronization point for - // the parent live-subsurface registry. New and reparented - // items enter d->subsurfaces through this path. - // Do not call updateBoundingRect() here: geometry/position for - // newly added subsurfaces is finalized in updateSubsurfaceItem(). - // Do not emit q_func()->subsurfaceAdded() here: external add - // notifications are emitted from ensureSubsurfaceItem() only - // after the item is fully initialized. - if (subsurfaces.contains(item)) { - qCWarning(lcWlSurface) - << "Duplicate subsurface add ignored:" << item; - return; - } - subsurfaces.append(item); - }); - QObject::connect(container, - &SubsurfaceContainer::subsurfaceRemoved, - q_func(), - [this](WSurfaceItem *item) { - // Container child removal is another lifetime path besides - // WSurface::destroyed; keep the parent registry in sync before - // recalculating geometry from d->subsurfaces. - subsurfaces.removeOne(item); - updateBoundingRect(); - Q_EMIT q_func()->subsurfaceRemoved(item); - }); + subsurface->removeAttachedData(q_func()); + + Q_EMIT q_func()->subsurfaceRemoved(item); + item->deleteLater(); + reorderSubsurfaceItems(); + updateBoundingRect(); } -void WSurfaceItemPrivate::updateSubsurfaceContainers() +void WSurfaceItemPrivate::updateSubsurfaceContainer(WSubsurface::Place place, + const QList &subsurfaces, + QPointer &container) { Q_Q(WSurfaceItem); - if (wl_list_empty(&surface->handle()->current.subsurfaces_below) && belowSubsurfaceContainer) { - if (belowSubsurfaceContainer->isEmpty()) { - delete belowSubsurfaceContainer; + if (!subsurfaces.isEmpty()) { + if (container) + return; + + container = new SubsurfaceContainer(q); + const auto zOrder = place == WSubsurface::Place::Below + ? WSurfaceItem::ZOrder::BelowSubsurface + : WSurfaceItem::ZOrder::AboveSubsurface; + container->setZ(static_cast(zOrder)); + container->setVisible(subsurfacesVisible); + QQuickItemPrivate::get(container)->anchors()->setFill(q); + return; + } + + if (!container) + return; + // reorderSubsurfaceItems() may have just moved the last child out of this + // container. Delete immediately when already empty, otherwise wait for the + // child-removed event before deleting it. + if (container->isEmpty()) { + delete container; + } else { + container->deleteAfterEmpty(); + } + container = nullptr; +} + +void WSurfaceItemPrivate::cleanupSubsurfaceContainers() +{ + auto cleanup = [](QPointer &container) { + if (!container) + return; + if (container->isEmpty()) { + delete container; } else { - belowSubsurfaceContainer->deleteAfterEmpty(); + container->deleteAfterEmpty(); } - } else if (!wl_list_empty(&surface->handle()->current.subsurfaces_below) && !belowSubsurfaceContainer) { - belowSubsurfaceContainer = new SubsurfaceContainer(q); - belowSubsurfaceContainer->setZ(static_cast(WSurfaceItem::ZOrder::BelowSubsurface)); - belowSubsurfaceContainer->setVisible(subsurfacesVisible); - QQuickItemPrivate::get(belowSubsurfaceContainer)->anchors()->setFill(q); - connectSubsurfaceContainerSignals(belowSubsurfaceContainer); - } - if (wl_list_empty(&surface->handle()->current.subsurfaces_above) && aboveSubsurfaceContainer) { - if (aboveSubsurfaceContainer->isEmpty()) { - delete aboveSubsurfaceContainer; - } else { - aboveSubsurfaceContainer->deleteAfterEmpty(); + container = nullptr; + }; + + cleanup(belowSubsurfaceContainer); + cleanup(aboveSubsurfaceContainer); +} + +void WSurfaceItemPrivate::reorderSubsurfaceItems() +{ + if (!surface) + return; + + // Ensure destination containers exist before reparenting items. A place + // transition (Below <-> Above) can require moving items into a newly + // created container during this pass. + updateSubsurfaceContainers(); + auto reorder = [owner = q_func()](const QList &subsurfaces, + SubsurfaceContainer *container) { + QQuickItem *previous = nullptr; + for (auto *subsurface : subsurfaces) { + auto *item = subsurface->getAttachedData(owner); + if (!item) + continue; + // QObject ownership stays with the parent surface item while the + // visual parent follows the subsurface's below/above container. + if (item->parentItem() != container) + item->setParentItem(container); + if (previous) + item->stackAfter(previous); + previous = item; + } + }; + + reorder(surface->subsurfacesBelow(), belowSubsurfaceContainer); + reorder(surface->subsurfacesAbove(), aboveSubsurfaceContainer); + // Re-run container maintenance after reparenting so containers that became + // empty during the move can be deleted or scheduled for deferred cleanup. + updateSubsurfaceContainers(); +} + +void WSurfaceItemPrivate::updateSubsurfacePositions() +{ + if (!contentContainer || !surface) + return; + for (auto *subsurface : surface->subsurfaces()) { + if (auto *item = subsurface->getAttachedData(q_func())) { + item->setSurfaceSizeRatio(surfaceSizeRatio); + item->setPosition(contentContainer->position() + subsurface->position() / surfaceSizeRatio); } - } else if (!wl_list_empty(&surface->handle()->current.subsurfaces_above) && !aboveSubsurfaceContainer) { - aboveSubsurfaceContainer = new SubsurfaceContainer(q); - aboveSubsurfaceContainer->setZ(static_cast(WSurfaceItem::ZOrder::AboveSubsurface)); - aboveSubsurfaceContainer->setVisible(subsurfacesVisible); - QQuickItemPrivate::get(aboveSubsurfaceContainer)->anchors()->setFill(q); - connectSubsurfaceContainerSignals(aboveSubsurfaceContainer); } } +void WSurfaceItemPrivate::updateSubsurfaceContainers() +{ + updateSubsurfaceContainer(WSubsurface::Place::Below, + surface->subsurfacesBelow(), + belowSubsurfaceContainer); + updateSubsurfaceContainer(WSubsurface::Place::Above, + surface->subsurfacesAbove(), + aboveSubsurfaceContainer); +} + void WSurfaceItemPrivate::resizeSurfaceToItemSize(const QSize &itemSize, const QSize &sizeDiff) { Q_Q(WSurfaceItem); @@ -1593,8 +1597,13 @@ QRectF WSurfaceItemPrivate::calculateBoundingRect() const if (contentContainer) rect |= q->mapFromItem(contentContainer, contentContainer->boundingRect()); - for (auto sub : std::as_const(subsurfaces)) - rect |= sub->boundingRect().translated(sub->position()); + if (surface) { + for (auto *subsurface : surface->subsurfaces()) { + if (auto *item = subsurface->getAttachedData(q_func())) { + rect |= item->boundingRect().translated(item->position()); + } + } + } return rect; } diff --git a/waylib/src/server/wayliblogging.cpp b/waylib/src/server/wayliblogging.cpp index 19dcd709fb..89ebe5de43 100644 --- a/waylib/src/server/wayliblogging.cpp +++ b/waylib/src/server/wayliblogging.cpp @@ -53,6 +53,7 @@ Q_LOGGING_CATEGORY(lcWlForeignToplevel, "waylib.protocols.foreigntoplevel", QtWa Q_LOGGING_CATEGORY(lcWlInputMethod, "waylib.protocols.inputmethod", QtInfoMsg) // Input method v2 and virtual keyboard Q_LOGGING_CATEGORY(lcWlExtForeignToplevel, "waylib.protocols.extforeigntoplevel", QtWarningMsg) // ext-foreign-toplevel-list-v1 Q_LOGGING_CATEGORY(lcWlTextInput, "waylib.protocols.textinput", QtInfoMsg) // Text input v1/v2/v3 protocol +Q_LOGGING_CATEGORY(lcWlRemoteSubsurface, "waylib.protocols.remotesubsurface", QtWarningMsg) // treeland-remote-subsurface-unstable-v1 // Layer shell Q_LOGGING_CATEGORY(lcWlLayerShell, "waylib.layer.shell", QtWarningMsg) // wlr-layer-shell surface requests diff --git a/waylib/src/server/wayliblogging.h b/waylib/src/server/wayliblogging.h index 1705a60927..31d553316c 100644 --- a/waylib/src/server/wayliblogging.h +++ b/waylib/src/server/wayliblogging.h @@ -46,6 +46,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcWlForeignToplevel) Q_DECLARE_LOGGING_CATEGORY(lcWlInputMethod) Q_DECLARE_LOGGING_CATEGORY(lcWlExtForeignToplevel) Q_DECLARE_LOGGING_CATEGORY(lcWlTextInput) +Q_DECLARE_LOGGING_CATEGORY(lcWlRemoteSubsurface) // Layer shell Q_DECLARE_LOGGING_CATEGORY(lcWlLayerShell) diff --git a/waylib/tools/CMakeLists.txt b/waylib/tools/CMakeLists.txt index 237eda2a1d..fa389fb93e 100644 --- a/waylib/tools/CMakeLists.txt +++ b/waylib/tools/CMakeLists.txt @@ -12,9 +12,6 @@ if(CMAKE_CROSSCOMPILING AND QTWAYLANDSCANNER_EXECUTABLE) elseif(CMAKE_CROSSCOMPILING) # search native tooling prefix set(NATIVE_PREFIX "" CACHE STRING "CMAKE_PREFIX_PATH for native Qt libraries") - if (NOT NATIVE_PREFIX) - string(SUBSTRING ${idx} NATIVE_PREFIX) - endif() message(STATUS "Building qtwaylandscanner_waylib against ${NATIVE_PREFIX}")