From 9cb7f0bfcb48be2489c903f1bd96d82d4e29da44 Mon Sep 17 00:00:00 2001 From: April & May & June Date: Mon, 5 Jan 2026 22:27:36 +0800 Subject: [PATCH] feat: expose a method to move XWayland window position relative to wl_surface For the requirement of DDE. --- examples/CMakeLists.txt | 1 + .../test_set_xwindow_position/CMakeLists.txt | 22 +++++ examples/test_set_xwindow_position/main.cpp | 98 +++++++++++++++++++ .../dde-shell/ddeshellmanagerinterfacev1.cpp | 24 +++++ src/seat/helper.cpp | 38 +++++++ src/seat/helper.h | 2 + 6 files changed, 185 insertions(+) create mode 100644 examples/test_set_xwindow_position/CMakeLists.txt create mode 100644 examples/test_set_xwindow_position/main.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index abae27a56..a5a962b27 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -21,3 +21,4 @@ add_subdirectory(test_color_control) add_subdirectory(test_shortcut_manager) add_subdirectory(test_keystate) add_subdirectory(test_set_wallpaper) +add_subdirectory(test_set_xwindow_position) diff --git a/examples/test_set_xwindow_position/CMakeLists.txt b/examples/test_set_xwindow_position/CMakeLists.txt new file mode 100644 index 000000000..92e48e3f7 --- /dev/null +++ b/examples/test_set_xwindow_position/CMakeLists.txt @@ -0,0 +1,22 @@ +find_package(Qt6 REQUIRED COMPONENTS WaylandClient Widgets) +find_package(TreelandProtocols REQUIRED) + +qt_add_executable(test-set-xwindow-position + main.cpp +) + +qt_generate_wayland_protocol_client_sources(test-set-xwindow-position + FILES + ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-dde-shell-v1.xml + NO_INCLUDE_CORE_ONLY +) + +target_link_libraries(test-set-xwindow-position + PRIVATE + Qt6::Gui + Qt6::GuiPrivate + Qt6::Widgets + Qt6::WaylandClient +) + +install(TARGETS test-set-xwindow-position RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") diff --git a/examples/test_set_xwindow_position/main.cpp b/examples/test_set_xwindow_position/main.cpp new file mode 100644 index 000000000..2c35bb1aa --- /dev/null +++ b/examples/test_set_xwindow_position/main.cpp @@ -0,0 +1,98 @@ +// Copyright (C) 2025 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 +#include +#include +#include +#include +#include + +#include + +#include + +#include + +class DDEShellManagerV1 + : public QWaylandClientExtensionTemplate + , public QtWayland::treeland_dde_shell_manager_v1 +{ + Q_OBJECT +public: + DDEShellManagerV1() + : QWaylandClientExtensionTemplate( + treeland_dde_shell_manager_v1_interface.version) + { + } +}; + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + qWarning() + << "In this example, an xeyes will be launched. Its window will be moved to (0, 0) " + "first using X API. Then, A test window of size 640x480 will be created. The xeyes " + "window will be moved to the top-right of the test window once per second using " + "set_xwindow_position_relative().\n\n" + "xeyes should be installed to run this example (sudo apt install x11-apps)."; + + system("bash -c \"pkill xeyes; xeyes & disown\""); + sleep(1); + system("xdotool search --name \"xeyes\" windowmove 0 0"); + sleep(1); + system("xdotool search --name \"xeyes\" > /tmp/xeyes_wid.txt"); + + QWidget *window = new QWidget(); + window->resize(640, 480); + window->setWindowTitle("Test set xwindow position"); + window->show(); + + uint32_t wid = 0; + QFile file("/tmp/xeyes_wid.txt"); + if (file.open(QIODevice::ReadWrite)) { + wid = file.readLine().trimmed().toUInt(); + file.close(); + file.remove(); + } else { + qCritical() << "Failed to open /tmp/xeyes_wid.txt"; + return 1; + } + + DDEShellManagerV1 manager; + + struct wl_callback_listener callback_listener = { .done = []([[maybe_unused]] void *data, + wl_callback *callback, + uint32_t ok) { + wl_callback_destroy(callback); + if (ok != 0) + qCritical() << "Failed to set xwindow position relative!"; + else + qWarning() << "Successfully set xwindow position relative. Check screen for result."; + } }; + + QTimer timer; + QObject::connect(&timer, &QTimer::timeout, [&] { + if (!manager.isActive()) { + qCritical() << "DDEShellManagerV1 is not active!"; + return; + } + struct wl_surface *surface = static_cast( + QGuiApplication::platformNativeInterface()->nativeResourceForWindow( + QStringLiteral("surface").toLocal8Bit(), + window->windowHandle())); + + wl_fixed_t dx = wl_fixed_from_int(640); + wl_fixed_t dy = wl_fixed_from_int(0); + + wl_callback *callback = manager.set_xwindow_position_relative(wid, surface, dx, dy); + wl_callback_add_listener(callback, &callback_listener, nullptr); + qWarning() << "Setting xwindow position relative, wait for result..."; + }); + timer.start(1000); + + return app.exec(); +} + +#include "main.moc" diff --git a/src/modules/dde-shell/ddeshellmanagerinterfacev1.cpp b/src/modules/dde-shell/ddeshellmanagerinterfacev1.cpp index b119eee09..6202f9fd3 100644 --- a/src/modules/dde-shell/ddeshellmanagerinterfacev1.cpp +++ b/src/modules/dde-shell/ddeshellmanagerinterfacev1.cpp @@ -5,6 +5,8 @@ #include "qwayland-server-treeland-dde-shell-v1.h" +#include "helper.h" + #include #include @@ -12,6 +14,8 @@ #include #include +#include + #define TREELAND_DDE_SHELL_MANAGER_V1_VERSION 1 static QList s_shellSurfaces; @@ -45,6 +49,12 @@ class DDEShellManagerInterfaceV1Private : public QtWaylandServer::treeland_dde_s uint32_t id) override; void treeland_dde_shell_manager_v1_get_treeland_lockscreen(Resource *resource, uint32_t id) override; + void treeland_dde_shell_manager_v1_set_xwindow_position_relative(Resource *resource, + uint32_t callback, + uint32_t wid, + struct ::wl_resource *anchor, + wl_fixed_t dx, + wl_fixed_t dy) override; }; void DDEShellManagerInterfaceV1Private::treeland_dde_shell_manager_v1_get_treeland_lockscreen( @@ -69,6 +79,20 @@ void DDEShellManagerInterfaceV1Private::treeland_dde_shell_manager_v1_get_treela Q_EMIT q->lockScreenCreated(lockScreen); } +void DDEShellManagerInterfaceV1Private::treeland_dde_shell_manager_v1_set_xwindow_position_relative(Resource *resource, + uint32_t callback, + uint32_t wid, + struct ::wl_resource *anchor, + wl_fixed_t dx, + wl_fixed_t dy) +{ + WSurface *wsurface = WSurface::fromHandle(qw_surface::from_resource(anchor)); + uint32_t ok = (wsurface && Helper::instance()->setXWindowPositionRelative(wid, wsurface, dx, dy)) ? 0 : 1; + wl_resource *cb = wl_resource_create(resource->client(), &wl_callback_interface, 1, callback); + wl_callback_send_done(cb, ok); + wl_resource_destroy(cb); +} + DDEShellManagerInterfaceV1Private::DDEShellManagerInterfaceV1Private(DDEShellManagerInterfaceV1 *_q) : q(_q) { diff --git a/src/seat/helper.cpp b/src/seat/helper.cpp index 195211875..64f4862fa 100644 --- a/src/seat/helper.cpp +++ b/src/seat/helper.cpp @@ -2924,3 +2924,41 @@ void Helper::configureNumlock() { } } +/** + * Move a XWayland window's surface corresponding to wid, to a + * position relative to a WSurface. Top-left point is always used. + * + * @param wid X Window ID for the XWayland surface + * @param anchor The anchor WSurface to be relative to + * @param dx Horizontal distance between the top-left point of anchor and the destination + * @param dy Vertical distance between the top-left point of anchor and the destination + */ +bool Helper::setXWindowPositionRelative(uint wid, WSurface *anchor, wl_fixed_t dx, wl_fixed_t dy) const +{ + SurfaceWrapper *ach = m_rootSurfaceContainer->getSurface(anchor); + if (!ach) { + qCWarning(treelandCore) << "setXWindowPositionRelative: Failed to get SurfaceWrapper from WSurface"; + return false; + } + + SurfaceWrapper *target = nullptr; + for (SurfaceWrapper *wrapper : std::as_const(rootSurfaceContainer()->surfaces())) { + if (wrapper->type() == SurfaceWrapper::Type::XWayland) { + wlr_xwayland_surface *surface = + wlr_xwayland_surface_try_from_wlr_surface(wrapper->surface()->handle()->handle()); + if (surface && surface->window_id == static_cast(wid)) { + target = wrapper; + break; + } + } + } + if (!target) { + qCWarning(treelandCore) << "setXWindowPositionRelative: XWayland surface corresponding to WID" << wid << "not found!"; + return false; + } + + QRectF rect(ach->position(), target->size()); + rect.translate(wl_fixed_to_double(dx), wl_fixed_to_double(dy)); + target->setPosition(rect.topLeft()); + return true; +} diff --git a/src/seat/helper.h b/src/seat/helper.h index 62f11dd80..87faf5f08 100644 --- a/src/seat/helper.h +++ b/src/seat/helper.h @@ -262,6 +262,8 @@ class Helper : public WSeatEventFilter void updateIdleInhibitor(); + bool setXWindowPositionRelative(uint wid, WSurface *anchor, wl_fixed_t dx, wl_fixed_t dy) const; + public Q_SLOTS: void activateSurface(SurfaceWrapper *wrapper, Qt::FocusReason reason = Qt::OtherFocusReason); void forceActivateSurface(SurfaceWrapper *wrapper,