diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 692cd848..cc875c47 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,6 +44,7 @@ target_link_libraries(treeland shortcut wallpaper-color window-management + virtual-output ) target_compile_definitions(treeland diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index 816e5df3..91de6627 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -48,3 +48,4 @@ add_subdirectory("personalization") add_subdirectory("shortcut") add_subdirectory("wallpaper-color") add_subdirectory("window-management") +add_subdirectory("virtual-output") diff --git a/src/modules/virtual-output/CMakeLists.txt b/src/modules/virtual-output/CMakeLists.txt new file mode 100644 index 00000000..b29ca934 --- /dev/null +++ b/src/modules/virtual-output/CMakeLists.txt @@ -0,0 +1,40 @@ +set(MODULE_NAME virtual-output) + +ws_generate_local(server ${CMAKE_CURRENT_SOURCE_DIR}/protocols/treeland-virtual-output-manager-v1.xml server-protocol) + +pkg_search_module(WLROOTS REQUIRED IMPORTED_TARGET wlroots) + +qt_add_library(${MODULE_NAME} STATIC) + +target_sources(${MODULE_NAME} PUBLIC +FILE_SET HEADERS +FILES + virtualoutputmanager.h + impl/virtual_output_manager_impl.h +) + +target_sources(${MODULE_NAME} PRIVATE + virtualoutputmanager.cpp + impl/virtual_output_manager_impl.cpp + server-protocol.c +) + +target_include_directories(${MODULE_NAME} PUBLIC + $ + $ +) + +target_compile_definitions(${MODULE_NAME} + PRIVATE + WLR_USE_UNSTABLE +) + +target_link_libraries(${MODULE_NAME} + PUBLIC + utils + PkgConfig::WLROOTS + Waylib::WaylibServer + Qt${QT_MAJOR_VERSION}::Core + Qt${QT_MAJOR_VERSION}::Gui + Qt${QT_MAJOR_VERSION}::Quick +) diff --git a/src/modules/virtual-output/impl/virtual_output_manager_impl.cpp b/src/modules/virtual-output/impl/virtual_output_manager_impl.cpp new file mode 100644 index 00000000..3248c78d --- /dev/null +++ b/src/modules/virtual-output/impl/virtual_output_manager_impl.cpp @@ -0,0 +1,217 @@ +// Copyright (C) 2024 Lu YaNing . +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "virtual_output_manager_impl.h" + +#include +#include + +#define TREELAND_VIRTUAL_OUTPUT_MANAGER_V1_VERSION 1 + +using QW_NAMESPACE::QWDisplay; + +static treeland_virtual_output_manager_v1 *virtual_output_manager_from_resource(wl_resource *resource); +static void virtual_output_manager_bind(wl_client *client, void *data, uint32_t version, uint32_t id); + +static void virtual_output_manager_handle_create_virtual_output([[maybe_unused]]struct wl_client *client, + struct wl_resource *manager_resource, + uint32_t id, + const char *name, + struct wl_array *outputs); + +static void virtual_output_manager_handle_get_virtual_output_list([[maybe_unused]]struct wl_client *client, struct wl_resource *resource); + +static void virtual_output_manager_handle_get_virtual_output([[maybe_unused]]struct wl_client *client, + struct wl_resource *resource, + const char *name, + uint32_t id); + +static void virtual_output_handle_destroy([[maybe_unused]]struct wl_client *client, + struct wl_resource *resource); + + +static void virtual_output_handle_destroy([[maybe_unused]]struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + + +static const struct virtual_output_v1_interface virtual_output_impl { + .destroy = virtual_output_handle_destroy, +}; + + +static const struct virtual_output_manager_v1_interface virtual_output_manager_impl { + .create_virtual_output = virtual_output_manager_handle_create_virtual_output, + .get_virtual_output_list = virtual_output_manager_handle_get_virtual_output_list, + .get_virtual_output = virtual_output_manager_handle_get_virtual_output, +}; + +static void virtual_output_resource_destroy(struct wl_resource *resource) +{ + wl_list_remove(wl_resource_get_link(resource)); +} + +static void virtual_output_manager_resource_destroy(struct wl_resource *resource) +{ + wl_list_remove(wl_resource_get_link(resource)); +} + +static struct treeland_virtual_output_v1 *treeland_virtual_output_from_resource(wl_resource *resource) +{ + assert(wl_resource_instance_of(resource, + &virtual_output_v1_interface, + &virtual_output_impl)); + auto *manager = static_cast(wl_resource_get_user_data(resource)); + assert(manager != nullptr); + return manager; +} + +static treeland_virtual_output_manager_v1 *virtual_output_manager_from_resource(wl_resource *resource) +{ + assert(wl_resource_instance_of(resource, + &virtual_output_manager_v1_interface, + &virtual_output_manager_impl)); + auto *manager = static_cast(wl_resource_get_user_data(resource)); + assert(manager != nullptr); + return manager; +} + +static void virtual_output_manager_handle_create_virtual_output([[maybe_unused]]struct wl_client *client, + struct wl_resource *manager_resource, + uint32_t id, + const char *name, + struct wl_array *outputs) +{ + auto *manager = virtual_output_manager_from_resource(manager_resource); + + auto *virtual_output = new treeland_virtual_output_v1; + if (virtual_output == nullptr) { + wl_resource_post_no_memory(manager_resource); + return; + } + + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *resource = + wl_resource_create(client, &virtual_output_v1_interface, version, id); + if (resource == nullptr) { + delete virtual_output; + wl_resource_post_no_memory(manager_resource); + return; + } + + wl_resource_set_implementation(resource, + &virtual_output_impl, + virtual_output, + virtual_output_resource_destroy); + wl_resource_set_user_data(resource, virtual_output); + + virtual_output->manager = manager; + virtual_output->resource = resource; + virtual_output->name = name; + virtual_output->screen_outputs = outputs; + + manager->virtual_output.append(virtual_output); + QObject::connect(virtual_output, &treeland_virtual_output_v1::beforeDestroy, manager, [manager, virtual_output]() { + manager->virtual_output.removeOne(virtual_output); + }); + + virtual_output->send_outputs(virtual_output->name, outputs); + Q_EMIT virtual_output->manager->virtualOutputCreated(virtual_output); +} + +static void virtual_output_manager_handle_get_virtual_output_list([[maybe_unused]]struct wl_client *client, struct wl_resource *resource) +{ + auto *manager = virtual_output_manager_from_resource(resource); + + wl_array arr; + wl_array_init(&arr); + for(int i = 0; i < manager->virtual_output.size(); ++i) { + treeland_virtual_output_v1 *virtual_output = manager->virtual_output.at(i); + char *dest = static_cast(wl_array_add(&arr, virtual_output->name.length() + 1)); + strncpy(dest, virtual_output->name.toLatin1().data(), static_cast(virtual_output->name.length())); + } + + virtual_output_manager_v1_send_virtual_output_list(resource,&arr); +} + +static void virtual_output_manager_handle_get_virtual_output([[maybe_unused]]struct wl_client *client, + struct wl_resource *resource, + const char *name, + uint32_t id) +{ + auto *manager = virtual_output_manager_from_resource(resource); + for(int i = 0; i < manager->virtual_output.size(); ++i) { + treeland_virtual_output_v1 *virtual_output = manager->virtual_output.at(i); + if (virtual_output->name == name) { + virtual_output->send_outputs(name, virtual_output->screen_outputs); + } + } +} + +void treeland_virtual_output_v1::send_outputs(QString name, struct wl_array *outputs) +{ + virtual_output_v1_send_outputs(resource, name.toUtf8().data(), outputs); +} + +void treeland_virtual_output_v1::send_error(uint32_t code, const char *message) +{ + virtual_output_v1_send_error(resource, code, message); +} + +treeland_virtual_output_v1::~treeland_virtual_output_v1() +{ + Q_EMIT beforeDestroy(); + if (resource) + wl_resource_set_user_data(resource,nullptr); +} + +treeland_virtual_output_manager_v1::~treeland_virtual_output_manager_v1() +{ + Q_EMIT beforeDestroy(); + if (global) + wl_global_destroy(global); +} + +static void virtual_output_manager_bind(wl_client *client, void *data, uint32_t version, uint32_t id) +{ + auto *manager = static_cast(data); + + struct wl_resource *resource = + wl_resource_create(client, &virtual_output_manager_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, + &virtual_output_manager_impl, + manager, + virtual_output_manager_resource_destroy); + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); +} + +treeland_virtual_output_manager_v1 *treeland_virtual_output_manager_v1::create(QWDisplay *display) +{ + auto *manager = new treeland_virtual_output_manager_v1; + if (!manager) + return nullptr; + + manager->event_loop = wl_display_get_event_loop(display->handle()); + manager->global = wl_global_create(display->handle(), + &virtual_output_manager_v1_interface, + TREELAND_VIRTUAL_OUTPUT_MANAGER_V1_VERSION, + manager, + virtual_output_manager_bind); + if (!manager->global) { + delete manager; + return nullptr; + } + + wl_list_init(&manager->resources); + + connect(display, &QWDisplay::beforeDestroy, manager, [manager] { delete manager; }); + + return manager; +} diff --git a/src/modules/virtual-output/impl/virtual_output_manager_impl.h b/src/modules/virtual-output/impl/virtual_output_manager_impl.h new file mode 100644 index 00000000..7c0bc959 --- /dev/null +++ b/src/modules/virtual-output/impl/virtual_output_manager_impl.h @@ -0,0 +1,50 @@ +// Copyright (C) 2024 Lu YaNing . +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "server-protocol.h" +#include +#include +#include +#include + +#include +#include + +struct treeland_virtual_output_v1; + +struct treeland_virtual_output_manager_v1 : public QObject +{ + Q_OBJECT +public: + ~treeland_virtual_output_manager_v1(); + + wl_global *global{ nullptr }; + wl_list resources; + wl_event_loop *event_loop{ nullptr }; + QList virtual_output; + + static treeland_virtual_output_manager_v1 *create(QW_NAMESPACE::QWDisplay *display); + +Q_SIGNALS: + void virtualOutputCreated(treeland_virtual_output_v1 *virtual_output); + void beforeDestroy(); +}; + +struct treeland_virtual_output_v1 : public QObject +{ + Q_OBJECT +public: + ~treeland_virtual_output_v1(); + treeland_virtual_output_manager_v1 *manager{ nullptr }; + wl_resource *resource{ nullptr }; + QString name; + struct wl_array *screen_outputs; + + void send_outputs(QString name, struct wl_array *outputs); + void send_error(uint32_t code, const char *message); // tode: send err code and message + +Q_SIGNALS: + void beforeDestroy(); +}; diff --git a/src/modules/virtual-output/protocols/treeland-virtual-output-manager-v1.xml b/src/modules/virtual-output/protocols/treeland-virtual-output-manager-v1.xml new file mode 100644 index 00000000..9b21c146 --- /dev/null +++ b/src/modules/virtual-output/protocols/treeland-virtual-output-manager-v1.xml @@ -0,0 +1,84 @@ + + + . + SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + ]]> + + + This interface is a manager that allows the creation of copied output. + + + + Create virtual output that can be used when setting screen copy mode for use + on multiple screens. Virtual outputs are created to mirror multiple wl_output + outputs. + + The elements of the array outputs are the wl_output corresponding to + the screen. + + The first element of the array outputs is the screen to be copied, and + the subsequent elements are the screens to be mirrored. + + The client calling this interface will not generate an additional wl_output + object on the client. + + + + + + + + Gets a list of virtual output names. + + + + + Sends a list of virtual output names to the client. + + + + + + The client obtains the corresponding virtual_output_v1 object + through the virtual output name. + + + + + + + + A virtual_output_v1 represents a set virtual screen output object. + + + + This event is sent to the client when any screen in the array changes. + + The elements of the array outputs are the wl_output corresponding to + the screen. + + The first element of the array outputs is the screen to be copied, and + the subsequent elements are the screens to be mirrored. + + When the primary screen (the screen being copied) is removed, a successor + is selected from the queue as the primary screen, and the queue information + is updated. + + + + + + + Send invalid replication output signal to the client. + + + + + + + Destroy the output. + + + + diff --git a/src/modules/virtual-output/virtualoutputmanager.cpp b/src/modules/virtual-output/virtualoutputmanager.cpp new file mode 100644 index 00000000..fae3a61d --- /dev/null +++ b/src/modules/virtual-output/virtualoutputmanager.cpp @@ -0,0 +1,96 @@ +// Copyright (C) 2024 Lu YaNing . +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "virtualoutputmanager.h" + +#include + +#include +#include + +#include +#include +#include + +#include +#include + +extern "C" { +#include +} + +static VirtualOutputV1 *VIRTUAL_OUTPUT = nullptr; + +VirtualOutputManagerAttached::VirtualOutputManagerAttached( + WSurface *target, VirtualOutputV1 *manager) + : QObject(target) + , m_target(target) + , m_manager(manager) +{ +} + +VirtualOutputV1::VirtualOutputV1(QObject *parent) +{ + if (VIRTUAL_OUTPUT) { + qFatal("There are multiple instances of VirtualOutputV1"); + } + + VIRTUAL_OUTPUT = this; +} + +void VirtualOutputV1::onVirtualOutputCreated(treeland_virtual_output_v1 *virtual_output) +{ + m_virtualOutput.push_back(virtual_output); + connect(virtual_output, &treeland_virtual_output_v1::beforeDestroy, this, [this, virtual_output] { + std::erase_if(m_virtualOutput, [virtual_output](auto *p) { + return p == virtual_output; + }); + }); + + if(virtual_output->name.isEmpty() || !virtual_output->screen_outputs || virtual_output->screen_outputs->size > 0) + return; // tode: send error to client + + Q_EMIT requestCreateVirtualOutput(virtual_output->name, virtual_output->screen_outputs); +} + +void VirtualOutputV1::setVirtualOutput(QString name, struct wl_array *array) +{ + if(name.isEmpty() || !array || array->size > 0) { + qDebug() << ""; + return; + } + + Q_EMIT requestCreateVirtualOutput(name, array); +} + +void VirtualOutputV1::create(WServer *server) +{ + m_manager = treeland_virtual_output_manager_v1::create(server->handle()); + connect(m_manager, + &treeland_virtual_output_manager_v1::virtualOutputCreated, + this, + &VirtualOutputV1::onVirtualOutputCreated); +} + +void VirtualOutputV1::destroy(WServer *server) { } + +wl_global *VirtualOutputV1::global() const +{ + return m_manager->global; +} + +// VirtualOutputManagerAttached *VirtualOutputV1::qmlAttachedProperties( +// QObject *target) +// { +// if (auto *surface = qobject_cast(target)) { +// return new VirtualOutputManagerAttached(surface->surface(), +// VIRTUAL_OUTPUT); +// } + +// if (auto *surface = qobject_cast(target)) { +// return new VirtualOutputManagerAttached(surface->surface(), +// VIRTUAL_OUTPUT); +// } + +// return nullptr; +// } diff --git a/src/modules/virtual-output/virtualoutputmanager.h b/src/modules/virtual-output/virtualoutputmanager.h new file mode 100644 index 00000000..f5201106 --- /dev/null +++ b/src/modules/virtual-output/virtualoutputmanager.h @@ -0,0 +1,55 @@ +// Copyright (C) 2024 Lu YaNing . +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once +#include "impl/virtual_output_manager_impl.h" + +#include +#include +#include + +QW_USE_NAMESPACE +WAYLIB_SERVER_USE_NAMESPACE + +class VirtualOutputV1; + +class VirtualOutputManagerAttached : public QObject +{ + Q_OBJECT + QML_ANONYMOUS + +public: + VirtualOutputManagerAttached(WSurface *target, VirtualOutputV1 *manager); + +Q_SIGNALS: + +private: + WSurface *m_target; + VirtualOutputV1 *m_manager; +}; + +class VirtualOutputV1 : public QObject, public WServerInterface +{ + Q_OBJECT + +public: + explicit VirtualOutputV1(QObject *parent = nullptr); + + // static TreelandVirtualOutput *qmlAttachedProperties(QObject *target); + void setVirtualOutput(QString name, struct wl_array *array); + +Q_SIGNALS: + + void requestCreateVirtualOutput(QString name, struct wl_array *array); + +private Q_SLOTS: + void onVirtualOutputCreated(treeland_virtual_output_v1 *virtual_output); + +private: + void create(WServer *server) override; + void destroy(WServer *server) override; + wl_global *global() const override; + + std::vector m_virtualOutput; + treeland_virtual_output_manager_v1 *m_manager{ nullptr }; +}; diff --git a/src/treeland.cpp b/src/treeland.cpp index fbc8d1d5..2b5e31f1 100644 --- a/src/treeland.cpp +++ b/src/treeland.cpp @@ -17,6 +17,7 @@ #include "shortcutmanager.h" #include "wallpapercolor.h" #include "windowmanagement.h" +#include "virtualoutputmanager.h" #include #include @@ -283,6 +284,11 @@ void TreeLand::setup() 0, "WindowManagementV1", m_server->attach()); + qmlRegisterSingletonInstance("TreeLand.Protocols", + 1, + 0, + "VirtualOutputV1", + m_server->attach()); m_engine->loadFromModule("TreeLand", "Main"); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index abfbcd19..4d55d28d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,3 +3,4 @@ add_subdirectory(test_wallpaper_color) add_subdirectory(test_show_desktop) add_subdirectory(test_window_crusor) add_subdirectory(test_xdgport_wallpaper) +add_subdirectory(test_virtual_output) diff --git a/tests/test_virtual_output/CMakeLists.txt b/tests/test_virtual_output/CMakeLists.txt new file mode 100644 index 00000000..abfc1a24 --- /dev/null +++ b/tests/test_virtual_output/CMakeLists.txt @@ -0,0 +1,19 @@ +find_package(Qt6 REQUIRED COMPONENTS WaylandClient Widgets) + +qt_add_executable(test_virtual_output + main.cpp +) + +qt_generate_wayland_protocol_client_sources(test_virtual_output + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/modules/virtual-output/protocols/treeland-virtual-output-manager-v1.xml +) + +target_link_libraries(test_virtual_output + PRIVATE + Qt${QT_MAJOR_VERSION}::Gui + Qt${QT_MAJOR_VERSION}::Widgets + Qt::WaylandClient + Qt::GuiPrivate + Qt::WaylandClientPrivate +) diff --git a/tests/test_virtual_output/main.cpp b/tests/test_virtual_output/main.cpp new file mode 100644 index 00000000..ed2126af --- /dev/null +++ b/tests/test_virtual_output/main.cpp @@ -0,0 +1,102 @@ +// Copyright (C) 2024 Lu YaNing . +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwayland-treeland-virtual-output-manager-v1.h" + +#include +#include + +#include +#include +#include +#include + +class VirtualOutputManager : public QWaylandClientExtensionTemplate, + public QtWayland::virtual_output_manager_v1 +{ + Q_OBJECT +public: + explicit VirtualOutputManager(); + + void treeland_virtual_output_manager_v1_virtual_output_list(wl_array *names) + { + // qInfo() << "-------Show Desktop State----- " << state; + } +}; + +VirtualOutputManager::VirtualOutputManager() + : QWaylandClientExtensionTemplate(1) +{ +} + +class VirtualOutput : public QWaylandClientExtensionTemplate, + public QtWayland::virtual_output_v1 +{ + Q_OBJECT +public: + explicit VirtualOutput(struct ::virtual_output_v1 *object); + + void treeland_virtual_output_v1_outputs(const QString &name, wl_array *outputs){ + qInfo() << "-------Screen group name---- " << name; + } + + void treeland_virtual_output_v1_error(uint32_t code, const QString &message){ + + qInfo() << "error code:"<(1) + , QtWayland::virtual_output_v1(object) +{ +} + +int main(int argc, char *argv[]) +{ + qputenv("QT_QPA_PLATFORM", "wayland"); + QApplication app(argc, argv); + VirtualOutputManager manager; + + QObject::connect(&manager, &VirtualOutputManager::activeChanged, &manager, [&manager] { + if (manager.isActive()) { + QWidget *widget = new QWidget; + widget->setAttribute(Qt::WA_TranslucentBackground); + widget->resize(640, 480); + + widget->show(); + + QWindow *window = widget->windowHandle(); + + if (window && window->handle()) { + QtWaylandClient::QWaylandWindow *waylandWindow = + static_cast(window->handle()); + + qInfo() << "--------waylandWindow->waylandScreen()->name()---------" << waylandWindow->waylandScreen()->name(); + + struct wl_output *output = waylandWindow->waylandScreen()->output(); + + QList screens = waylandWindow->display()->screens(); + qInfo() << "--------screens---------" << screens.size(); + + // 实际使用需要判断主屏(被镜像的屏幕),将主屏放在wl_array的第一个,复制屏幕依次填充 + if (!screens.isEmpty()){ + QByteArray pointerAddressesArray; + for(auto *screen : screens) { + quintptr address = reinterpret_cast(screen->output()); + pointerAddressesArray.append(reinterpret_cast(&address), sizeof(address)); + } + + VirtualOutput *screen_output = + new VirtualOutput(manager.create_virtual_output("copyscreen1",pointerAddressesArray)); //"copyscreen1": 客户端自定义分组名称 + + } + } + } + }); + + return app.exec(); +} + +#include "main.moc" diff --git a/tests/test_window_bg/main.cpp b/tests/test_window_bg/main.cpp index f676cf66..7f37a41e 100644 --- a/tests/test_window_bg/main.cpp +++ b/tests/test_window_bg/main.cpp @@ -60,6 +60,7 @@ int main(int argc, char *argv[]) QWindow *window = widget->windowHandle(); if (window && window->handle()) { + qInfo() << "--------manager.isActive()---------"<(window->handle());