From 8f507d8e26e2cc3d69b707806411a8e109fdde33 Mon Sep 17 00:00:00 2001 From: justforlxz Date: Fri, 8 Dec 2023 17:29:19 +0800 Subject: [PATCH] feat: support dock preview support dock preview Log: --- .../treeland-foreign-toplevel-manager-v1.xml | 86 ++++++-- src/treeland/protocols/CMakeLists.txt | 1 + .../protocols/dockpreviewcontextv1.cpp | 99 ++++++++++ .../foreign_toplevel_manager_impl.cpp | 178 ++++++++++++++++- .../protocols/foreign_toplevel_manager_impl.h | 34 +++- .../protocols/foreigntoplevelhandlev1.cpp | 2 +- .../protocols/foreigntoplevelhandlev1.h | 34 +++- .../protocols/foreigntoplevelmanagerv1.cpp | 8 + .../protocols/foreigntoplevelmanagerv1.cpp | 49 ++++- .../protocols/foreigntoplevelmanagerv1.h | 5 + src/treeland/quick/qml/CMakeLists.txt | 1 + src/treeland/quick/qml/DockPreview.qml | 183 ++++++++++++++++++ src/treeland/quick/qml/LayerSurface.qml | 2 + .../quick/qml/StackToplevelHelper.qml | 4 + src/treeland/quick/qml/StackWorkspace.qml | 28 +++ src/treeland/quick/utils/CMakeLists.txt | 2 + .../quick/utils/dockpreviewfilter.cpp | 52 +++++ src/treeland/quick/utils/dockpreviewfilter.h | 29 +++ 18 files changed, 762 insertions(+), 35 deletions(-) create mode 100644 src/treeland/protocols/dockpreviewcontextv1.cpp create mode 100644 src/treeland/quick/qml/DockPreview.qml create mode 100644 src/treeland/quick/utils/dockpreviewfilter.cpp create mode 100644 src/treeland/quick/utils/dockpreviewfilter.h diff --git a/src/treeland/data/treeland-foreign-toplevel-manager-v1.xml b/src/treeland/data/treeland-foreign-toplevel-manager-v1.xml index c9d1a515..a4a3ed34 100644 --- a/src/treeland/data/treeland-foreign-toplevel-manager-v1.xml +++ b/src/treeland/data/treeland-foreign-toplevel-manager-v1.xml @@ -60,6 +60,10 @@ the client should free any resources associated with it. + + + + @@ -93,28 +97,13 @@ - This identifier is used to check if two or more toplevel handles belong - to the same toplevel. - - The identifier is useful for command line tools or privileged clients - which may need to reference an exact toplevel across processes or - instances of the ext_foreign_toplevel_list_v1 global. - - The compositor must only send this event when the handle is created. - - The identifier must be unique per toplevel and it's handles. Two different - toplevels must not have the same identifier. The identifier is only valid - as long as the toplevel is mapped. If the toplevel is unmapped the identifier - must not be reused. An identifier must not be reused by the compositor to - ensure there are no races when sharing identifiers between processes. - - An identifier is a string that contains up to 32 printable ASCII bytes. - An identifier must not be an empty string. It is recommended that a - compositor includes an opaque generation value in identifiers. How the - generation value is used when generating the identifier is implementation - dependent. + The identifier of each top level and its handle must be unique. + Two different top layers cannot have the same identifier. + This identifier is only valid as long as the top level is mapped. + Identifiers must not be reused if the top level is not mapped. + The compositor must not reuse identifiers to ensure there are no races when identifiers are shared between processes. - + @@ -290,4 +279,59 @@ allow-null="true" /> + + + This interface allows dock set windows preview. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + + + + + + + + + + + This event is sent after mouse enter preview box. + + + + + This event is sent after mouse leave preview box. + + + + + + X and Y are relative to the relative_surface. + surfaces wl_array is identifiers. + + + + + + + + + + + close preview box + + + + + + Destroy the context object. + + + diff --git a/src/treeland/protocols/CMakeLists.txt b/src/treeland/protocols/CMakeLists.txt index fb1d0f3e..c3ff63e5 100644 --- a/src/treeland/protocols/CMakeLists.txt +++ b/src/treeland/protocols/CMakeLists.txt @@ -28,6 +28,7 @@ target_sources(treeland-protocols PRIVATE personalizationwallpapercontext.cpp personalizationmanager.cpp qshortcutmanager.cpp + dockpreviewcontextv1.cpp ) target_compile_definitions(treeland-protocols diff --git a/src/treeland/protocols/dockpreviewcontextv1.cpp b/src/treeland/protocols/dockpreviewcontextv1.cpp new file mode 100644 index 00000000..df572375 --- /dev/null +++ b/src/treeland/protocols/dockpreviewcontextv1.cpp @@ -0,0 +1,99 @@ +// Copyright (C) 2023 Dingyuan Zhang . +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + + +#include "foreigntoplevelhandlev1.h" +#include "foreign_toplevel_manager_impl.h" + +#include + +#include +#include +#include + +QW_USE_NAMESPACE + +class TreeLandDockPreviewContextV1Private : public QWObjectPrivate +{ +public: + TreeLandDockPreviewContextV1Private(treeland_dock_preview_context_v1 *handle, bool isOwner, TreeLandDockPreviewContextV1 *qq) + : QWObjectPrivate(handle, isOwner, qq) + { + Q_ASSERT(!map.contains(handle)); + map.insert(handle, qq); + sc.connect(&handle->events.destroy, this, &TreeLandDockPreviewContextV1Private::on_destroy); + sc.connect(&handle->events.request_show, this, &TreeLandDockPreviewContextV1Private::on_request_show); + sc.connect(&handle->events.request_close, this, &TreeLandDockPreviewContextV1Private::on_request_close); + } + ~TreeLandDockPreviewContextV1Private() { + if (!m_handle) + return; + destroy(); + if (isHandleOwner) + treeland_dock_preview_context_v1_destroy(q_func()->handle()); + } + + inline void destroy() { + Q_ASSERT(m_handle); + Q_ASSERT(map.contains(m_handle)); + Q_EMIT q_func()->beforeDestroy(q_func()); + map.remove(m_handle); + sc.invalidate(); + } + + void on_destroy(void *); + void on_request_show(void *); + void on_request_close(void *); + + static QHash map; + QW_DECLARE_PUBLIC(TreeLandDockPreviewContextV1) + QWSignalConnector sc; +}; +QHash TreeLandDockPreviewContextV1Private::map; + +void TreeLandDockPreviewContextV1Private::on_destroy(void *) +{ + destroy(); + m_handle = nullptr; + delete q_func(); +} + +void TreeLandDockPreviewContextV1Private::on_request_show(void *data) +{ + struct treeland_dock_preview_context_v1_preview_event *event = static_cast(data); + Q_EMIT q_func()->requestShow(event); +} + +void TreeLandDockPreviewContextV1Private::on_request_close([[maybe_unused]] void *data) +{ + Q_EMIT q_func()->requestClose(); +} + +TreeLandDockPreviewContextV1::TreeLandDockPreviewContextV1(treeland_dock_preview_context_v1 *handle, bool isOwner) + : QObject(nullptr) + , QWObject(*new TreeLandDockPreviewContextV1Private(handle, isOwner, this)) +{ + +} + +TreeLandDockPreviewContextV1 *TreeLandDockPreviewContextV1::get(treeland_dock_preview_context_v1 *handle) +{ + return TreeLandDockPreviewContextV1Private::map.value(handle); +} + +TreeLandDockPreviewContextV1 *TreeLandDockPreviewContextV1::from(treeland_dock_preview_context_v1 *handle) +{ + if (auto o = get(handle)) + return o; + return new TreeLandDockPreviewContextV1(handle, false); +} + +void TreeLandDockPreviewContextV1::enter() +{ + treeland_dock_preview_context_v1_enter(handle()); +} + +void TreeLandDockPreviewContextV1::leave() +{ + treeland_dock_preview_context_v1_leave(handle()); +} diff --git a/src/treeland/protocols/foreign_toplevel_manager_impl.cpp b/src/treeland/protocols/foreign_toplevel_manager_impl.cpp index 03910ee3..be106d88 100644 --- a/src/treeland/protocols/foreign_toplevel_manager_impl.cpp +++ b/src/treeland/protocols/foreign_toplevel_manager_impl.cpp @@ -43,10 +43,29 @@ static void treeland_foreign_toplevel_handle_set_fullscreen(struct wl_client *cl struct wl_resource *output); static void treeland_foreign_toplevel_handle_unset_fullscreen(struct wl_client *client, struct wl_resource *resource); +static void treeland_foreign_toplevel_handle_set_preview(struct wl_client *client, + struct wl_resource *resource); +static void treeland_foreign_toplevel_handle_unset_preview(struct wl_client *client, + struct wl_resource *resource); static void treeland_foreign_toplevel_handle_destroy(struct wl_client *client, struct wl_resource *resource); static void treeland_foreign_toplevel_manager_handle_stop(struct wl_client *client, struct wl_resource *resource); +static void treeland_foreign_toplevel_manager_handle_get_dock_preview_context(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *relative_surface, + uint32_t id); + +static void treeland_dock_preview_context_handle_show(struct wl_client *client, + struct wl_resource *resource, + struct wl_array *surfaces, + int32_t x, + int32_t y, + uint32_t direction); +static void treeland_dock_preview_context_handle_close(struct wl_client *client, + struct wl_resource *resource); + +static void treeland_dock_preview_context_handle_destroy(struct wl_client *client, struct wl_resource *resource); static const struct ztreeland_foreign_toplevel_handle_v1_interface treeland_toplevel_handle_impl = { .set_maximized = treeland_foreign_toplevel_handle_set_maximized, @@ -61,6 +80,13 @@ static const struct ztreeland_foreign_toplevel_handle_v1_interface treeland_topl .unset_fullscreen = treeland_foreign_toplevel_handle_unset_fullscreen, }; +static const struct treeland_dock_preview_context_v1_interface + treeland_dock_preview_context_impl = { + .show = treeland_dock_preview_context_handle_show, + .close = treeland_dock_preview_context_handle_close, + .destroy = treeland_dock_preview_context_handle_destroy, +}; + static struct treeland_foreign_toplevel_handle_v1 * toplevel_handle_from_resource(struct wl_resource *resource) { @@ -71,6 +97,16 @@ toplevel_handle_from_resource(struct wl_resource *resource) wl_resource_get_user_data(resource)); } +static struct treeland_dock_preview_context_v1 * +toplevel_dock_preview_context_from_resource(struct wl_resource *resource) +{ + assert(wl_resource_instance_of(resource, + &treeland_dock_preview_context_v1_interface, + &treeland_dock_preview_context_impl)); + return static_cast( + wl_resource_get_user_data(resource)); +} + static void toplevel_handle_send_maximized_event(struct wl_resource *resource, bool state) { struct treeland_foreign_toplevel_handle_v1 *toplevel = toplevel_handle_from_resource(resource); @@ -143,6 +179,31 @@ static void toplevel_send_fullscreen_event(struct wl_resource *resource, wl_signal_emit_mutable(&toplevel->events.request_fullscreen, &event); } +static void dock_send_preview_event(struct wl_resource *resource, struct wl_array *surfaces, int32_t x, int32_t y, int32_t direction) +{ + struct treeland_dock_preview_context_v1 *dock_preview = toplevel_dock_preview_context_from_resource(resource); + if (!dock_preview) { + return; + } + + std::vector s; + const uint32_t *data = reinterpret_cast(surfaces->data); + const int32_t count = surfaces->size / sizeof(uint32_t); + for (int i = 0; i != count; ++i) { + s.push_back(data[i]); + } + + struct treeland_dock_preview_context_v1_preview_event event = { + .toplevel = dock_preview, + .toplevels = s, + .x = x, + .y = y, + .direction = static_cast(direction), + }; + + wl_signal_emit_mutable(&dock_preview->events.request_show, &event); +} + static void treeland_foreign_toplevel_handle_set_fullscreen([[maybe_unused]] struct wl_client *client, struct wl_resource *resource, struct wl_resource *output) @@ -280,14 +341,9 @@ void treeland_foreign_toplevel_handle_v1_set_pid( } void treeland_foreign_toplevel_handle_v1_set_identifier( - struct treeland_foreign_toplevel_handle_v1 *toplevel, const char *identifier) + struct treeland_foreign_toplevel_handle_v1 *toplevel, uint32_t identifier) { - free(toplevel->identifier); - toplevel->identifier = strdup(identifier); - if (toplevel->identifier == NULL) { - wlr_log(WLR_ERROR, "failed to allocate memory for toplevel title"); - return; - } + toplevel->identifier = identifier; struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) @@ -589,6 +645,34 @@ void treeland_foreign_toplevel_handle_v1_set_parent( toplevel_update_idle_source(toplevel); } +void treeland_dock_preview_context_v1_enter( + struct treeland_dock_preview_context_v1 *toplevel) +{ + treeland_dock_preview_context_v1_send_enter(toplevel->resource); +} + +void treeland_dock_preview_context_v1_leave( + struct treeland_dock_preview_context_v1 *toplevel) +{ + treeland_dock_preview_context_v1_send_leave(toplevel->resource); +} + +void treeland_dock_preview_context_v1_destroy( + struct treeland_dock_preview_context_v1 *toplevel) +{ + if (!toplevel) { + return; + } + + wl_signal_emit_mutable(&toplevel->events.destroy, toplevel); + + wl_resource_set_user_data(toplevel->resource, NULL); + wl_list_remove(wl_resource_get_link(toplevel->resource)); + wl_list_remove(&toplevel->link); + + free(toplevel); +} + void treeland_foreign_toplevel_handle_v1_destroy( struct treeland_foreign_toplevel_handle_v1 *toplevel) { @@ -643,6 +727,33 @@ static void treeland_foreign_toplevel_resource_destroy(struct wl_resource *resou wl_list_remove(wl_resource_get_link(resource)); } +static void treeland_dock_preview_context_handle_show([[maybe_unused]] struct wl_client *client, + struct wl_resource *resource, + struct wl_array *surfaces, + int32_t x, + int32_t y, + uint32_t direction) +{ + dock_send_preview_event(resource, surfaces, x, y, direction); +} + + +static void treeland_dock_preview_context_handle_close([[maybe_unused]] struct wl_client *client, + struct wl_resource *resource) +{ + struct treeland_dock_preview_context_v1 *dock_preview = toplevel_dock_preview_context_from_resource(resource); + if (!dock_preview) { + return; + } + + wl_signal_emit_mutable(&dock_preview->events.request_close, nullptr); +} + +static void treeland_dock_preview_context_handle_destroy([[maybe_unused]] struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + static struct wl_resource *create_toplevel_resource_for_resource( struct treeland_foreign_toplevel_handle_v1 *toplevel, struct wl_resource *manager_resource) { @@ -700,8 +811,17 @@ treeland_foreign_toplevel_handle_v1_create(struct treeland_foreign_toplevel_mana } static const struct ztreeland_foreign_toplevel_manager_v1_interface - treeland_foreign_toplevel_manager_impl = { .stop = - treeland_foreign_toplevel_manager_handle_stop }; + treeland_foreign_toplevel_manager_impl = { + .stop = treeland_foreign_toplevel_manager_handle_stop, + .get_dock_preview_context = treeland_foreign_toplevel_manager_handle_get_dock_preview_context, +}; + +struct treeland_foreign_toplevel_manager_v1 *foreign_toplevel_manager_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &ztreeland_foreign_toplevel_manager_v1_interface, &treeland_foreign_toplevel_manager_impl)); + struct treeland_foreign_toplevel_manager_v1 *manager = static_cast(wl_resource_get_user_data(resource)); + assert(manager != NULL); + return manager; +} static void treeland_foreign_toplevel_manager_handle_stop([[maybe_unused]] struct wl_client *client, struct wl_resource *resource) @@ -714,6 +834,45 @@ static void treeland_foreign_toplevel_manager_handle_stop([[maybe_unused]] struc wl_resource_destroy(resource); } +static void treeland_dock_preview_context_resource_destroy(struct wl_resource *resource) +{ + wl_list_remove(wl_resource_get_link(resource)); +} + +static void treeland_foreign_toplevel_manager_handle_get_dock_preview_context(struct wl_client *client, + struct wl_resource *manager_resource, + struct wl_resource *relative_surface, + uint32_t id) +{ + auto *manager = foreign_toplevel_manager_from_resource(manager_resource); + + struct treeland_dock_preview_context_v1 *context = static_cast(calloc(1, sizeof(*context))); + if (context == NULL) { + 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, &treeland_dock_preview_context_v1_interface, version, id); + if (resource == NULL) { + free(context); + wl_resource_post_no_memory(manager_resource); + return; + } + + wl_resource_set_implementation(resource, &treeland_dock_preview_context_impl, context, treeland_dock_preview_context_resource_destroy); + + wl_signal_init(&context->events.request_show); + wl_signal_init(&context->events.request_close); + wl_signal_init(&context->events.destroy); + + context->manager = manager; + context->relative_surface = relative_surface; + context->resource = resource; + + wl_signal_emit_mutable(&context->manager->events.dock_preview_created, context); +} + static void treeland_foreign_toplevel_manager_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); @@ -823,6 +982,7 @@ treeland_foreign_toplevel_manager_v1_create(struct wl_display *display) } wl_signal_init(&manager->events.destroy); + wl_signal_init(&manager->events.dock_preview_created); wl_list_init(&manager->resources); wl_list_init(&manager->toplevels); diff --git a/src/treeland/protocols/foreign_toplevel_manager_impl.h b/src/treeland/protocols/foreign_toplevel_manager_impl.h index 3e43169e..d138ed63 100644 --- a/src/treeland/protocols/foreign_toplevel_manager_impl.h +++ b/src/treeland/protocols/foreign_toplevel_manager_impl.h @@ -5,6 +5,8 @@ #include +#include + struct treeland_foreign_toplevel_manager_v1 { struct wl_event_loop *event_loop; struct wl_global *global; @@ -14,6 +16,7 @@ struct treeland_foreign_toplevel_manager_v1 { struct wl_listener display_destroy; struct { + struct wl_signal dock_preview_created; struct wl_signal destroy; } events; @@ -38,6 +41,18 @@ struct treeland_foreign_toplevel_handle_v1_output { struct wl_listener output_destroy; }; +struct treeland_dock_preview_context_v1 { + struct treeland_foreign_toplevel_manager_v1 *manager; + struct wl_resource *resource; + struct wl_list link; + struct wl_resource *relative_surface; + struct { + struct wl_signal request_show; + struct wl_signal request_close; + struct wl_signal destroy; + } events; +}; + struct treeland_foreign_toplevel_handle_v1 { struct treeland_foreign_toplevel_manager_v1 *manager; struct wl_list resources; @@ -46,8 +61,9 @@ struct treeland_foreign_toplevel_handle_v1 { char *title; char *app_id; - char *identifier; + uint32_t identifier; pid_t pid; + struct treeland_foreign_toplevel_handle_v1 *parent; struct wl_list outputs; // treeland_foreign_toplevel_v1_output.link uint32_t state; // enum treeland_foreign_toplevel_v1_state @@ -98,6 +114,13 @@ struct treeland_foreign_toplevel_handle_v1_set_rectangle_event { int32_t x, y, width, height; }; +struct treeland_dock_preview_context_v1_preview_event { + struct treeland_dock_preview_context_v1 *toplevel; + std::vector toplevels; + int32_t x, y; + int32_t direction; +}; + struct treeland_foreign_toplevel_manager_v1 * treeland_foreign_toplevel_manager_v1_create(struct wl_display *display); @@ -115,7 +138,7 @@ void treeland_foreign_toplevel_handle_v1_set_app_id( void treeland_foreign_toplevel_handle_v1_set_pid( struct treeland_foreign_toplevel_handle_v1 *toplevel, const pid_t pid); void treeland_foreign_toplevel_handle_v1_set_identifier( - struct treeland_foreign_toplevel_handle_v1 *toplevel, const char *identifier); + struct treeland_foreign_toplevel_handle_v1 *toplevel, uint32_t identifier); void treeland_foreign_toplevel_handle_v1_output_enter( struct treeland_foreign_toplevel_handle_v1 *toplevel, @@ -135,3 +158,10 @@ void treeland_foreign_toplevel_handle_v1_set_fullscreen( void treeland_foreign_toplevel_handle_v1_set_parent( struct treeland_foreign_toplevel_handle_v1 *toplevel, struct treeland_foreign_toplevel_handle_v1 *parent); + +void treeland_dock_preview_context_v1_enter( + struct treeland_dock_preview_context_v1 *toplevel); +void treeland_dock_preview_context_v1_leave( + struct treeland_dock_preview_context_v1 *toplevel); +void treeland_dock_preview_context_v1_destroy( + struct treeland_dock_preview_context_v1 *toplevel); diff --git a/src/treeland/protocols/foreigntoplevelhandlev1.cpp b/src/treeland/protocols/foreigntoplevelhandlev1.cpp index f93247d4..1bc57a1b 100644 --- a/src/treeland/protocols/foreigntoplevelhandlev1.cpp +++ b/src/treeland/protocols/foreigntoplevelhandlev1.cpp @@ -173,7 +173,7 @@ void TreeLandForeignToplevelHandleV1::setPid(pid_t pid) treeland_foreign_toplevel_handle_v1_set_pid(handle(), pid); } -void TreeLandForeignToplevelHandleV1::setIdentifier(const char *identifier) +void TreeLandForeignToplevelHandleV1::setIdentifier(uint32_t identifier) { treeland_foreign_toplevel_handle_v1_set_identifier(handle(), identifier); } diff --git a/src/treeland/protocols/foreigntoplevelhandlev1.h b/src/treeland/protocols/foreigntoplevelhandlev1.h index 3c443ca2..bd51e5bb 100644 --- a/src/treeland/protocols/foreigntoplevelhandlev1.h +++ b/src/treeland/protocols/foreigntoplevelhandlev1.h @@ -12,6 +12,7 @@ struct treeland_foreign_toplevel_handle_v1_minimized_event; struct treeland_foreign_toplevel_handle_v1_activated_event; struct treeland_foreign_toplevel_handle_v1_fullscreen_event; struct treeland_foreign_toplevel_handle_v1_set_rectangle_event; +struct treeland_dock_preview_context_v1_preview_event; struct treeland_foreign_toplevel_manager_v1; QW_USE_NAMESPACE @@ -21,6 +22,7 @@ class QWOutput; class QWDisplay; QW_END_NAMESPACE +class TreeLandDockPreviewContextV1; class TreeLandForeignToplevelManagerV1Private; class QW_EXPORT TreeLandForeignToplevelManagerV1 : public QObject, public QWObject { @@ -37,6 +39,7 @@ class QW_EXPORT TreeLandForeignToplevelManagerV1 : public QObject, public QWObje Q_SIGNALS: void beforeDestroy(TreeLandForeignToplevelManagerV1 *self); + void dockPreviewContextCreated(TreeLandDockPreviewContextV1 *context); private: TreeLandForeignToplevelManagerV1(treeland_foreign_toplevel_manager_v1 *handle, bool isOwner); @@ -69,7 +72,7 @@ class TreeLandForeignToplevelHandleV1 : public QObject, public QWObject void setParent(TreeLandForeignToplevelHandleV1 *parent); void setTitle(const char *title); void setPid(pid_t pid); - void setIdentifier(const char *identifier); + void setIdentifier(uint32_t identifier); Q_SIGNALS: void beforeDestroy(TreeLandForeignToplevelHandleV1 *self); @@ -83,3 +86,32 @@ class TreeLandForeignToplevelHandleV1 : public QObject, public QWObject private: TreeLandForeignToplevelHandleV1(treeland_foreign_toplevel_handle_v1 *handle, bool isOwner); }; + +struct treeland_dock_preview_context_v1; +class TreeLandDockPreviewContextV1Private; +class TreeLandDockPreviewContextV1 : public QObject, public QWObject +{ + Q_OBJECT + QW_DECLARE_PRIVATE(TreeLandDockPreviewContextV1) +public: + ~TreeLandDockPreviewContextV1() = default; + + inline treeland_dock_preview_context_v1 *handle() const { + return QWObject::handle(); + } + + static TreeLandDockPreviewContextV1 *get(treeland_dock_preview_context_v1 *handle); + static TreeLandDockPreviewContextV1 *from(treeland_dock_preview_context_v1 *handle); + + void enter(); + void leave(); + +Q_SIGNALS: + void beforeDestroy(TreeLandDockPreviewContextV1 *self); + void requestShow(treeland_dock_preview_context_v1_preview_event *event); + void requestClose(); + +private: + TreeLandDockPreviewContextV1(treeland_dock_preview_context_v1 *handle, bool isOwner); +}; + diff --git a/src/treeland/protocols/foreigntoplevelmanagerv1.cpp b/src/treeland/protocols/foreigntoplevelmanagerv1.cpp index 3e2a119a..2a65c259 100644 --- a/src/treeland/protocols/foreigntoplevelmanagerv1.cpp +++ b/src/treeland/protocols/foreigntoplevelmanagerv1.cpp @@ -22,6 +22,7 @@ class TreeLandForeignToplevelManagerV1Private : public QWObjectPrivate Q_ASSERT(!map.contains(handle)); map.insert(handle, qq); sc.connect(&handle->events.destroy, this, &TreeLandForeignToplevelManagerV1Private::on_destroy); + sc.connect(&handle->events.dock_preview_created, this, &TreeLandForeignToplevelManagerV1Private::on_dock_preview_created); } ~TreeLandForeignToplevelManagerV1Private() { if (!m_handle) @@ -38,6 +39,7 @@ class TreeLandForeignToplevelManagerV1Private : public QWObjectPrivate } void on_destroy(void *); + void on_dock_preview_created(void *); static QHash map; QW_DECLARE_PUBLIC(TreeLandForeignToplevelManagerV1) @@ -52,6 +54,12 @@ void TreeLandForeignToplevelManagerV1Private::on_destroy(void *) delete q_func(); } +void TreeLandForeignToplevelManagerV1Private::on_dock_preview_created(void *data) +{ + auto *context = TreeLandDockPreviewContextV1::from(static_cast(data)); + Q_EMIT q_func()->dockPreviewContextCreated(context); +} + TreeLandForeignToplevelManagerV1::TreeLandForeignToplevelManagerV1(treeland_foreign_toplevel_manager_v1 *handle, bool isOwner) : QObject(nullptr) , QWObject(*new TreeLandForeignToplevelManagerV1Private(handle, isOwner, this)) diff --git a/src/treeland/quick/protocols/foreigntoplevelmanagerv1.cpp b/src/treeland/quick/protocols/foreigntoplevelmanagerv1.cpp index b170abc3..135b52ff 100644 --- a/src/treeland/quick/protocols/foreigntoplevelmanagerv1.cpp +++ b/src/treeland/quick/protocols/foreigntoplevelmanagerv1.cpp @@ -5,6 +5,7 @@ #include "foreign-toplevel-manager-server-protocol.h" #include "foreigntoplevelhandlev1.h" +#include "foreign_toplevel_manager_impl.h" #include #include @@ -168,7 +169,7 @@ class QuickForeignToplevelManagerV1Private : public WObjectPrivate { wl_client_get_credentials(client, &pid, nullptr, nullptr); handle->setPid(pid); - handle->setIdentifier(QString::number(reinterpret_cast(surface)).toUtf8()); + handle->setIdentifier(*reinterpret_cast(surface->handle()->handle())); Q_EMIT surface->titleChanged(); Q_EMIT surface->appIdChanged(); @@ -210,6 +211,7 @@ class QuickForeignToplevelManagerV1Private : public WObjectPrivate { TreeLandForeignToplevelManagerV1 *manager = nullptr; std::map> surfaces; std::map> connections; + std::vector dockPreviews; }; QuickForeignToplevelManagerV1::QuickForeignToplevelManagerV1(QObject *parent) @@ -233,11 +235,56 @@ void QuickForeignToplevelManagerV1::remove(WXdgSurface *surface) { d->remove(surface); } +void QuickForeignToplevelManagerV1::enterDockPreview(WSurface *relative_surface) { + W_D(QuickForeignToplevelManagerV1); + + for (auto *context : d->dockPreviews) { + if (context->handle()->relative_surface == relative_surface->handle()->handle()->resource) { + context->enter(); + break; + } + } +} + +void QuickForeignToplevelManagerV1::leaveDockPreview(WSurface *relative_surface) { + W_D(QuickForeignToplevelManagerV1); + + for (auto *context : d->dockPreviews) { + if (context->handle()->relative_surface == relative_surface->handle()->handle()->resource) { + context->leave(); + break; + } + } +} + void QuickForeignToplevelManagerV1::create() { W_D(QuickForeignToplevelManagerV1); WQuickWaylandServerInterface::create(); d->manager = TreeLandForeignToplevelManagerV1::create(server()->handle()); + + connect(d->manager, &TreeLandForeignToplevelManagerV1::dockPreviewContextCreated, this, [this, d](TreeLandDockPreviewContextV1 *context) { + d->dockPreviews.push_back(context); + connect(context, &TreeLandDockPreviewContextV1::beforeDestroy, this, [d, context] { + std::erase_if(d->dockPreviews, [context] (auto *p) { return p == context; }); + }); + connect(context, &TreeLandDockPreviewContextV1::requestShow, this, [this, d](struct treeland_dock_preview_context_v1_preview_event *event) { + std::vector surfaces; + for (auto it = event->toplevels.cbegin(); it != event->toplevels.cend(); ++it) { + const uint32_t identifier = *it; + for (auto it = d->surfaces.cbegin(); it != d->surfaces.cend(); ++it) { + if (it->second->handle()->identifier == identifier) { + surfaces.push_back(WSurface::fromHandle(it->first->handle()->handle()->surface)); + break; + } + } + }; + + Q_EMIT requestDockPreview(surfaces, WSurface::fromHandle(wlr_surface_from_resource(event->toplevel->relative_surface)), QPoint(event->x, event->y), event->direction); + }); + + connect(context, &TreeLandDockPreviewContextV1::requestClose, this, &QuickForeignToplevelManagerV1::requestDockClose); + }); } QuickForeignToplevelManagerAttached *QuickForeignToplevelManagerV1::qmlAttachedProperties(QObject *target) diff --git a/src/treeland/quick/protocols/foreigntoplevelmanagerv1.h b/src/treeland/quick/protocols/foreigntoplevelmanagerv1.h index 3d9f0a2d..f3667d70 100644 --- a/src/treeland/quick/protocols/foreigntoplevelmanagerv1.h +++ b/src/treeland/quick/protocols/foreigntoplevelmanagerv1.h @@ -94,6 +94,9 @@ class QuickForeignToplevelManagerV1 : public WQuickWaylandServerInterface, publi Q_INVOKABLE void add(WXdgSurface *surface); Q_INVOKABLE void remove(WXdgSurface *surface); + Q_INVOKABLE void enterDockPreview(WSurface *relative_surface); + Q_INVOKABLE void leaveDockPreview(WSurface *relative_surface); + static QuickForeignToplevelManagerAttached *qmlAttachedProperties(QObject *target); Q_SIGNALS: @@ -103,6 +106,8 @@ class QuickForeignToplevelManagerV1 : public WQuickWaylandServerInterface, publi void requestFullscreen(WXdgSurface *surface, treeland_foreign_toplevel_handle_v1_fullscreen_event *event); void requestClose(WXdgSurface *surface); void rectangleChanged(WXdgSurface *surface, treeland_foreign_toplevel_handle_v1_set_rectangle_event *event); + void requestDockPreview(std::vector surfaces, WSurface *target, QPoint abs, int direction); + void requestDockClose(); private: void create() override; diff --git a/src/treeland/quick/qml/CMakeLists.txt b/src/treeland/quick/qml/CMakeLists.txt index 05841738..36219c18 100644 --- a/src/treeland/quick/qml/CMakeLists.txt +++ b/src/treeland/quick/qml/CMakeLists.txt @@ -23,6 +23,7 @@ qt_add_qml_module(treeland-qml MiniDock.qml WindowsSwitcher.qml WindowsSwitcherPreview.qml + DockPreview.qml RESOURCE_PREFIX /qt/qml ) diff --git a/src/treeland/quick/qml/DockPreview.qml b/src/treeland/quick/qml/DockPreview.qml new file mode 100644 index 00000000..7b35c78d --- /dev/null +++ b/src/treeland/quick/qml/DockPreview.qml @@ -0,0 +1,183 @@ +import QtQuick +import Waylib.Server +import TreeLand.Utils + +Item { + id: root + + property alias model: model + + signal stopped + signal entered(var relativeSurface) + signal exited(var relativeSurface) + signal surfaceActivated(XdgSurface surface) + + visible: false + + property var target + property var pos + property int direction: 0 + + property int checkExited: 0 + property var isEntered: false + + function show(surfaces, target, pos, direction) { + filterModel.clear(); + for (let surface of surfaces) { + filterModel.append(surface); + } + root.pos = pos; + root.direction = direction; + root.target = target; + visible = true; + } + + function close() { + visible = false; + stopped(); + filterModel.clear(); + } + + Loader { + id: context + } + + Component { + id: contextComponent + + WindowsSwitcherPreview { + } + } + + DockPreviewFilter { + id: filterModel + sourceModel: model + } + + ListModel { + id: model + function removeSurface(surface) { + for (var i = 0; i < model.count; i++) { + if (model.get(i).source === surface) { + model.remove(i); + break; + } + } + filterModel.remove(surface.waylandSurface.surface); + } + } + + Timer { + id: exitedTimer + interval: 100 + onTriggered: { + root.exited(root.target.item.surface.surface); + } + } + + Rectangle { + width: box.width + height: box.height + x: box.x + y: box.y + + radius: 10 + opacity: 0.4 + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { + if (root.isEntered) { + root.isEntered = false; + return; + } + root.entered(root.target.item.surface.surface); + } + onExited: { + if (root.isEntered) { + return; + } + exitedTimer.start(); + } + } + } + + Column { + id: box + + x: root.direction === 0 ? root.target.shell.x + root.pos.x - width / 2 : + root.direction === 1 ? root.target.shell.x + root.pos.x - width : + root.direction === 2 ? root.target.shell.x + root.pos.x - width / 2 : + root.direction === 3 ? root.target.shell.x + root.target.shell.width : 0 + + y: root.direction === 0 ? root.target.shell.y + root.target.shell.height : + root.direction === 1 ? root.target.shell.y + root.pos.y - height / 2 : + root.direction === 2 ? root.target.shell.y - height : + root.direction === 3 ? root.target.shell.y + root.pos.y - height / 2 : 0 + + Row { + width: listView.width + height: 20 + + Text { + id: title + width: listView.width + clip: true + verticalAlignment: Text.AlignVCenter + font.pointSize: 15 + text: "Title" + } + + Image { + // icon? + width: 30 + height: 30 + } + } + + ListView { + id: listView + height: root.direction % 2 ? listView.count * 180 : 180 + width: root.direction % 2 ? 180 : listView.count * 180 + orientation: root.direction % 2 ? ListView.Vertical : ListView.Horizontal + model: filterModel + delegate: Item { + required property XdgSurface surface + width: 180 + height: 180 + clip: true + visible: true + ShaderEffectSource { + id: effect + anchors.centerIn: parent + width: listView.width - 20 + height: Math.min(surface.height * width / surface.width, width) + live: true + hideSource: false + smooth: true + sourceItem: surface + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { + root.isEntered = true; + title.text = surface.surface.title + context.parent = root.parent; + context.anchors.fill = root; + context.sourceComponent = contextComponent; + context.item.start(surface); + surfaceActivated(surface); + } + onExited: { + title.text = "Title" + } + onClicked: { + surfaceActivated(surface); + } + } + } + } + } + } +} diff --git a/src/treeland/quick/qml/LayerSurface.qml b/src/treeland/quick/qml/LayerSurface.qml index b12fb4bc..545dd865 100644 --- a/src/treeland/quick/qml/LayerSurface.qml +++ b/src/treeland/quick/qml/LayerSurface.qml @@ -17,6 +17,8 @@ Item { property bool mapped: waylandSurface.surface && waylandSurface.surface.mapped && waylandSurface.WaylandSocket.rootSocket.enabled property bool pendingDestroy: false + property alias surfaceItem: surfaceItem + id: root z: zValueFormLayer(waylandSurface.layer) diff --git a/src/treeland/quick/qml/StackToplevelHelper.qml b/src/treeland/quick/qml/StackToplevelHelper.qml index d28cd2af..a0629c43 100644 --- a/src/treeland/quick/qml/StackToplevelHelper.qml +++ b/src/treeland/quick/qml/StackToplevelHelper.qml @@ -16,6 +16,7 @@ Item { required property ToplevelSurface waylandSurface required property ListModel dockModel required property ListModel switcherModel + required property ListModel dockPreviewModel required property DynamicCreatorComponent creator property WindowDecoration decoration property var quickForeignToplevelManageMapper: waylandSurface.TreeLandForeignToplevelManagerV1 @@ -207,6 +208,7 @@ Item { } switcherModel.append({ source: surface }); + dockPreviewModel.append({ surface: surface, source: waylandSurface.surface }); } else { // if not mapped if (waylandSurface.isMinimized) { // mapped becomes false but not pendingDestroy @@ -223,6 +225,7 @@ Item { closeAnimation.item.start(surface) } switcherModel.removeSurface(surface) + dockPreviewModel.removeSurface(surface) } } @@ -230,6 +233,7 @@ Item { pendingDestroy = true switcherModel.removeSurface(surface) + dockPreviewModel.removeSurface(surface) if (!surface.visible || !closeAnimation.active) { if (waylandSurface.isMinimized) { diff --git a/src/treeland/quick/qml/StackWorkspace.qml b/src/treeland/quick/qml/StackWorkspace.qml index 388bcac8..0592c164 100644 --- a/src/treeland/quick/qml/StackWorkspace.qml +++ b/src/treeland/quick/qml/StackWorkspace.qml @@ -136,6 +136,7 @@ Item { waylandSurface: toplevelSurfaceItem.waylandSurface dockModel: dock.model switcherModel: switcher.model + dockPreviewModel: dockPreview.model creator: toplevelComponent decoration: decoration } @@ -386,6 +387,33 @@ Item { } } + Connections { + target: treelandForeignToplevelManager + function onRequestDockPreview(surfaces, target, abs, direction) { + dockPreview.show(surfaces, getSurfaceItemFromWaylandSurface(target), abs, direction) + } + function onRequestDockClose() { + dockPreview.close() + } + } + + DockPreview { + id: dockPreview + z: 1 + anchors.fill: parent + visible: false + onEntered: (relativeSurface) => { + treelandForeignToplevelManager.enterDockPreview(relativeSurface) + } + onExited: (relativeSurface) => { + treelandForeignToplevelManager.leaveDockPreview(relativeSurface) + } + onSurfaceActivated: (surface) => { + surface.cancelMinimize() + TreeLandHelper.activatedSurface = surface.waylandSurface + } + } + Connections { target: TreeLandHelper function onSwitcherChanged(mode) { diff --git a/src/treeland/quick/utils/CMakeLists.txt b/src/treeland/quick/utils/CMakeLists.txt index 2ca5c528..eae16c45 100644 --- a/src/treeland/quick/utils/CMakeLists.txt +++ b/src/treeland/quick/utils/CMakeLists.txt @@ -7,6 +7,7 @@ qt_add_qml_module(treeland-quick-utils SOURCES helper.cpp treelandhelper.cpp + dockpreviewfilter.cpp RESOURCE_PREFIX /qt/qml ) @@ -16,6 +17,7 @@ FILE_SET HEADERS FILES helper.h treelandhelper.h + dockpreviewfilter.h ) target_compile_definitions(treeland-quick-utils diff --git a/src/treeland/quick/utils/dockpreviewfilter.cpp b/src/treeland/quick/utils/dockpreviewfilter.cpp new file mode 100644 index 00000000..e0706634 --- /dev/null +++ b/src/treeland/quick/utils/dockpreviewfilter.cpp @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2023 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 "dockpreviewfilter.h" + +#include +#include + +DockPreviewFilterProxyModel::DockPreviewFilterProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + setFilterCaseSensitivity(Qt::CaseInsensitive); +} + +bool DockPreviewFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex modelIndex = this->sourceModel()->index(sourceRow, 0, sourceParent); + auto v = sourceModel()->data(modelIndex, sourceModel()->roleNames().key("source")); + + if (v.isNull()) { + return false; + } + + auto find = std::ranges::find_if(m_surfaces, [v](Waylib::Server::WSurface *s) { + return s == v.value(); + }); + + return find != m_surfaces.end(); +} + +void DockPreviewFilterProxyModel::clear() +{ + m_surfaces.clear(); + + invalidateFilter(); +} + +void DockPreviewFilterProxyModel::append(Waylib::Server::WSurface *surface) +{ + m_surfaces.push_back(surface); + + invalidateFilter(); +} + +void DockPreviewFilterProxyModel::remove(Waylib::Server::WSurface *surface) +{ + std::erase_if(m_surfaces, [surface](auto *s) { + return s == surface; + }); + + invalidateFilter(); +} diff --git a/src/treeland/quick/utils/dockpreviewfilter.h b/src/treeland/quick/utils/dockpreviewfilter.h new file mode 100644 index 00000000..f6df9fc3 --- /dev/null +++ b/src/treeland/quick/utils/dockpreviewfilter.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 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 + +#include + +class DockPreviewFilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + QML_ELEMENT + QML_NAMED_ELEMENT(DockPreviewFilter) + +public: + explicit DockPreviewFilterProxyModel(QObject *parent = nullptr); + + Q_INVOKABLE void clear(); + Q_INVOKABLE void append(Waylib::Server::WSurface *surface); + Q_INVOKABLE void remove(Waylib::Server::WSurface *surface); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + +private: + std::vector m_surfaces; +};