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;
+};