feat(src/window/switcher/app-capture): add app-capture implementation

This commit is contained in:
sankalp committed 2026-07-12 03:53:58 +05:45
1 parent 3ed0707b14
commit ce21c075da
11 files changed
+2322

No files matched your search

@@ -0,0 +1,90 @@
<?xml version="1.0"?>
<repository version="1.2"
xmlns="http://www.gtk.org/introspection/core/1.0"
xmlns:c="http://www.gtk.org/introspection/c/1.0"
xmlns:glib="http://www.gtk.org/introspection/glib/1.0">
<include name="GObject" version="2.0"/>
<include name="GLib" version="2.0"/>
<package name="appcapture"/>
<c:include name="app-capture.h"/>
<namespace name="AppCapture"
version="1.0"
shared-library="libappcapture.so"
c:identifier-prefixes="App"
c:symbol-prefixes="app">
<class name="Capture"
c:symbol-prefix="capture"
c:type="AppCapture"
parent="GObject.Object"
glib:type-name="AppCapture"
glib:get-type="app_capture_get_type"
glib:type-struct="CaptureClass">
<constructor name="new" c:identifier="app_capture_new">
<return-value transfer-ownership="full">
<type name="Capture" c:type="AppCapture*"/>
</return-value>
</constructor>
<!-- address accepts "0x564f60266bd0" or "564f60266bd0" -->
<method name="capture_by_handle" c:identifier="app_capture_capture_by_handle">
<parameters>
<instance-parameter name="self" transfer-ownership="none">
<type name="Capture" c:type="AppCapture*"/>
</instance-parameter>
<parameter name="address" transfer-ownership="none">
<type name="utf8" c:type="const gchar*"/>
</parameter>
</parameters>
<return-value transfer-ownership="none">
<type name="none" c:type="void"/>
</return-value>
</method>
<glib:signal name="frame-ready" when="last">
<return-value transfer-ownership="none">
<type name="none" c:type="void"/>
</return-value>
<parameters>
<parameter name="bytes" transfer-ownership="none">
<type name="GLib.Bytes" c:type="GBytes*"/>
</parameter>
<parameter name="width" transfer-ownership="none">
<type name="gint" c:type="gint"/>
</parameter>
<parameter name="height" transfer-ownership="none">
<type name="gint" c:type="gint"/>
</parameter>
<parameter name="stride" transfer-ownership="none">
<type name="gint" c:type="gint"/>
</parameter>
</parameters>
</glib:signal>
<glib:signal name="frame-failed" when="last">
<return-value transfer-ownership="none">
<type name="none" c:type="void"/>
</return-value>
<parameters>
<parameter name="reason" transfer-ownership="none">
<type name="utf8" c:type="gchararray"/>
</parameter>
</parameters>
</glib:signal>
</class>
<record name="CaptureClass"
c:type="AppCaptureClass"
glib:is-gtype-struct-for="Capture">
<field name="parent_class">
<type name="GObject.ObjectClass" c:type="GObjectClass"/>
</field>
</record>
</namespace>
</repository>
@@ -0,0 +1 @@
@@ -0,0 +1,672 @@
#define _GNU_SOURCE
#include "app-capture.h"
#include <wayland-client.h>
#include <gdk/gdkwayland.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "hyprland-toplevel-export-v1.h"
#include "wlr-foreign-toplevel-management-unstable-v1.h"
#include "hyprland-toplevel-mapping-v1.h"
/* =========================================================================
* Signals
* ========================================================================= */
enum {
SIGNAL_FRAME_READY,
SIGNAL_FRAME_FAILED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
/* How long capture_by_handle() will wait for an unknown address's wlr handle
* to arrive before giving up and emitting frame-failed. */
#define PENDING_CAPTURE_TIMEOUT_MS 1500
/* =========================================================================
* Toplevel entry — one per live wlr toplevel
* =========================================================================
* We build a table of these at init time by:
* 1. Binding zwlr_foreign_toplevel_manager_v1 and receiving toplevel events
* 2. For each handle, requesting a hyprland_toplevel_mapping to get the
* full 64-bit Hyprland address (split as hi + lo uint32s)
* This lets capture_by_handle() look up the wlr object for any address.
* ========================================================================= */
typedef struct {
struct zwlr_foreign_toplevel_handle_v1 *wlr_handle;
char address[32]; /* hex string, e.g. "564f60266bd0", no "0x" prefix */
gboolean mapped; /* false until window_address event arrives */
} ToplevelEntry;
/* =========================================================================
* Pending capture — capture_by_handle() called before mapping arrived
* ========================================================================= */
typedef struct {
AppCapture *self; /* unowned; pending queue is owned by self */
char *address; /* owned, stripped of "0x" */
guint timeout_id; /* 0 once consumed */
} PendingCapture;
/* =========================================================================
* Object struct
* ========================================================================= */
struct _AppCapture {
GObject parent_instance;
/* Wayland globals */
struct wl_display *display;
struct wl_registry *registry;
struct wl_shm *shm;
struct hyprland_toplevel_export_manager_v1 *export_manager;
struct zwlr_foreign_toplevel_manager_v1 *wlr_manager;
struct hyprland_toplevel_mapping_manager_v1 *mapping_manager;
/* Address → wlr_handle table */
GPtrArray *toplevels; /* element-type: ToplevelEntry* */
/* Captures waiting on a not-yet-mapped address */
GPtrArray *pending_captures; /* element-type: PendingCapture* */
/* Per-frame state — reset before every capture */
int shm_fd;
unsigned char *pixel_data;
uint32_t width;
uint32_t height;
uint32_t stride;
uint32_t format;
size_t shm_size;
};
G_DEFINE_TYPE(AppCapture, app_capture, G_TYPE_OBJECT)
/* =========================================================================
* Forward declarations
* ========================================================================= */
static void cleanup_shm(AppCapture *self);
static void request_mapping(AppCapture *self, ToplevelEntry *entry);
static void emit_frame_failed(AppCapture *self, const char *reason);
static void do_capture(AppCapture *self,
struct zwlr_foreign_toplevel_handle_v1 *wlr_handle);
static struct zwlr_foreign_toplevel_handle_v1 *find_wlr_handle(AppCapture *self,
const char *addr);
static void flush_pending_for_address(AppCapture *self, const char *address);
static gboolean pending_capture_timeout_cb(gpointer data);
static void pending_capture_free(gpointer data);
/* =========================================================================
* Hyprland toplevel mapping listener
* Fires once per ToplevelEntry with the full 64-bit address.
* ========================================================================= */
typedef struct {
AppCapture *self;
ToplevelEntry *entry;
} MappingContext;
static void mapping_handle_window_address(void *data,
struct hyprland_toplevel_window_mapping_handle_v1 *handle,
uint32_t address_hi, uint32_t address)
{
MappingContext *ctx = data;
ToplevelEntry *entry = ctx->entry;
AppCapture *self = ctx->self;
uint64_t full = ((uint64_t)address_hi << 32) | (uint64_t)address;
snprintf(entry->address, sizeof(entry->address), "%" PRIx64, full);
entry->mapped = TRUE;
/* Dispatch any capture that was waiting for this address */
flush_pending_for_address(self, entry->address);
hyprland_toplevel_window_mapping_handle_v1_destroy(handle);
g_free(ctx);
}
static void mapping_handle_failed(void *data,
struct hyprland_toplevel_window_mapping_handle_v1 *handle)
{
MappingContext *ctx = data;
/* Address stays empty — this entry won't be matchable, which is fine.
* Any pending capture for that address will time out via its own timer. */
hyprland_toplevel_window_mapping_handle_v1_destroy(handle);
g_free(ctx);
}
static const struct hyprland_toplevel_window_mapping_handle_v1_listener mapping_listener = {
.window_address = mapping_handle_window_address,
.failed = mapping_handle_failed,
};
/* =========================================================================
* wlr foreign toplevel listeners
* We only need enough to know when a window appears or disappears.
* ========================================================================= */
static void wlr_handle_title(void *data,
struct zwlr_foreign_toplevel_handle_v1 *handle, const char *title)
{ (void)data; (void)handle; (void)title; }
static void wlr_handle_app_id(void *data,
struct zwlr_foreign_toplevel_handle_v1 *handle, const char *app_id)
{ (void)data; (void)handle; (void)app_id; }
static void wlr_handle_output_enter(void *data,
struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *output)
{ (void)data; (void)handle; (void)output; }
static void wlr_handle_output_leave(void *data,
struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *output)
{ (void)data; (void)handle; (void)output; }
static void wlr_handle_state(void *data,
struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_array *state)
{ (void)data; (void)handle; (void)state; }
static void wlr_handle_done(void *data,
struct zwlr_foreign_toplevel_handle_v1 *handle)
{ (void)data; (void)handle; }
static void wlr_handle_closed(void *data,
struct zwlr_foreign_toplevel_handle_v1 *handle)
{
ToplevelEntry *entry = data;
/* Mark as unmapped so capture_by_handle skips it */
entry->mapped = FALSE;
zwlr_foreign_toplevel_handle_v1_destroy(handle);
entry->wlr_handle = NULL;
}
static void wlr_handle_parent(void *data,
struct zwlr_foreign_toplevel_handle_v1 *handle,
struct zwlr_foreign_toplevel_handle_v1 *parent)
{ (void)data; (void)handle; (void)parent; }
static const struct zwlr_foreign_toplevel_handle_v1_listener wlr_handle_listener = {
.title = wlr_handle_title,
.app_id = wlr_handle_app_id,
.output_enter = wlr_handle_output_enter,
.output_leave = wlr_handle_output_leave,
.state = wlr_handle_state,
.done = wlr_handle_done,
.closed = wlr_handle_closed,
.parent = wlr_handle_parent,
};
/* =========================================================================
* wlr foreign toplevel manager listener
* ========================================================================= */
static void wlr_manager_handle_toplevel(void *data,
struct zwlr_foreign_toplevel_manager_v1 *manager,
struct zwlr_foreign_toplevel_handle_v1 *handle)
{
(void)manager;
AppCapture *self = APP_CAPTURE(data);
ToplevelEntry *entry = g_new0(ToplevelEntry, 1);
entry->wlr_handle = handle;
entry->mapped = FALSE;
zwlr_foreign_toplevel_handle_v1_add_listener(handle,
&wlr_handle_listener, entry);
g_ptr_array_add(self->toplevels, entry);
/* Immediately request the Hyprland address mapping for this handle */
if (self->mapping_manager)
request_mapping(self, entry);
}
static void wlr_manager_handle_finished(void *data,
struct zwlr_foreign_toplevel_manager_v1 *manager)
{ (void)data; (void)manager; }
static const struct zwlr_foreign_toplevel_manager_v1_listener wlr_manager_listener = {
.toplevel = wlr_manager_handle_toplevel,
.finished = wlr_manager_handle_finished,
};
/* =========================================================================
* Mapping helper — ask Hyprland for the address of a wlr handle
* ========================================================================= */
static void request_mapping(AppCapture *self, ToplevelEntry *entry)
{
MappingContext *ctx = g_new0(MappingContext, 1);
ctx->self = self;
ctx->entry = entry;
struct hyprland_toplevel_window_mapping_handle_v1 *mh =
hyprland_toplevel_mapping_manager_v1_get_window_for_toplevel_wlr(
self->mapping_manager, entry->wlr_handle);
hyprland_toplevel_window_mapping_handle_v1_add_listener(mh,
&mapping_listener, ctx);
}
/* =========================================================================
* Registry listener
* ========================================================================= */
static void registry_handle_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version)
{
(void)version;
AppCapture *self = APP_CAPTURE(data);
if (g_strcmp0(interface,
hyprland_toplevel_export_manager_v1_interface.name) == 0) {
self->export_manager = wl_registry_bind(registry, name,
&hyprland_toplevel_export_manager_v1_interface, 2);
}
else if (g_strcmp0(interface, wl_shm_interface.name) == 0) {
self->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
}
else if (g_strcmp0(interface,
zwlr_foreign_toplevel_manager_v1_interface.name) == 0) {
self->wlr_manager = wl_registry_bind(registry, name,
&zwlr_foreign_toplevel_manager_v1_interface, 3);
zwlr_foreign_toplevel_manager_v1_add_listener(self->wlr_manager,
&wlr_manager_listener, self);
}
else if (g_strcmp0(interface,
hyprland_toplevel_mapping_manager_v1_interface.name) == 0) {
self->mapping_manager = wl_registry_bind(registry, name,
&hyprland_toplevel_mapping_manager_v1_interface, 1);
}
}
static void registry_handle_global_remove(void *data,
struct wl_registry *registry, uint32_t name)
{ (void)data; (void)registry; (void)name; }
static const struct wl_registry_listener registry_listener = {
.global = registry_handle_global,
.global_remove = registry_handle_global_remove,
};
/* =========================================================================
* Frame listener
*
* Event order: buffer → (linux_dmabuf) → buffer_done → [copy()] → flags → ready|failed
* ========================================================================= */
static void frame_handle_buffer(void *data,
struct hyprland_toplevel_export_frame_v1 *frame,
uint32_t format, uint32_t width, uint32_t height, uint32_t stride)
{
(void)frame;
AppCapture *self = APP_CAPTURE(data);
self->format = format;
self->width = width;
self->height = height;
self->stride = stride;
}
static void frame_handle_linux_dmabuf(void *data,
struct hyprland_toplevel_export_frame_v1 *frame,
uint32_t format, uint32_t width, uint32_t height)
{ (void)data; (void)frame; (void)format; (void)width; (void)height; }
static void frame_handle_buffer_done(void *data,
struct hyprland_toplevel_export_frame_v1 *frame)
{
AppCapture *self = APP_CAPTURE(data);
if (self->width == 0 || self->height == 0 || self->stride == 0) {
g_warning("AppCapture: buffer_done with zero dimensions");
hyprland_toplevel_export_frame_v1_destroy(frame);
emit_frame_failed(self, "buffer_invalid");
return;
}
self->shm_size = (size_t)self->stride * self->height;
self->shm_fd = memfd_create("app-capture-buffer", 0);
if (self->shm_fd < 0) {
g_warning("AppCapture: memfd_create failed");
hyprland_toplevel_export_frame_v1_destroy(frame);
emit_frame_failed(self, "alloc_failed");
return;
}
if (ftruncate(self->shm_fd, (off_t)self->shm_size) < 0) {
g_warning("AppCapture: ftruncate failed");
cleanup_shm(self);
hyprland_toplevel_export_frame_v1_destroy(frame);
emit_frame_failed(self, "alloc_failed");
return;
}
self->pixel_data = mmap(NULL, self->shm_size,
PROT_READ | PROT_WRITE, MAP_SHARED,
self->shm_fd, 0);
if (self->pixel_data == MAP_FAILED) {
g_warning("AppCapture: mmap failed");
self->pixel_data = NULL;
cleanup_shm(self);
hyprland_toplevel_export_frame_v1_destroy(frame);
emit_frame_failed(self, "alloc_failed");
return;
}
struct wl_shm_pool *pool = wl_shm_create_pool(self->shm, self->shm_fd,
(int32_t)self->shm_size);
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool,
0, (int32_t)self->width, (int32_t)self->height,
(int32_t)self->stride, self->format);
wl_shm_pool_destroy(pool);
hyprland_toplevel_export_frame_v1_copy(frame, buffer, 1);
wl_buffer_destroy(buffer);
}
static void frame_handle_flags(void *data,
struct hyprland_toplevel_export_frame_v1 *frame, uint32_t flags)
{ (void)data; (void)frame; (void)flags; }
static void frame_handle_ready(void *data,
struct hyprland_toplevel_export_frame_v1 *frame,
uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec)
{
(void)tv_sec_hi; (void)tv_sec_lo; (void)tv_nsec;
AppCapture *self = APP_CAPTURE(data);
if (!self->pixel_data) {
g_warning("AppCapture: ready fired but pixel_data is NULL");
hyprland_toplevel_export_frame_v1_destroy(frame);
emit_frame_failed(self, "internal");
return;
}
GBytes *bytes = g_bytes_new(self->pixel_data, self->shm_size);
g_signal_emit(self, signals[SIGNAL_FRAME_READY], 0,
bytes, (gint)self->width, (gint)self->height, (gint)self->stride);
g_bytes_unref(bytes);
cleanup_shm(self);
hyprland_toplevel_export_frame_v1_destroy(frame);
}
static void frame_handle_failed(void *data,
struct hyprland_toplevel_export_frame_v1 *frame)
{
AppCapture *self = APP_CAPTURE(data);
g_warning("AppCapture: frame capture failed");
cleanup_shm(self);
hyprland_toplevel_export_frame_v1_destroy(frame);
emit_frame_failed(self, "frame_failed");
}
static void frame_handle_damage(void *data,
struct hyprland_toplevel_export_frame_v1 *frame,
uint32_t x, uint32_t y, uint32_t width, uint32_t height)
{ (void)data; (void)frame; (void)x; (void)y; (void)width; (void)height; }
static const struct hyprland_toplevel_export_frame_v1_listener frame_listener = {
.buffer = frame_handle_buffer,
.linux_dmabuf = frame_handle_linux_dmabuf,
.buffer_done = frame_handle_buffer_done,
.flags = frame_handle_flags,
.ready = frame_handle_ready,
.failed = frame_handle_failed,
.damage = frame_handle_damage,
};
/* =========================================================================
* Capture helpers
* ========================================================================= */
static void emit_frame_failed(AppCapture *self, const char *reason)
{
g_signal_emit(self, signals[SIGNAL_FRAME_FAILED], 0, reason);
}
static struct zwlr_foreign_toplevel_handle_v1 *find_wlr_handle(AppCapture *self,
const char *addr)
{
for (guint i = 0; i < self->toplevels->len; i++) {
ToplevelEntry *entry = g_ptr_array_index(self->toplevels, i);
if (entry->mapped && entry->wlr_handle &&
g_strcmp0(entry->address, addr) == 0)
return entry->wlr_handle;
}
return NULL;
}
static void do_capture(AppCapture *self,
struct zwlr_foreign_toplevel_handle_v1 *wlr_handle)
{
self->width = self->height = self->stride = self->format = 0;
struct hyprland_toplevel_export_frame_v1 *frame =
hyprland_toplevel_export_manager_v1_capture_toplevel_with_wlr_toplevel_handle(
self->export_manager,
0, /* overlay_cursor */
wlr_handle
);
hyprland_toplevel_export_frame_v1_add_listener(frame, &frame_listener, self);
wl_display_flush(self->display);
}
/* =========================================================================
* Pending capture queue
*
* When capture_by_handle() is called for an address whose wlr handle hasn't
* been mapped yet (race between Hyprland IPC and wlr foreign-toplevel), we
* park a PendingCapture and wait for either:
* - mapping_handle_window_address → flush_pending_for_address → do_capture
* - PENDING_CAPTURE_TIMEOUT_MS elapses → emit frame-failed
*
* In practice the JS layer is single-flight so this queue holds ≤ 1 entry.
* ========================================================================= */
static void pending_capture_free(gpointer data)
{
PendingCapture *pc = data;
if (pc->timeout_id != 0) {
g_source_remove(pc->timeout_id);
pc->timeout_id = 0;
}
g_free(pc->address);
g_free(pc);
}
static gboolean pending_capture_timeout_cb(gpointer data)
{
PendingCapture *pc = data;
AppCapture *self = pc->self;
g_warning("AppCapture: timeout waiting for wlr handle '%s' "
"(toplevel table has %u entries)",
pc->address, self->toplevels->len);
/* Mark consumed so pending_capture_free doesn't double-remove */
pc->timeout_id = 0;
/* Remove first (frees pc), THEN emit — emit may re-enter capture_by_handle */
g_ptr_array_remove(self->pending_captures, pc);
emit_frame_failed(self, "no_handle_timeout");
return G_SOURCE_REMOVE;
}
static void flush_pending_for_address(AppCapture *self, const char *address)
{
if (self->pending_captures->len == 0) return;
struct zwlr_foreign_toplevel_handle_v1 *wlr_handle =
find_wlr_handle(self, address);
if (!wlr_handle) return;
/* Iterate backwards so removals don't shift indices we're about to visit */
for (guint i = self->pending_captures->len; i > 0; i--) {
PendingCapture *pc = g_ptr_array_index(self->pending_captures, i - 1);
if (g_strcmp0(pc->address, address) != 0) continue;
do_capture(self, wlr_handle);
g_ptr_array_remove_index(self->pending_captures, i - 1);
/* JS side is single-flight — at most one match expected.
* Break to avoid issuing two captures against the same shm state. */
break;
}
}
/* =========================================================================
* SHM helpers
* ========================================================================= */
static void cleanup_shm(AppCapture *self)
{
if (self->pixel_data && self->pixel_data != MAP_FAILED) {
munmap(self->pixel_data, self->shm_size);
self->pixel_data = NULL;
}
if (self->shm_fd >= 0) {
close(self->shm_fd);
self->shm_fd = -1;
}
self->shm_size = 0;
}
static void toplevel_entry_free(gpointer data)
{
ToplevelEntry *entry = data;
if (entry->wlr_handle)
zwlr_foreign_toplevel_handle_v1_destroy(entry->wlr_handle);
g_free(entry);
}
/* =========================================================================
* GObject lifecycle
* ========================================================================= */
static void app_capture_finalize(GObject *object)
{
AppCapture *self = APP_CAPTURE(object);
cleanup_shm(self);
g_ptr_array_unref(self->pending_captures);
g_ptr_array_unref(self->toplevels);
if (self->export_manager)
hyprland_toplevel_export_manager_v1_destroy(self->export_manager);
if (self->mapping_manager)
hyprland_toplevel_mapping_manager_v1_destroy(self->mapping_manager);
if (self->wlr_manager)
zwlr_foreign_toplevel_manager_v1_destroy(self->wlr_manager);
if (self->shm)
wl_shm_destroy(self->shm);
if (self->registry)
wl_registry_destroy(self->registry);
G_OBJECT_CLASS(app_capture_parent_class)->finalize(object);
}
static void app_capture_class_init(AppCaptureClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = app_capture_finalize;
signals[SIGNAL_FRAME_READY] = g_signal_new(
"frame-ready",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE, 4,
G_TYPE_BYTES, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT
);
signals[SIGNAL_FRAME_FAILED] = g_signal_new(
"frame-failed",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE, 1,
G_TYPE_STRING
);
}
static void app_capture_init(AppCapture *self)
{
self->shm_fd = -1;
self->pixel_data = NULL;
self->shm_size = 0;
self->toplevels = g_ptr_array_new_with_free_func(toplevel_entry_free);
self->pending_captures = g_ptr_array_new_with_free_func(pending_capture_free);
GdkDisplay *gdk_display = gdk_display_get_default();
self->display = gdk_wayland_display_get_wl_display(gdk_display);
self->registry = wl_display_get_registry(self->display);
wl_registry_add_listener(self->registry, &registry_listener, self);
/* First roundtrip: compositor advertises globals, we bind them all */
wl_display_roundtrip(self->display);
/* Second roundtrip: wlr_manager emits toplevel events for existing
* windows, and mapping requests are sent for each one */
wl_display_roundtrip(self->display);
/* Third roundtrip: mapping responses (window_address events) arrive */
wl_display_roundtrip(self->display);
if (!self->export_manager)
g_warning("AppCapture: hyprland_toplevel_export_manager_v1 not found");
if (!self->wlr_manager)
g_warning("AppCapture: zwlr_foreign_toplevel_manager_v1 not found");
if (!self->mapping_manager)
g_warning("AppCapture: hyprland_toplevel_mapping_manager_v1 not found");
if (!self->shm)
g_warning("AppCapture: wl_shm not found");
}
/* =========================================================================
* Public API
* ========================================================================= */
AppCapture *app_capture_new(void)
{
return g_object_new(APP_TYPE_CAPTURE, NULL);
}
void app_capture_capture_by_handle(AppCapture *self, const gchar *address)
{
g_return_if_fail(APP_IS_CAPTURE(self));
g_return_if_fail(address != NULL);
if (!self->export_manager) {
g_warning("AppCapture: export_manager not available");
emit_frame_failed(self, "no_export_manager");
return;
}
/* Strip optional "0x" prefix so matching works regardless of input */
const gchar *addr = address;
if (g_str_has_prefix(addr, "0x") || g_str_has_prefix(addr, "0X"))
addr += 2;
/* Fast path: handle already known and mapped */
struct zwlr_foreign_toplevel_handle_v1 *wlr_handle =
find_wlr_handle(self, addr);
if (wlr_handle) {
do_capture(self, wlr_handle);
return;
}
/* Slow path: park and wait up to PENDING_CAPTURE_TIMEOUT_MS for the
* wlr_manager toplevel/mapping events to arrive. Race window is
* typically <100 ms in practice but can spike under load. */
PendingCapture *pc = g_new0(PendingCapture, 1);
pc->self = self;
pc->address = g_strdup(addr);
pc->timeout_id = g_timeout_add(PENDING_CAPTURE_TIMEOUT_MS,
pending_capture_timeout_cb, pc);
g_ptr_array_add(self->pending_captures, pc);
}
@@ -0,0 +1,43 @@
#pragma once
#include <glib-object.h>
G_BEGIN_DECLS
#define APP_TYPE_CAPTURE (app_capture_get_type())
G_DECLARE_FINAL_TYPE(AppCapture, app_capture, APP, CAPTURE, GObject)
/**
* app_capture_new:
*
* Returns: (transfer full): a new #AppCapture.
* Binds hyprland_toplevel_export_manager_v1, zwlr_foreign_toplevel_manager_v1,
* and hyprland_toplevel_mapping_manager_v1, then does three roundtrips to
* enumerate all live windows and map their addresses.
*/
AppCapture *app_capture_new(void);
/**
* app_capture_capture_by_handle:
* @self: an #AppCapture
* @address: (transfer none): the Hyprland window address as a string.
* Accepts both "0x564f60266bd0" and "564f60266bd0" (with or without 0x).
*
* Captures one frame of the window. Every call eventually emits exactly one of:
* - #AppCapture::frame-ready (GBytes, gint, gint, gint) on success
* - #AppCapture::frame-failed (gchararray reason) on failure
*
* If the wlr handle for @address has not yet been announced/mapped (race
* between Hyprland IPC and the wlr foreign-toplevel protocol), the request
* is queued internally for up to ~1.5 s waiting for the mapping to arrive.
*
* Failure reasons emitted via frame-failed:
* "no_export_manager" — wayland init never bound the export protocol
* "no_handle_timeout" — wlr handle never appeared for this address
* "frame_failed" — compositor refused the export (unmapped, hidden, …)
* "buffer_invalid" — buffer event reported zero dimensions
* "alloc_failed" — memfd_create / ftruncate / mmap failed
* "internal" — defensive fallback (should never fire)
*/
void app_capture_capture_by_handle(AppCapture *self, const gchar *address);
G_END_DECLS
+110
View File
@@ -0,0 +1,110 @@
project('app-capture', 'c',
version: '1.0',
default_options: ['c_std=c11', 'warning_level=1']
)
# ─── Dependencies ─────────────────────────────────────────────────────────────
wayland_client = dependency('wayland-client')
wayland_scanner_dep = dependency('wayland-scanner', native: true)
gobject = dependency('gobject-2.0')
gtk3 = dependency('gtk+-3.0')
gir = dependency('gobject-introspection-1.0', required: true)
wayland_scanner = find_program('wayland-scanner')
# ─── Protocol XML files ───────────────────────────────────────────────────────
# All must exist under protocols/ in the source tree:
#
# hyprland-toplevel-export-v1.xml (hyprwm/hyprland-protocols)
# wlr-foreign-toplevel-management-unstable-v1.xml (swaywm/wlr-protocols)
# hyprland-toplevel-mapping-v1.xml (hyprwm/hyprland-protocols)
# ext-foreign-toplevel-list-v1.xml (wayland-protocols staging)
#
hypr_export_xml = files('protocols/hyprland-toplevel-export-v1.xml')
wlr_toplevel_xml = files('protocols/wlr-foreign-toplevel-management-unstable-v1.xml')
hypr_mapping_xml = files('protocols/hyprland-toplevel-mapping-v1.xml')
ext_toplevel_xml = files('protocols/ext-foreign-toplevel-list-v1.xml')
# ─── Generate C bindings ──────────────────────────────────────────────────────
# hyprland-toplevel-export: the capture protocol we call directly
hypr_export_client_h = custom_target('hypr-export-client-h',
input: hypr_export_xml, output: 'hyprland-toplevel-export-v1.h',
command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'],
)
hypr_export_private_c = custom_target('hypr-export-private-c',
input: hypr_export_xml, output: 'hyprland-toplevel-export-v1.c',
command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
)
# wlr-foreign-toplevel: used to enumerate live windows and pass handles to the
# export protocol's v2 request; also provides zwlr_foreign_toplevel_handle_v1_interface
# which hyprland-toplevel-export-v1.c references at link time.
wlr_toplevel_client_h = custom_target('wlr-toplevel-client-h',
input: wlr_toplevel_xml, output: 'wlr-foreign-toplevel-management-unstable-v1.h',
command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'],
)
wlr_toplevel_private_c = custom_target('wlr-toplevel-private-c',
input: wlr_toplevel_xml, output: 'wlr-foreign-toplevel-management-unstable-v1.c',
command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
)
# hyprland-toplevel-mapping: maps wlr handles → full 64-bit Hyprland addresses.
# Its generated .c references ext_foreign_toplevel_handle_v1_interface (below).
hypr_mapping_client_h = custom_target('hypr-mapping-client-h',
input: hypr_mapping_xml, output: 'hyprland-toplevel-mapping-v1.h',
command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'],
)
hypr_mapping_private_c = custom_target('hypr-mapping-private-c',
input: hypr_mapping_xml, output: 'hyprland-toplevel-mapping-v1.c',
command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
)
# ext-foreign-toplevel-list: we never call it directly, but its private-code
# provides ext_foreign_toplevel_handle_v1_interface which the mapping protocol's
# generated .c references at link time.
ext_toplevel_private_c = custom_target('ext-toplevel-private-c',
input: ext_toplevel_xml, output: 'ext-foreign-toplevel-list-v1.c',
command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
)
# ─── Shared library ───────────────────────────────────────────────────────────
lib = shared_library('appcapture',
sources: [
'app-capture.c',
hypr_export_private_c,
hypr_export_client_h,
wlr_toplevel_private_c,
wlr_toplevel_client_h,
hypr_mapping_private_c,
hypr_mapping_client_h,
ext_toplevel_private_c, # link-time symbol only, no header needed in our code
],
include_directories: [
include_directories('.'),
],
dependencies: [
wayland_client,
gobject,
gtk3,
],
gnu_symbol_visibility: 'default',
install: true,
)
# ─── GObject Introspection ────────────────────────────────────────────────────
gir_file = files('AppCapture-1.0.gir')
install_data(gir_file,
install_dir: get_option('datadir') / 'gir-1.0',
)
gir_compiler = find_program('g-ir-compiler')
custom_target('appcapture-typelib',
input: gir_file,
output: 'AppCapture-1.0.typelib',
command: [gir_compiler, '--output=@OUTPUT@', '@INPUT@'],
install: true,
install_dir: get_option('libdir') / 'girepository-1.0',
)
@@ -0,0 +1,219 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="ext_foreign_toplevel_list_v1">
<copyright>
Copyright © 2018 Ilia Bozhinov
Copyright © 2020 Isaac Freund
Copyright © 2022 wb9688
Copyright © 2023 i509VCB
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<description summary="list toplevels">
The purpose of this protocol is to provide protocol object handles for
toplevels, possibly originating from another client.
This protocol is intentionally minimalistic and expects additional
functionality (e.g. creating a screencopy source from a toplevel handle,
getting information about the state of the toplevel) to be implemented
in extension protocols.
The compositor may choose to restrict this protocol to a special client
launched by the compositor itself or expose it to all clients,
this is compositor policy.
The key words "must", "must not", "required", "shall", "shall not",
"should", "should not", "recommended", "may", and "optional" in this
document are to be interpreted as described in IETF RFC 2119.
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.
</description>
<interface name="ext_foreign_toplevel_list_v1" version="1">
<description summary="list toplevels">
A toplevel is defined as a surface with a role similar to xdg_toplevel.
XWayland surfaces may be treated like toplevels in this protocol.
After a client binds the ext_foreign_toplevel_list_v1, each mapped
toplevel window will be sent using the ext_foreign_toplevel_list_v1.toplevel
event.
Clients which only care about the current state can perform a roundtrip after
binding this global.
For each instance of ext_foreign_toplevel_list_v1, the compositor must
create a new ext_foreign_toplevel_handle_v1 object for each mapped toplevel.
If a compositor implementation sends the ext_foreign_toplevel_list_v1.finished
event after the global is bound, the compositor must not send any
ext_foreign_toplevel_list_v1.toplevel events.
</description>
<event name="toplevel">
<description summary="a toplevel has been created">
This event is emitted whenever a new toplevel window is created. It is
emitted for all toplevels, regardless of the app that has created them.
All initial properties of the toplevel (identifier, title, app_id) will be sent
immediately after this event using the corresponding events for
ext_foreign_toplevel_handle_v1. The compositor will use the
ext_foreign_toplevel_handle_v1.done event to indicate when all data has
been sent.
</description>
<arg name="toplevel" type="new_id" interface="ext_foreign_toplevel_handle_v1"/>
</event>
<event name="finished">
<description summary="the compositor has finished with the toplevel manager">
This event indicates that the compositor is done sending events
to this object. The client should destroy the object.
See ext_foreign_toplevel_list_v1.destroy for more information.
The compositor must not send any more toplevel events after this event.
</description>
</event>
<request name="stop">
<description summary="stop sending events">
This request indicates that the client no longer wishes to receive
events for new toplevels.
The Wayland protocol is asynchronous, meaning the compositor may send
further toplevel events until the stop request is processed.
The client should wait for a ext_foreign_toplevel_list_v1.finished
event before destroying this object.
</description>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the ext_foreign_toplevel_list_v1 object">
This request should be called either when the client will no longer
use the ext_foreign_toplevel_list_v1 or after the finished event
has been received to allow destruction of the object.
If a client wishes to destroy this object it should send a
ext_foreign_toplevel_list_v1.stop request and wait for a ext_foreign_toplevel_list_v1.finished
event, then destroy the handles and then this object.
</description>
</request>
</interface>
<interface name="ext_foreign_toplevel_handle_v1" version="1">
<description summary="a mapped toplevel">
A ext_foreign_toplevel_handle_v1 object represents a mapped toplevel
window. A single app may have multiple mapped toplevels.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the ext_foreign_toplevel_handle_v1 object">
This request should be used when the client will no longer use the handle
or after the closed event has been received to allow destruction of the
object.
When a handle is destroyed, a new handle may not be created by the server
until the toplevel is unmapped and then remapped. Destroying a toplevel handle
is not recommended unless the client is cleaning up child objects
before destroying the ext_foreign_toplevel_list_v1 object, the toplevel
was closed or the toplevel handle will not be used in the future.
Other protocols which extend the ext_foreign_toplevel_handle_v1
interface should require destructors for extension interfaces be
called before allowing the toplevel handle to be destroyed.
</description>
</request>
<event name="closed">
<description summary="the toplevel has been closed">
The server will emit no further events on the ext_foreign_toplevel_handle_v1
after this event. Any requests received aside from the destroy request must
be ignored. Upon receiving this event, the client should destroy the handle.
Other protocols which extend the ext_foreign_toplevel_handle_v1
interface must also ignore requests other than destructors.
</description>
</event>
<event name="done">
<description summary="all information about the toplevel has been sent">
This event is sent after all changes in the toplevel state have
been sent.
This allows changes to the ext_foreign_toplevel_handle_v1 properties
to be atomically applied. Other protocols which extend the
ext_foreign_toplevel_handle_v1 interface may use this event to also
atomically apply any pending state.
This event must not be sent after the ext_foreign_toplevel_handle_v1.closed
event.
</description>
</event>
<event name="title">
<description summary="title change">
The title of the toplevel has changed.
The configured state must not be applied immediately. See
ext_foreign_toplevel_handle_v1.done for details.
</description>
<arg name="title" type="string"/>
</event>
<event name="app_id">
<description summary="app_id change">
The app id of the toplevel has changed.
The configured state must not be applied immediately. See
ext_foreign_toplevel_handle_v1.done for details.
</description>
<arg name="app_id" type="string"/>
</event>
<event name="identifier">
<description summary="a stable identifier for a toplevel">
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 its 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.
</description>
<arg name="identifier" type="string"/>
</event>
</interface>
</protocol>
@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="ext_image_capture_source_v1">
<copyright>
Copyright © 2022 Andri Yngvason
Copyright © 2024 Simon Ser
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<description summary="opaque image capture source objects">
This protocol serves as an intermediary between capturing protocols and
potential image capture sources such as outputs and toplevels.
This protocol may be extended to support more image capture sources in the
future, thereby adding those image capture sources to other protocols that
use the image capture source object without having to modify those
protocols.
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.
</description>
<interface name="ext_image_capture_source_v1" version="1">
<description summary="opaque image capture source object">
The image capture source object is an opaque descriptor for a capturable
resource. This resource may be any sort of entity from which an image
may be derived.
Note, because ext_image_capture_source_v1 objects are created from multiple
independent factory interfaces, the ext_image_capture_source_v1 interface is
frozen at version 1.
</description>
<request name="destroy" type="destructor">
<description summary="delete this object">
Destroys the image capture source. This request may be sent at any time
by the client.
</description>
</request>
</interface>
<interface name="ext_output_image_capture_source_manager_v1" version="1">
<description summary="image capture source manager for outputs">
A manager for creating image capture source objects for wl_output objects.
</description>
<request name="create_source">
<description summary="create source object for output">
Creates a source object for an output. Images captured from this source
will show the same content as the output. Some elements may be omitted,
such as cursors and overlays that have been marked as transparent to
capturing.
</description>
<arg name="source" type="new_id" interface="ext_image_capture_source_v1"/>
<arg name="output" type="object" interface="wl_output"/>
</request>
<request name="destroy" type="destructor">
<description summary="delete this object">
Destroys the manager. This request may be sent at any time by the client
and objects created by the manager will remain valid after its
destruction.
</description>
</request>
</interface>
<interface name="ext_foreign_toplevel_image_capture_source_manager_v1" version="1">
<description summary="image capture source manager for foreign toplevels">
A manager for creating image capture source objects for
ext_foreign_toplevel_handle_v1 objects.
</description>
<request name="create_source">
<description summary="create source object for foreign toplevel">
Creates a source object for a foreign toplevel handle. Images captured
from this source will show the same content as the toplevel.
</description>
<arg name="source" type="new_id" interface="ext_image_capture_source_v1"/>
<arg name="toplevel_handle" type="object" interface="ext_foreign_toplevel_handle_v1"/>
</request>
<request name="destroy" type="destructor">
<description summary="delete this object">
Destroys the manager. This request may be sent at any time by the client
and objects created by the manager will remain valid after its
destruction.
</description>
</request>
</interface>
</protocol>
@@ -0,0 +1,456 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="ext_image_copy_capture_v1">
<copyright>
Copyright © 2021-2023 Andri Yngvason
Copyright © 2024 Simon Ser
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<description summary="image capturing into client buffers">
This protocol allows clients to ask the compositor to capture image sources
such as outputs and toplevels into user submitted buffers.
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.
</description>
<interface name="ext_image_copy_capture_manager_v1" version="1">
<description summary="manager to inform clients and begin capturing">
This object is a manager which offers requests to start capturing from a
source.
</description>
<enum name="error">
<entry name="invalid_option" value="1" summary="invalid option flag"/>
</enum>
<enum name="options" bitfield="true">
<entry name="paint_cursors" value="1" summary="paint cursors onto captured frames"/>
</enum>
<request name="create_session">
<description summary="capture an image capture source">
Create a capturing session for an image capture source.
If the paint_cursors option is set, cursors shall be composited onto
the captured frame. The cursor must not be composited onto the frame
if this flag is not set.
If the options bitfield is invalid, the invalid_option protocol error
is sent.
</description>
<arg name="session" type="new_id" interface="ext_image_copy_capture_session_v1"/>
<arg name="source" type="object" interface="ext_image_capture_source_v1"/>
<arg name="options" type="uint" enum="options"/>
</request>
<request name="create_pointer_cursor_session">
<description summary="capture the pointer cursor of an image capture source">
Create a cursor capturing session for the pointer of an image capture
source.
</description>
<arg name="session" type="new_id" interface="ext_image_copy_capture_cursor_session_v1"/>
<arg name="source" type="object" interface="ext_image_capture_source_v1"/>
<arg name="pointer" type="object" interface="wl_pointer"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the manager">
Destroy the manager object.
Other objects created via this interface are unaffected.
</description>
</request>
</interface>
<interface name="ext_image_copy_capture_session_v1" version="1">
<description summary="image copy capture session">
This object represents an active image copy capture session.
After a capture session is created, buffer constraint events will be
emitted from the compositor to tell the client which buffer types and
formats are supported for reading from the session. The compositor may
re-send buffer constraint events whenever they change.
To advertise buffer constraints, the compositor must send in no
particular order: zero or more shm_format and dmabuf_format events, zero
or one dmabuf_device event, and exactly one buffer_size event. Then the
compositor must send a done event.
When the client has received all the buffer constraints, it can create a
buffer accordingly, attach it to the capture session using the
attach_buffer request, set the buffer damage using the damage_buffer
request and then send the capture request.
</description>
<enum name="error">
<entry name="duplicate_frame" value="1"
summary="create_frame sent before destroying previous frame"/>
</enum>
<event name="buffer_size">
<description summary="image capture source dimensions">
Provides the dimensions of the source image in buffer pixel coordinates.
The client must attach buffers that match this size.
</description>
<arg name="width" type="uint" summary="buffer width"/>
<arg name="height" type="uint" summary="buffer height"/>
</event>
<event name="shm_format">
<description summary="shm buffer format">
Provides the format that must be used for shared-memory buffers.
This event may be emitted multiple times, in which case the client may
choose any given format.
</description>
<arg name="format" type="uint" enum="wl_shm.format" summary="shm format"/>
</event>
<event name="dmabuf_device">
<description summary="dma-buf device">
This event advertises the device buffers must be allocated on for
dma-buf buffers.
In general the device is a DRM node. The DRM node type (primary vs.
render) is unspecified. Clients must not rely on the compositor sending
a particular node type. Clients cannot check two devices for equality
by comparing the dev_t value.
</description>
<arg name="device" type="array" summary="device dev_t value"/>
</event>
<event name="dmabuf_format">
<description summary="dma-buf format">
Provides the format that must be used for dma-buf buffers.
The client may choose any of the modifiers advertised in the array of
64-bit unsigned integers.
This event may be emitted multiple times, in which case the client may
choose any given format.
</description>
<arg name="format" type="uint" summary="drm format code"/>
<arg name="modifiers" type="array" summary="drm format modifiers"/>
</event>
<event name="done">
<description summary="all constraints have been sent">
This event is sent once when all buffer constraint events have been
sent.
The compositor must always end a batch of buffer constraint events with
this event, regardless of whether it sends the initial constraints or
an update.
</description>
</event>
<event name="stopped">
<description summary="session is no longer available">
This event indicates that the capture session has stopped and is no
longer available. This can happen in a number of cases, e.g. when the
underlying source is destroyed, if the user decides to end the image
capture, or if an unrecoverable runtime error has occurred.
The client should destroy the session after receiving this event.
</description>
</event>
<request name="create_frame">
<description summary="create a frame">
Create a capture frame for this session.
At most one frame object can exist for a given session at any time. If
a client sends a create_frame request before a previous frame object
has been destroyed, the duplicate_frame protocol error is raised.
</description>
<arg name="frame" type="new_id" interface="ext_image_copy_capture_frame_v1"/>
</request>
<request name="destroy" type="destructor">
<description summary="delete this object">
Destroys the session. This request can be sent at any time by the
client.
This request doesn't affect ext_image_copy_capture_frame_v1 objects created by
this object.
</description>
</request>
</interface>
<interface name="ext_image_copy_capture_frame_v1" version="1">
<description summary="image capture frame">
This object represents an image capture frame.
The client should attach a buffer, damage the buffer, and then send a
capture request.
If the capture is successful, the compositor must send the frame metadata
(transform, damage, presentation_time in any order) followed by the ready
event.
If the capture fails, the compositor must send the failed event.
</description>
<enum name="error">
<entry name="no_buffer" value="1" summary="capture sent without attach_buffer"/>
<entry name="invalid_buffer_damage" value="2" summary="invalid buffer damage"/>
<entry name="already_captured" value="3" summary="capture request has been sent"/>
</enum>
<request name="destroy" type="destructor">
<description summary="destroy this object">
Destroys the frame. This request can be sent at any time by the
client.
</description>
</request>
<request name="attach_buffer">
<description summary="attach buffer to session">
Attach a buffer to the session.
The wl_buffer.release request is unused.
The new buffer replaces any previously attached buffer.
This request must not be sent after capture, or else the
already_captured protocol error is raised.
</description>
<arg name="buffer" type="object" interface="wl_buffer"/>
</request>
<request name="damage_buffer">
<description summary="damage buffer">
Apply damage to the buffer which is to be captured next. This request
may be sent multiple times to describe a region.
The client indicates the accumulated damage since this wl_buffer was
last captured. During capture, the compositor will update the buffer
with at least the union of the region passed by the client and the
region advertised by ext_image_copy_capture_frame_v1.damage.
When a wl_buffer is captured for the first time, or when the client
doesn't track damage, the client must damage the whole buffer.
This is for optimisation purposes. The compositor may use this
information to reduce copying.
These coordinates originate from the upper left corner of the buffer.
If x or y are strictly negative, or if width or height are negative or
zero, the invalid_buffer_damage protocol error is raised.
This request must not be sent after capture, or else the
already_captured protocol error is raised.
</description>
<arg name="x" type="int" summary="region x coordinate"/>
<arg name="y" type="int" summary="region y coordinate"/>
<arg name="width" type="int" summary="region width"/>
<arg name="height" type="int" summary="region height"/>
</request>
<request name="capture">
<description summary="capture a frame">
Capture a frame.
Unless this is the first successful captured frame performed in this
session, the compositor may wait an indefinite amount of time for the
source content to change before performing the copy.
This request may only be sent once, or else the already_captured
protocol error is raised. A buffer must be attached before this request
is sent, or else the no_buffer protocol error is raised.
</description>
</request>
<event name="transform">
<description summary="buffer transform">
This event is sent before the ready event and holds the transform that
the compositor has applied to the buffer contents.
</description>
<arg name="transform" type="uint" enum="wl_output.transform"/>
</event>
<event name="damage">
<description summary="buffer damaged region">
This event is sent before the ready event. It may be generated multiple
times to describe a region.
The first captured frame in a session will always carry full damage.
Subsequent frames' damaged regions describe which parts of the buffer
have changed since the last ready event.
These coordinates originate in the upper left corner of the buffer.
</description>
<arg name="x" type="int" summary="damage x coordinate"/>
<arg name="y" type="int" summary="damage y coordinate"/>
<arg name="width" type="int" summary="damage width"/>
<arg name="height" type="int" summary="damage height"/>
</event>
<event name="presentation_time">
<description summary="presentation time of the frame">
This event indicates the time at which the frame is presented to the
output in system monotonic time. This event is sent before the ready
event.
The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples,
each component being an unsigned 32-bit value. Whole seconds are in
tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo,
and the additional fractional part in tv_nsec as nanoseconds. Hence,
for valid timestamps tv_nsec must be in [0, 999999999].
</description>
<arg name="tv_sec_hi" type="uint"
summary="high 32 bits of the seconds part of the timestamp"/>
<arg name="tv_sec_lo" type="uint"
summary="low 32 bits of the seconds part of the timestamp"/>
<arg name="tv_nsec" type="uint"
summary="nanoseconds part of the timestamp"/>
</event>
<event name="ready">
<description summary="frame is available for reading">
Called as soon as the frame is copied, indicating it is available
for reading.
The buffer may be re-used by the client after this event.
After receiving this event, the client must destroy the object.
</description>
</event>
<enum name="failure_reason">
<entry name="unknown" value="0">
<description summary="unknown runtime error">
An unspecified runtime error has occurred. The client may retry.
</description>
</entry>
<entry name="buffer_constraints" value="1">
<description summary="buffer constraints mismatch">
The buffer submitted by the client doesn't match the latest session
constraints. The client should re-allocate its buffers and retry.
</description>
</entry>
<entry name="stopped" value="2">
<description summary="session is no longer available">
The session has stopped. See ext_image_copy_capture_session_v1.stopped.
</description>
</entry>
</enum>
<event name="failed">
<description summary="capture failed">
This event indicates that the attempted frame copy has failed.
After receiving this event, the client must destroy the object.
</description>
<arg name="reason" type="uint" enum="failure_reason"/>
</event>
</interface>
<interface name="ext_image_copy_capture_cursor_session_v1" version="1">
<description summary="cursor capture session">
This object represents a cursor capture session. It extends the base
capture session with cursor-specific metadata.
</description>
<enum name="error">
<entry name="duplicate_session" value="1" summary="get_capture_session sent twice"/>
</enum>
<request name="destroy" type="destructor">
<description summary="delete this object">
Destroys the session. This request can be sent at any time by the
client.
This request doesn't affect ext_image_copy_capture_frame_v1 objects created by
this object.
</description>
</request>
<request name="get_capture_session">
<description summary="get image copy capturer session">
Gets the image copy capture session for this cursor session.
The session will produce frames of the cursor image. The compositor may
pause the session when the cursor leaves the captured area.
This request must not be sent more than once, or else the
duplicate_session protocol error is raised.
</description>
<arg name="session" type="new_id" interface="ext_image_copy_capture_session_v1"/>
</request>
<event name="enter">
<description summary="cursor entered captured area">
Sent when a cursor enters the captured area. It shall be generated
before the "position" and "hotspot" events when and only when a cursor
enters the area.
The cursor enters the captured area when the cursor image intersects
with the captured area. Note, this is different from e.g.
wl_pointer.enter.
</description>
</event>
<event name="leave">
<description summary="cursor left captured area">
Sent when a cursor leaves the captured area. No "position" or "hotspot"
event is generated for the cursor until the cursor enters the captured
area again.
</description>
</event>
<event name="position">
<description summary="position changed">
Cursors outside the image capture source do not get captured and no
event will be generated for them.
The given position is the position of the cursor's hotspot and it is
relative to the main buffer's top left corner in transformed buffer
pixel coordinates. The coordinates may be negative or greater than the
main buffer size.
</description>
<arg name="x" type="int" summary="position x coordinates"/>
<arg name="y" type="int" summary="position y coordinates"/>
</event>
<event name="hotspot">
<description summary="hotspot changed">
The hotspot describes the offset between the cursor image and the
position of the input device.
The given coordinates are the hotspot's offset from the origin in
buffer coordinates.
Clients should not apply the hotspot immediately: the hotspot becomes
effective when the next ext_image_copy_capture_frame_v1.ready event is received.
Compositors may delay this event until the client captures a new frame.
</description>
<arg name="x" type="int" summary="hotspot x coordinates"/>
<arg name="y" type="int" summary="hotspot y coordinates"/>
</event>
</interface>
</protocol>
@@ -0,0 +1,228 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="hyprland_toplevel_export_v1">
<copyright>
Copyright © 2022 Vaxry
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</copyright>
<description summary="capturing the contents of toplevel windows">
This protocol allows clients to ask for exporting another toplevel's
surface(s) to a buffer.
Particularly useful for sharing a single window.
</description>
<interface name="hyprland_toplevel_export_manager_v1" version="2">
<description summary="manager to inform clients and begin capturing">
This object is a manager which offers requests to start capturing from a
source.
</description>
<request name="capture_toplevel">
<description summary="capture a toplevel">
Capture the next frame of a toplevel. (window)
The captured frame will not contain any server-side decorations and will
ignore the compositor-set geometry, like e.g. rounded corners.
It will contain all the subsurfaces and popups, however the latter will be clipped
to the geometry of the base surface.
The handle parameter refers to the address of the window as seen in `hyprctl clients`.
For example, for d161e7b0 it would be 3512854448.
</description>
<arg name="frame" type="new_id" interface="hyprland_toplevel_export_frame_v1"/>
<arg name="overlay_cursor" type="int"
summary="composite cursor onto the frame"/>
<arg name="handle" type="uint" summary="the handle of the toplevel (window) to be captured"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the manager">
All objects created by the manager will still remain valid, until their
appropriate destroy request has been called.
</description>
</request>
<!-- Version 2 -->
<request name="capture_toplevel_with_wlr_toplevel_handle" since="2">
<description summary="capture a toplevel">
Same as capture_toplevel, but with a zwlr_foreign_toplevel_handle_v1 handle.
</description>
<arg name="frame" type="new_id" interface="hyprland_toplevel_export_frame_v1"/>
<arg name="overlay_cursor" type="int"
summary="composite cursor onto the frame"/>
<arg name="handle" type="object" interface="zwlr_foreign_toplevel_handle_v1" allow-null="false" summary="the zwlr_foreign_toplevel_handle_v1 handle of the toplevel to be captured"/>
</request>
<!-- End Version 2 -->
</interface>
<interface name="hyprland_toplevel_export_frame_v1" version="2">
<description summary="a frame ready for copy">
This object represents a single frame.
When created, a series of buffer events will be sent, each representing a
supported buffer type. The "buffer_done" event is sent afterwards to
indicate that all supported buffer types have been enumerated. The client
will then be able to send a "copy" request. If the capture is successful,
the compositor will send a "flags" followed by a "ready" event.
wl_shm buffers are always supported, ie. the "buffer" event is guaranteed to be sent.
If the capture failed, the "failed" event is sent. This can happen anytime
before the "ready" event.
Once either a "ready" or a "failed" event is received, the client should
destroy the frame.
</description>
<event name="buffer">
<description summary="wl_shm buffer information">
Provides information about wl_shm buffer parameters that need to be
used for this frame. This event is sent once after the frame is created
if wl_shm buffers are supported.
</description>
<arg name="format" type="uint" enum="wl_shm.format" summary="buffer format"/>
<arg name="width" type="uint" summary="buffer width"/>
<arg name="height" type="uint" summary="buffer height"/>
<arg name="stride" type="uint" summary="buffer stride"/>
</event>
<request name="copy">
<description summary="copy the frame">
Copy the frame to the supplied buffer. The buffer must have the
correct size, see hyprland_toplevel_export_frame_v1.buffer and
hyprland_toplevel_export_frame_v1.linux_dmabuf. The buffer needs to have a
supported format.
If the frame is successfully copied, a "flags" and a "ready" event is
sent. Otherwise, a "failed" event is sent.
This event will wait for appropriate damage to be copied, unless the ignore_damage
arg is set to a non-zero value.
</description>
<arg name="buffer" type="object" interface="wl_buffer"/>
<arg name="ignore_damage" type="int"/>
</request>
<event name="damage">
<description summary="carries the coordinates of the damaged region">
This event is sent right before the ready event when ignore_damage was
not set. It may be generated multiple times for each copy
request.
The arguments describe a box around an area that has changed since the
last copy request that was derived from the current screencopy manager
instance.
The union of all regions received between the call to copy
and a ready event is the total damage since the prior ready event.
</description>
<arg name="x" type="uint" summary="damaged x coordinates"/>
<arg name="y" type="uint" summary="damaged y coordinates"/>
<arg name="width" type="uint" summary="current width"/>
<arg name="height" type="uint" summary="current height"/>
</event>
<enum name="error">
<entry name="already_used" value="0"
summary="the object has already been used to copy a wl_buffer"/>
<entry name="invalid_buffer" value="1"
summary="buffer attributes are invalid"/>
</enum>
<enum name="flags" bitfield="true">
<entry name="y_invert" value="1" summary="contents are y-inverted"/>
</enum>
<event name="flags">
<description summary="frame flags">
Provides flags about the frame. This event is sent once before the
"ready" event.
</description>
<arg name="flags" type="uint" enum="flags" summary="frame flags"/>
</event>
<event name="ready">
<description summary="indicates frame is available for reading">
Called as soon as the frame is copied, indicating it is available
for reading. This event includes the time at which presentation happened
at.
The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples,
each component being an unsigned 32-bit value. Whole seconds are in
tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo,
and the additional fractional part in tv_nsec as nanoseconds. Hence,
for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part
may have an arbitrary offset at start.
After receiving this event, the client should destroy the object.
</description>
<arg name="tv_sec_hi" type="uint"
summary="high 32 bits of the seconds part of the timestamp"/>
<arg name="tv_sec_lo" type="uint"
summary="low 32 bits of the seconds part of the timestamp"/>
<arg name="tv_nsec" type="uint"
summary="nanoseconds part of the timestamp"/>
</event>
<event name="failed">
<description summary="frame copy failed">
This event indicates that the attempted frame copy has failed.
After receiving this event, the client should destroy the object.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="delete this object, used or not">
Destroys the frame. This request can be sent at any time by the client.
</description>
</request>
<event name="linux_dmabuf">
<description summary="linux-dmabuf buffer information">
Provides information about linux-dmabuf buffer parameters that need to
be used for this frame. This event is sent once after the frame is
created if linux-dmabuf buffers are supported.
</description>
<arg name="format" type="uint" summary="fourcc pixel format"/>
<arg name="width" type="uint" summary="buffer width"/>
<arg name="height" type="uint" summary="buffer height"/>
</event>
<event name="buffer_done">
<description summary="all buffer types reported">
This event is sent once after all buffer events have been sent.
The client should proceed to create a buffer of one of the supported
types, and send a "copy" request.
</description>
</event>
</interface>
</protocol>
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="hyprland_toplevel_mapping_v1">
<copyright>
Copyright © 2025 WhySoBad
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</copyright>
<description summary="mapping of toplevels to windows">
This protocol allows clients to retrieve the mapping of toplevels to hyprland window addresses.
</description>
<interface name="hyprland_toplevel_mapping_manager_v1" version="1">
<description summary="manager to request toplevel mappings">
This object is a manager which offers requests to retrieve a window address
for a toplevel.
</description>
<request name="get_window_for_toplevel">
<description summary="get window address for toplevel">
Get the window address for a toplevel.
</description>
<arg
name="handle"
type="new_id"
interface="hyprland_toplevel_window_mapping_handle_v1"
/>
<arg
name="toplevel"
type="object"
interface="ext_foreign_toplevel_handle_v1"
summary="toplevel to get the window address for"
/>
</request>
<request name="get_window_for_toplevel_wlr">
<description summary="get window address for wlr toplevel">
Get the window address for a wlr toplevel.
</description>
<arg
name="handle"
type="new_id"
interface="hyprland_toplevel_window_mapping_handle_v1"
/>
<arg
name="toplevel"
type="object"
interface="zwlr_foreign_toplevel_handle_v1"
summary="wlr toplevel to get the window address for"
/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the manager">
All objects created by the manager will still remain valid, until their appropriate destroy
request has been called.
</description>
</request>
</interface>
<interface name="hyprland_toplevel_window_mapping_handle_v1" version="1">
<description summary="toplevel to window mapping">
This object represents a mapping of a (wlr) toplevel to a window address.
Once created, the `window_address` event will be sent containing the address of the window
associated with the toplevel.
Should the mapping fail, the `failed` event will be sent.
</description>
<event name="window_address">
<description summary="address of the window">
The full 64bit window address. The `address` field contains the lower 32 bits whilst the
`address_hi` contains the upper 32 bits
</description>
<arg
name="address_hi"
type="uint"
summary="upper 32 bits of the window address"
/>
<arg
name="address"
type="uint"
summary="lower 32 bits of the window address"
/>
</event>
<event name="failed">
<description summary="mapping failed">
The mapping of the toplevel to a window address failed. Most likely the window does not
exist (anymore).
</description>
</event>
<request name="destroy" type="destructor">
<description summary="destroy handle">
Destroy the handle. This request can be sent at any time by the client.
</description>
</request>
</interface>
</protocol>
@@ -0,0 +1,270 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_foreign_toplevel_management_unstable_v1">
<copyright>
Copyright © 2018 Ilia Bozhinov
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<interface name="zwlr_foreign_toplevel_manager_v1" version="3">
<description summary="list and control opened apps">
The purpose of this protocol is to enable the creation of taskbars
and docks by providing them with a list of opened applications and
letting them request certain actions on them, like maximizing, etc.
After a client binds the zwlr_foreign_toplevel_manager_v1, each opened
toplevel window will be sent via the toplevel event
</description>
<event name="toplevel">
<description summary="a toplevel has been created">
This event is emitted whenever a new toplevel window is created. It
is emitted for all toplevels, regardless of the app that has created
them.
All initial details of the toplevel(title, app_id, states, etc.) will
be sent immediately after this event via the corresponding events in
zwlr_foreign_toplevel_handle_v1.
</description>
<arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/>
</event>
<request name="stop">
<description summary="stop sending events">
Indicates the client no longer wishes to receive events for new toplevels.
However the compositor may emit further toplevel_created events, until
the finished event is emitted.
The client must not send any more requests after this one.
</description>
</request>
<event name="finished" type="destructor">
<description summary="the compositor has finished with the toplevel manager">
This event indicates that the compositor is done sending events to the
zwlr_foreign_toplevel_manager_v1. The server will destroy the object
immediately after sending this request, so it will become invalid and
the client should free any resources associated with it.
</description>
</event>
</interface>
<interface name="zwlr_foreign_toplevel_handle_v1" version="3">
<description summary="an opened toplevel">
A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel
window. Each app may have multiple opened toplevels.
Each toplevel has a list of outputs it is visible on, conveyed to the
client with the output_enter and output_leave events.
</description>
<event name="title">
<description summary="title change">
This event is emitted whenever the title of the toplevel changes.
</description>
<arg name="title" type="string"/>
</event>
<event name="app_id">
<description summary="app-id change">
This event is emitted whenever the app-id of the toplevel changes.
</description>
<arg name="app_id" type="string"/>
</event>
<event name="output_enter">
<description summary="toplevel entered an output">
This event is emitted whenever the toplevel becomes visible on
the given output. A toplevel may be visible on multiple outputs.
</description>
<arg name="output" type="object" interface="wl_output"/>
</event>
<event name="output_leave">
<description summary="toplevel left an output">
This event is emitted whenever the toplevel stops being visible on
the given output. It is guaranteed that an entered-output event
with the same output has been emitted before this event.
</description>
<arg name="output" type="object" interface="wl_output"/>
</event>
<request name="set_maximized">
<description summary="requests that the toplevel be maximized">
Requests that the toplevel be maximized. If the maximized state actually
changes, this will be indicated by the state event.
</description>
</request>
<request name="unset_maximized">
<description summary="requests that the toplevel be unmaximized">
Requests that the toplevel be unmaximized. If the maximized state actually
changes, this will be indicated by the state event.
</description>
</request>
<request name="set_minimized">
<description summary="requests that the toplevel be minimized">
Requests that the toplevel be minimized. If the minimized state actually
changes, this will be indicated by the state event.
</description>
</request>
<request name="unset_minimized">
<description summary="requests that the toplevel be unminimized">
Requests that the toplevel be unminimized. If the minimized state actually
changes, this will be indicated by the state event.
</description>
</request>
<request name="activate">
<description summary="activate the toplevel">
Request that this toplevel be activated on the given seat.
There is no guarantee the toplevel will be actually activated.
</description>
<arg name="seat" type="object" interface="wl_seat"/>
</request>
<enum name="state">
<description summary="types of states on the toplevel">
The different states that a toplevel can have. These have the same meaning
as the states with the same names defined in xdg-toplevel
</description>
<entry name="maximized" value="0" summary="the toplevel is maximized"/>
<entry name="minimized" value="1" summary="the toplevel is minimized"/>
<entry name="activated" value="2" summary="the toplevel is active"/>
<entry name="fullscreen" value="3" summary="the toplevel is fullscreen" since="2"/>
</enum>
<event name="state">
<description summary="the toplevel state changed">
This event is emitted immediately after the zlw_foreign_toplevel_handle_v1
is created and each time the toplevel state changes, either because of a
compositor action or because of a request in this protocol.
</description>
<arg name="state" type="array"/>
</event>
<event name="done">
<description summary="all information about the toplevel has been sent">
This event is sent after all changes in the toplevel state have been
sent.
This allows changes to the zwlr_foreign_toplevel_handle_v1 properties
to be seen as atomic, even if they happen via multiple events.
</description>
</event>
<request name="close">
<description summary="request that the toplevel be closed">
Send a request to the toplevel to close itself. The compositor would
typically use a shell-specific method to carry out this request, for
example by sending the xdg_toplevel.close event. However, this gives
no guarantees the toplevel will actually be destroyed. If and when
this happens, the zwlr_foreign_toplevel_handle_v1.closed event will
be emitted.
</description>
</request>
<request name="set_rectangle">
<description summary="the rectangle which represents the toplevel">
The rectangle of the surface specified in this request corresponds to
the place where the app using this protocol represents the given toplevel.
It can be used by the compositor as a hint for some operations, e.g
minimizing. The client is however not required to set this, in which
case the compositor is free to decide some default value.
If the client specifies more than one rectangle, only the last one is
considered.
The dimensions are given in surface-local coordinates.
Setting width=height=0 removes the already-set rectangle.
</description>
<arg name="surface" type="object" interface="wl_surface"/>
<arg name="x" type="int"/>
<arg name="y" type="int"/>
<arg name="width" type="int"/>
<arg name="height" type="int"/>
</request>
<enum name="error">
<entry name="invalid_rectangle" value="0"
summary="the provided rectangle is invalid"/>
</enum>
<event name="closed">
<description summary="this toplevel has been destroyed">
This event means the toplevel has been destroyed. It is guaranteed there
won't be any more events for this zwlr_foreign_toplevel_handle_v1. The
toplevel itself becomes inert so any requests will be ignored except the
destroy request.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="destroy the zwlr_foreign_toplevel_handle_v1 object">
Destroys the zwlr_foreign_toplevel_handle_v1 object.
This request should be called either when the client does not want to
use the toplevel anymore or after the closed event to finalize the
destruction of the object.
</description>
</request>
<!-- Version 2 additions -->
<request name="set_fullscreen" since="2">
<description summary="request that the toplevel be fullscreened">
Requests that the toplevel be fullscreened on the given output. If the
fullscreen state and/or the outputs the toplevel is visible on actually
change, this will be indicated by the state and output_enter/leave
events.
The output parameter is only a hint to the compositor. Also, if output
is NULL, the compositor should decide which output the toplevel will be
fullscreened on, if at all.
</description>
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
</request>
<request name="unset_fullscreen" since="2">
<description summary="request that the toplevel be unfullscreened">
Requests that the toplevel be unfullscreened. If the fullscreen state
actually changes, this will be indicated by the state event.
</description>
</request>
<!-- Version 3 additions -->
<event name="parent" since="3">
<description summary="parent change">
This event is emitted whenever the parent of the toplevel changes.
No event is emitted when the parent handle is destroyed by the client.
</description>
<arg name="parent" type="object" interface="zwlr_foreign_toplevel_handle_v1" allow-null="true"/>
</event>
</interface>
</protocol>