diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..183e13e --- /dev/null +++ b/.clang-format @@ -0,0 +1,20 @@ +BasedOnStyle: Google +IndentWidth: 4 +UseTab: Never + +BreakBeforeBraces: Attach +BinPackParameters: false +BinPackArguments: false +ColumnLimit: 0 + +AlignAfterOpenBracket: BlockIndent + +AllowShortIfStatementsOnASingleLine: false +IndentCaseLabels: false +AccessModifierOffset: -4 +NamespaceIndentation: All + +# Override Google style defaults +DerivePointerAlignment: false +PointerAlignment: Left + diff --git a/examples/simple/simple.c b/examples/simple/simple.c index 056f698..403bded 100644 --- a/examples/simple/simple.c +++ b/examples/simple/simple.c @@ -1,5 +1,5 @@ -/* cc simple.c `pkg-config --libs --cflags libglace` */ -#include +/* cc simple.c `pkg-config --libs --cflags glace` */ +#include static void on_client_chagend(GlaceClient* client) { printf( diff --git a/glace/buffer-utils.h b/glace/buffer-utils.h new file mode 100644 index 0000000..67322d5 --- /dev/null +++ b/glace/buffer-utils.h @@ -0,0 +1,100 @@ +#include +#include +#include + +#if defined(__SSSE3__) +#include +#elif defined(__SSE2__) +#include +#endif +#if defined(__AVX2__) +#include +#endif + +static inline void buffer_utils_bgrx_to_rgbx(unsigned char* restrict data, long pixels) { +#if defined(__AVX2__) + // 8 pixels (32 bytes) per iteration + long i = 0; + long n = pixels & ~7UL; + + const __m256i shuffle_mask = _mm256_setr_epi8( + 2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15, 18, 17, 16, 19, 22, 21, 20, 23, 26, 25, 24, 27, 30, 29, 28, 31 + ); + + for (; i < n; i += 8) { + __m256i px = _mm256_loadu_si256((__m256i*)(data + i * 4)); + px = _mm256_shuffle_epi8(px, shuffle_mask); + _mm256_storeu_si256((__m256i*)(data + i * 4), px); + } + + // leftovers + for (; i < pixels; i++) { + unsigned char pixel = data[i]; + data[i] = (pixel & 0xFF00FF00) | ((pixel << 16) & 0x00FF0000) | ((pixel >> 16) & 0xFF); + } +#elif defined(__SSSE3__) + // 4 pixels (16 bytes) per iteration + long i = 0; + long n = pixels & ~3UL; + + const __m128i shuffle_mask = _mm_setr_epi8( + 2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15 + ); + + for (; i < n; i += 4) { + __m128i px = _mm_loadu_si128((__m128i*)(data + i * 4)); + px = _mm_shuffle_epi8(px, shuffle_mask); + _mm_storeu_si128((__m128i*)(data + i * 4), px); + } + + for (; i < pixels; i++) { + unsigned char pixel = data[i]; + data[i] = (pixel & 0xFF00FF00) | ((pixel << 16) & 0x00FF0000) | ((pixel >> 16) & 0xFF); + } +#elif defined(__SSE2__) + // 4 pixels (16 bytes) per iteration (shift & mask) + long i = 0; + long n = pixels & ~3UL; + + __m128i mask_rb = _mm_set1_epi32(0x00FF00FF); // R and B + __m128i mask_gx = _mm_set1_epi32(0xFF00FF00); // G and X + + for (; i < n; i += 4) { + __m128i px = _mm_loadu_si128((__m128i*)(data + i * 4)); + + __m128i rb = _mm_and_si128(px, mask_rb); + __m128i gx = _mm_and_si128(px, mask_gx); + + __m128i r = _mm_slli_epi32(rb, 16); + __m128i b = _mm_srli_epi32(rb, 16); + + __m128i swapped = _mm_or_si128(gx, _mm_or_si128(r, b)); + _mm_storeu_si128((__m128i*)(data + i * 4), swapped); + } + + for (; i < pixels; i++) { + unsigned char pixel = data[i]; + data[i] = (pixel & 0xFF00FF00) | ((pixel << 16) & 0x00FF0000) | ((pixel >> 16) & 0xFF); + } + +#else + for (long i = 0; i < pixels; i++) { + unsigned char pixel = data[i]; + + data[i] = (pixel & 0xFF00FF00) | ((pixel << 16) & 0x00FF0000) | ((pixel >> 16) & 0xFF); + } +#endif +} + +static void buffer_utils_log_available_accelerator() { + const char* prefix = "[INFO][BUFFER]"; +#if defined(__AVX2__) + g_debug("%s using AVX2 for buffer format conversion acceleration", prefix); +#elif defined(__SSSE3__) + g_debug("%s using SSSE3 for buffer format conversion acceleration", prefix); +#elif defined(__SSE2__) + g_debug("%s using SSE2 for buffer format conversion acceleration", prefix); +#else + g_debug("%s compiled with no buffer conversion acceleration", prefix); +#endif +} \ No newline at end of file diff --git a/glace/glace-client-private.h b/glace/glace-client-private.h new file mode 100644 index 0000000..a44a898 --- /dev/null +++ b/glace/glace-client-private.h @@ -0,0 +1,63 @@ +#pragma once + +#ifndef __LIBGLACE_CLIENT_PRIVATE_H__ +#define __LIBGLACE_CLIENT_PRIVATE_H__ + +#include "glace-client.h" +#include "glace-manager-private.h" + +#define CLIENT_SET_CURRENT_PROP(client, prop, value) \ + (client->priv->current_properties.prop = value) + +#define CLIENT_SET_PENDING_PROP(client, prop, value) \ + (client->priv->pending_properties.prop = value) + +#define CLIENT_GET_CURRENT_PROP(client, prop) \ + (client->priv->current_properties.prop) + +#define CLIENT_GET_PENDING_PROP(client, prop) \ + (client->priv->pending_properties.prop) + +#define CLIENT_BRIDGE_PROPS(client, prop, PROP_UPPER) \ + do { \ + if (CLIENT_GET_CURRENT_PROP(client, prop) != CLIENT_GET_PENDING_PROP(client, prop)) { \ + CLIENT_GET_CURRENT_PROP(client, prop) = CLIENT_GET_PENDING_PROP(client, prop); \ + g_object_notify_by_pspec( \ + G_OBJECT(client), \ + glace_client_properties[GLACE_CLIENT_PROPERTY_##PROP_UPPER] \ + ); \ + } \ + } while (0) + +#define CLIENT_SET_STATE_FOR_CASE(client, state, ENUM_M, prop) \ + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_##ENUM_M: \ + CLIENT_SET_PENDING_PROP(client, prop, true); \ + break; + +#define CLIENT_SET_DEFAULT_STATES(client) \ + CLIENT_SET_PENDING_PROP(client, maximized, false); \ + CLIENT_SET_PENDING_PROP(client, minimized, false); \ + CLIENT_SET_PENDING_PROP(client, activated, false); \ + CLIENT_SET_PENDING_PROP(client, fullscreen, false); + +#define IF_INVALID_CLIENT(client) if (client == NULL || GLACE_IS_CLIENT(client) == false || client->priv->closed == true) + +#define RETURN_IF_INVALID_CLIENT(client, r_value) \ + do { \ + IF_INVALID_CLIENT(client) { \ + g_warning("[WARNING][CLIENT] function %s got an invalid client", __func__); \ + return r_value; \ + } \ + } while (0) + +// signal emitters +// static void glace_client_signal_changed_emit(GlaceClient* self); +// static void glace_client_signal_close_emit(GlaceClient* self); + +// methods +// static void glace_client_init(GlaceClient* self); +// static void glace_client_class_init(GlaceClientClass* klass); + +GlaceClient* glace_client_new(struct zwlr_foreign_toplevel_handle_v1* wlr_handle, struct hyprland_toplevel_mapping_manager_v1* hl_mapping_manager, GdkWaylandDisplay* gdk_display); + +#endif /* __LIBGLACE_CLIENT_PRIVATE_H__ */ diff --git a/libglace/libglace-client-private.c b/glace/glace-client.c similarity index 50% rename from libglace/libglace-client-private.c rename to glace/glace-client.c index 22112df..91af9de 100644 --- a/libglace/libglace-client-private.c +++ b/glace/glace-client.c @@ -1,7 +1,17 @@ -#include "libglace-private.h" +#include "glace-private.h" static guint glace_client_signals[GLACE_CLIENT_N_SIGNALS] = {0}; -static GParamSpec* glace_client_properties[GLACE_CLIENT_N_PROPERTIES] = {NULL,}; +static GParamSpec* glace_client_properties[GLACE_CLIENT_N_PROPERTIES] = { + NULL, +}; + +static void on_mapping_handle_window_address(void* user_data, struct hyprland_toplevel_window_mapping_handle_v1* handle, uint32_t address_hi, uint32_t address); +static void on_mapping_handle_failed(void* user_data, struct hyprland_toplevel_window_mapping_handle_v1* handle); + +static const struct hyprland_toplevel_window_mapping_handle_v1_listener mapping_handle_listener = { + .window_address = &on_mapping_handle_window_address, + .failed = &on_mapping_handle_failed, +}; static void glace_client_signal_changed_emit(GlaceClient* self) { g_signal_emit( @@ -19,7 +29,6 @@ static void glace_client_signal_close_emit(GlaceClient* self) { ); } - static void glace_client_get_property( GObject* object, guint prop_id, @@ -29,37 +38,39 @@ static void glace_client_get_property( GlaceClient* self = GLACE_CLIENT(object); switch (prop_id) { - case GLACE_CLIENT_PROPERTY_ID: - g_value_set_uint(value, (guint)self->priv->id); - break; - case GLACE_CLIENT_PROPERTY_APP_ID: - g_value_set_string(value, CLIENT_GET_CURRENT_PROP(self, app_id)); - break; - case GLACE_CLIENT_PROPERTY_TITLE: - g_value_set_string(value, CLIENT_GET_CURRENT_PROP(self, title)); - break; - case GLACE_CLIENT_PROPERTY_MAXIMIZED: - g_value_set_boolean(value, CLIENT_GET_CURRENT_PROP(self, maximized)); - break; - case GLACE_CLIENT_PROPERTY_MINIMIZED: - g_value_set_boolean(value, CLIENT_GET_CURRENT_PROP(self, minimized)); - break; - case GLACE_CLIENT_PROPERTY_ACTIVATED: - g_value_set_boolean(value, CLIENT_GET_CURRENT_PROP(self, activated)); - break; - case GLACE_CLIENT_PROPERTY_FULLSCREEN: - g_value_set_boolean(value, CLIENT_GET_CURRENT_PROP(self, fullscreen)); - break; - case GLACE_CLIENT_PROPERTY_CLOSED: - g_value_set_boolean(value, self->priv->closed); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); - break; + case GLACE_CLIENT_PROPERTY_ID: + g_value_set_uint(value, (guint)self->priv->id); + break; + case GLACE_CLIENT_PROPERTY_APP_ID: + g_value_set_string(value, CLIENT_GET_CURRENT_PROP(self, app_id)); + break; + case GLACE_CLIENT_PROPERTY_TITLE: + g_value_set_string(value, CLIENT_GET_CURRENT_PROP(self, title)); + break; + case GLACE_CLIENT_PROPERTY_MAXIMIZED: + g_value_set_boolean(value, CLIENT_GET_CURRENT_PROP(self, maximized)); + break; + case GLACE_CLIENT_PROPERTY_MINIMIZED: + g_value_set_boolean(value, CLIENT_GET_CURRENT_PROP(self, minimized)); + break; + case GLACE_CLIENT_PROPERTY_ACTIVATED: + g_value_set_boolean(value, CLIENT_GET_CURRENT_PROP(self, activated)); + break; + case GLACE_CLIENT_PROPERTY_FULLSCREEN: + g_value_set_boolean(value, CLIENT_GET_CURRENT_PROP(self, fullscreen)); + break; + case GLACE_CLIENT_PROPERTY_CLOSED: + g_value_set_boolean(value, self->priv->closed); + break; + case GLACE_CLIENT_PROPERTY_HYPRLAND_ADDRESS: + g_value_set_uint64(value, self->priv->hyprland_address); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; } } - // client event handlers static void on_toplevel_handle_title( void* data, @@ -71,7 +82,6 @@ static void on_toplevel_handle_title( CLIENT_SET_PENDING_PROP(self, title, (gchar*)(strdup(title))); } - static void on_toplevel_handle_app_id( void* data, struct zwlr_foreign_toplevel_handle_v1* wlr_handle, @@ -89,14 +99,12 @@ static void on_toplevel_handle_output_enter( struct wl_output* output ) {} - static void on_toplevel_handle_output_leave( void* data, struct zwlr_foreign_toplevel_handle_v1* wlr_handle, struct wl_output* output ) {} - static void on_toplevel_handle_state( void* data, struct zwlr_foreign_toplevel_handle_v1* wlr_handle, @@ -108,7 +116,7 @@ static void on_toplevel_handle_state( enum zwlr_foreign_toplevel_handle_v1_state* state; wl_array_for_each(state, states) { - switch (*state) { + switch (*state) { CLIENT_SET_STATE_FOR_CASE(self, state, MAXIMIZED, maximized); CLIENT_SET_STATE_FOR_CASE(self, state, MINIMIZED, minimized); CLIENT_SET_STATE_FOR_CASE(self, state, ACTIVATED, activated); @@ -117,7 +125,6 @@ static void on_toplevel_handle_state( } } - static void on_toplevel_handle_done( void* data, struct zwlr_foreign_toplevel_handle_v1* wlr_handle @@ -136,7 +143,6 @@ static void on_toplevel_handle_done( free(CLIENT_GET_CURRENT_PROP(self, app_id)); } - if (CLIENT_GET_PENDING_PROP(self, title)) { CLIENT_BRIDGE_PROPS(self, title, TITLE); CLIENT_SET_PENDING_PROP(self, title, NULL); @@ -156,7 +162,6 @@ static void on_toplevel_handle_done( glace_client_signal_changed_emit(self); } - static void on_toplevel_handle_closed( void* data, struct zwlr_foreign_toplevel_handle_v1* wlr_handle @@ -168,14 +173,12 @@ static void on_toplevel_handle_closed( glace_client_signal_close_emit(self); } - static void on_toplevel_handle_parent( void* data, struct zwlr_foreign_toplevel_handle_v1* wlr_handle, struct zwlr_foreign_toplevel_handle_v1* parent ) {} - static struct zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_listener = { .title = &on_toplevel_handle_title, .app_id = &on_toplevel_handle_app_id, @@ -187,7 +190,6 @@ static struct zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_listener .parent = &on_toplevel_handle_parent }; - static void glace_client_class_init(GlaceClientClass* klass) { GObjectClass* parent_class = G_OBJECT_CLASS(klass); @@ -231,9 +233,7 @@ static void glace_client_class_init(GlaceClientClass* klass) { 0 ); - glace_client_properties[ - GLACE_CLIENT_PROPERTY_ID - ] = g_param_spec_uint( + glace_client_properties[GLACE_CLIENT_PROPERTY_ID] = g_param_spec_uint( "id", "id", "the id of the client", @@ -243,9 +243,7 @@ static void glace_client_class_init(GlaceClientClass* klass) { G_PARAM_READABLE ); - glace_client_properties[ - GLACE_CLIENT_PROPERTY_APP_ID - ] = g_param_spec_string( + glace_client_properties[GLACE_CLIENT_PROPERTY_APP_ID] = g_param_spec_string( "app-id", "class", "the application id of the client (class name under X11)", @@ -253,9 +251,7 @@ static void glace_client_class_init(GlaceClientClass* klass) { G_PARAM_READABLE ); - glace_client_properties[ - GLACE_CLIENT_PROPERTY_TITLE - ] = g_param_spec_string( + glace_client_properties[GLACE_CLIENT_PROPERTY_TITLE] = g_param_spec_string( "title", "title", "the current title of the client", @@ -264,9 +260,7 @@ static void glace_client_class_init(GlaceClientClass* klass) { ); // state properties - glace_client_properties[ - GLACE_CLIENT_PROPERTY_MAXIMIZED - ] = g_param_spec_boolean( + glace_client_properties[GLACE_CLIENT_PROPERTY_MAXIMIZED] = g_param_spec_boolean( "maximized", "maximized", "whether this client is currently maximized or not", @@ -274,9 +268,7 @@ static void glace_client_class_init(GlaceClientClass* klass) { G_PARAM_READABLE ); - glace_client_properties[ - GLACE_CLIENT_PROPERTY_MINIMIZED - ] = g_param_spec_boolean( + glace_client_properties[GLACE_CLIENT_PROPERTY_MINIMIZED] = g_param_spec_boolean( "minimized", "minimized", "whether this client is currently minimized or not", @@ -284,9 +276,7 @@ static void glace_client_class_init(GlaceClientClass* klass) { G_PARAM_READABLE ); - glace_client_properties[ - GLACE_CLIENT_PROPERTY_ACTIVATED - ] = g_param_spec_boolean( + glace_client_properties[GLACE_CLIENT_PROPERTY_ACTIVATED] = g_param_spec_boolean( "activated", "focused", "whether this client is currently activated (focused) or not", @@ -294,17 +284,11 @@ static void glace_client_class_init(GlaceClientClass* klass) { G_PARAM_READABLE ); - glace_client_properties[ - GLACE_CLIENT_PROPERTY_FULLSCREEN - ] = g_param_spec_boolean( - "fullscreen", "fullscreen", "whether this client is currently in a fullscreen state or not", - false, - G_PARAM_READABLE + glace_client_properties[GLACE_CLIENT_PROPERTY_FULLSCREEN] = g_param_spec_boolean( + "fullscreen", "fullscreen", "whether this client is currently in a fullscreen state or not", false, G_PARAM_READABLE ); - glace_client_properties[ - GLACE_CLIENT_PROPERTY_CLOSED - ] = g_param_spec_boolean( + glace_client_properties[GLACE_CLIENT_PROPERTY_CLOSED] = g_param_spec_boolean( "closed", "closed", "whether this client is closed (killed) or not, it's guaranteed that you won't receive events for this client after it gets closed", @@ -312,6 +296,16 @@ static void glace_client_class_init(GlaceClientClass* klass) { G_PARAM_READABLE ); + glace_client_properties[GLACE_CLIENT_PROPERTY_HYPRLAND_ADDRESS] = g_param_spec_uint64( + "hyprland-address", + "hyprland-address", + "the internal window address for hyprland in 64bits", + 0, + G_MAXUINT64, + 0, + G_PARAM_READABLE + ); + g_object_class_install_properties( parent_class, GLACE_CLIENT_N_PROPERTIES, @@ -319,7 +313,6 @@ static void glace_client_class_init(GlaceClientClass* klass) { ); } - static void glace_client_init(GlaceClient* self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE( self, @@ -328,6 +321,7 @@ static void glace_client_init(GlaceClient* self) { ); self->priv->closed = false; + self->priv->hyprland_address = 0; CLIENT_SET_CURRENT_PROP(self, app_id, false); CLIENT_SET_CURRENT_PROP(self, title, false); CLIENT_SET_CURRENT_PROP(self, maximized, false); @@ -336,9 +330,41 @@ static void glace_client_init(GlaceClient* self) { CLIENT_SET_CURRENT_PROP(self, fullscreen, false); } +static void on_mapping_handle_window_address( + void* user_data, + struct hyprland_toplevel_window_mapping_handle_v1* handle, + // that's how you know a protocol is good + uint32_t address_hi, + uint32_t address_lo +) { + GlaceClient* self = GLACE_CLIENT(user_data); + RETURN_IF_INVALID_CLIENT(self, hyprland_toplevel_window_mapping_handle_v1_destroy(handle)); + + self->priv->hyprland_address = ((guint64)address_hi << 32) | address_lo; + + // we have a proper address now, notify the user... + g_object_notify_by_pspec( + G_OBJECT(self), + glace_client_properties[GLACE_CLIENT_PROPERTY_HYPRLAND_ADDRESS] + ); + + glace_client_signal_changed_emit(self); + + hyprland_toplevel_window_mapping_handle_v1_destroy(handle); +} + +static void on_mapping_handle_failed( + void* user_data, + struct hyprland_toplevel_window_mapping_handle_v1* handle +) { + // something's fucked up, cleanup meanwhile + hyprland_toplevel_window_mapping_handle_v1_destroy(handle); + return; +} GlaceClient* glace_client_new( struct zwlr_foreign_toplevel_handle_v1* wlr_handle, + struct hyprland_toplevel_mapping_manager_v1* hl_mapping_manager, GdkWaylandDisplay* gdk_display ) { GlaceClient* self = g_object_new(GLACE_TYPE_CLIENT, NULL); @@ -350,8 +376,145 @@ GlaceClient* glace_client_new( &toplevel_handle_listener, self ); + + if (!hl_mapping_manager) + return self; + + struct hyprland_toplevel_window_mapping_handle_v1* + handle = hyprland_toplevel_mapping_manager_v1_get_window_for_toplevel_wlr( + hl_mapping_manager, + self->priv->wlr_handle + ); + + hyprland_toplevel_window_mapping_handle_v1_add_listener( + handle, + &mapping_handle_listener, + self // user_data + ); + return self; } +// client getters +guint glace_client_get_id(GlaceClient* self) { + return (guint)self->priv->id; +} + +guint64 glace_client_get_hyprland_address(GlaceClient* self) { + return (guint64)self->priv->hyprland_address; +} + +const gchar* glace_client_get_app_id(GlaceClient* self) { + return CLIENT_GET_CURRENT_PROP(self, app_id); +} + +const gchar* glace_client_get_title(GlaceClient* self) { + return CLIENT_GET_CURRENT_PROP(self, title); +} + +gboolean glace_client_get_maximized(GlaceClient* self) { + return CLIENT_GET_CURRENT_PROP(self, maximized); +} + +gboolean glace_client_get_minimized(GlaceClient* self) { + return CLIENT_GET_CURRENT_PROP(self, minimized); +} + +gboolean glace_client_get_activated(GlaceClient* self) { + return CLIENT_GET_CURRENT_PROP(self, activated); +} + +gboolean glace_client_get_fullscreen(GlaceClient* self) { + return CLIENT_GET_CURRENT_PROP(self, fullscreen); +} + +gboolean glace_client_get_closed(GlaceClient* self) { + return self->priv->closed; +} + +// client methods +void glace_client_maximize(GlaceClient* self) { + // replacing EMPTY_TOKEN with a trailing comma + // will do the trick as well + RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); + + zwlr_foreign_toplevel_handle_v1_set_maximized(self->priv->wlr_handle); +} + +void glace_client_unmaximize(GlaceClient* self) { + RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); + + zwlr_foreign_toplevel_handle_v1_unset_maximized(self->priv->wlr_handle); +} + +void glace_client_minimize(GlaceClient* self) { + RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); + + zwlr_foreign_toplevel_handle_v1_set_minimized(self->priv->wlr_handle); +} + +void glace_client_unminimize(GlaceClient* self) { + RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); + + zwlr_foreign_toplevel_handle_v1_unset_minimized(self->priv->wlr_handle); +} +void glace_client_close(GlaceClient* self) { + RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); + + zwlr_foreign_toplevel_handle_v1_close(self->priv->wlr_handle); +} + +void glace_client_activate(GlaceClient* self) { + RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); + + GdkSeat* gdk_seat = gdk_display_get_default_seat(self->priv->gdk_display); + struct wl_seat* seat = gdk_wayland_seat_get_wl_seat(gdk_seat); + + zwlr_foreign_toplevel_handle_v1_activate( + self->priv->wlr_handle, + seat + ); +} + +void glace_client_move( + GlaceClient* self, + GdkWindow* window, + const GdkRectangle* rectangle +) { + RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); + + if (window == NULL) { + zwlr_foreign_toplevel_handle_v1_set_rectangle( + self->priv->wlr_handle, + NULL, + rectangle->x, + rectangle->y, + rectangle->width, + rectangle->height + ); + return; + } + + zwlr_foreign_toplevel_handle_v1_set_rectangle( + self->priv->wlr_handle, + gdk_wayland_window_get_wl_surface(window), + rectangle->x, + rectangle->y, + rectangle->width, + rectangle->height + ); +} + +void glace_client_fullscreen(GlaceClient* self) { + RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); + + zwlr_foreign_toplevel_handle_v1_set_fullscreen(self->priv->wlr_handle, self->priv->output); +} + +void glace_client_unfullscreen(GlaceClient* self) { + RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); + + zwlr_foreign_toplevel_handle_v1_unset_fullscreen(self->priv->wlr_handle); +} G_DEFINE_TYPE(GlaceClient, glace_client, G_TYPE_OBJECT); diff --git a/libglace/libglace-client.h b/glace/glace-client.h similarity index 88% rename from libglace/libglace-client.h rename to glace/glace-client.h index a074c87..13f602e 100644 --- a/libglace/libglace-client.h +++ b/glace/glace-client.h @@ -1,7 +1,14 @@ +#pragma once + #ifndef __LIBGLACE_CLIENT_H__ #define __LIBGLACE_CLIENT_H__ -#include "libglace-manager.h" +#include +#include +#include +#include +#include +#include G_BEGIN_DECLS @@ -31,15 +38,15 @@ struct _GlaceClientClass { GObjectClass parent_class; // _class_ methods - void (*activate)(GlaceClient *self); - void (*maximize)(GlaceClient *self); - void (*minimize)(GlaceClient *self); - void (*fullscreen)(GlaceClient *self); - void (*unmaximize)(GlaceClient *self); - void (*unminimize)(GlaceClient *self); - void (*unfullscreen)(GlaceClient *self); + void (*activate)(GlaceClient* self); + void (*maximize)(GlaceClient* self); + void (*minimize)(GlaceClient* self); + void (*fullscreen)(GlaceClient* self); + void (*unmaximize)(GlaceClient* self); + void (*unminimize)(GlaceClient* self); + void (*unfullscreen)(GlaceClient* self); void (*move)(GlaceClient* self, GdkWindow* window, const GdkRectangle* rectangle); - void (*close)(GlaceClient *self); + void (*close)(GlaceClient* self); }; /** @@ -62,6 +69,8 @@ struct _GlaceClientPrivate { GdkWaylandDisplay* gdk_display; GlaceClientProperties current_properties; GlaceClientProperties pending_properties; + + uint64_t hyprland_address; }; enum { @@ -80,6 +89,7 @@ enum { GLACE_CLIENT_PROPERTY_ACTIVATED, GLACE_CLIENT_PROPERTY_FULLSCREEN, GLACE_CLIENT_PROPERTY_CLOSED, + GLACE_CLIENT_PROPERTY_HYPRLAND_ADDRESS, GLACE_CLIENT_N_PROPERTIES }; @@ -92,6 +102,7 @@ gboolean glace_client_get_minimized(GlaceClient* self); gboolean glace_client_get_activated(GlaceClient* self); gboolean glace_client_get_fullscreen(GlaceClient* self); gboolean glace_client_get_closed(GlaceClient* self); +guint64 glace_client_get_hyprland_address(GlaceClient* self); // methods GType glace_client_get_type(); @@ -99,7 +110,7 @@ GType glace_client_get_type(); /** * glace_client_maximize: * @self: a #GlaceClient - * + * * to maximize a client (AKA make it to fill it's display area) */ void glace_client_maximize(GlaceClient* self); @@ -107,7 +118,7 @@ void glace_client_maximize(GlaceClient* self); /** * glace_client_unmaximize: * @self: a #GlaceClient - * + * * to unset a client from the maximized state */ void glace_client_unmaximize(GlaceClient* self); @@ -115,7 +126,7 @@ void glace_client_unmaximize(GlaceClient* self); /** * glace_client_minimize: * @self: a #GlaceClient - * + * * to minimize a client (AKA hide it to taskbar) */ void glace_client_minimize(GlaceClient* self); @@ -123,7 +134,7 @@ void glace_client_minimize(GlaceClient* self); /** * glace_client_unminimize: * @self: a #GlaceClient - * + * * to unset a client from the minimized state */ void glace_client_unminimize(GlaceClient* self); @@ -131,7 +142,7 @@ void glace_client_unminimize(GlaceClient* self); /** * glace_client_close: * @self: a #GlaceClient - * + * * to close a client * the `closed` property will get changed if the request was done successfully * it's guaranteed that you won't receive events for this client after it gets closed @@ -141,7 +152,7 @@ void glace_client_close(GlaceClient* self); /** * glace_client_activate: * @self: a #GlaceClient - * + * * to activate a client (AKA focus it) * the `activated` property will get changed if the request was done successfully */ @@ -152,7 +163,7 @@ void glace_client_activate(GlaceClient* self); * @self: a #GlaceClient * @window: (nullable): the #GdkWindow to use it's surface as a hint for the compositor, you can pass NULL for this * @rectangle: the #GdkRectangle to use - * + * * move this client to a X and Y coords / width and height using a rectangle * it's not guaranteed if the client will actually get moved or not * check if your target compositor(s) supports this feature or not @@ -162,7 +173,7 @@ void glace_client_move(GlaceClient* self, GdkWindow* window, const GdkRectangle* /** * glace_client_fullscreen: * @self: a #GlaceClient - * + * * to get a client in a fullscreen state * it will be ignore if the client is in a fullscreen state * the `fullscreen` property will get changed if the request was done successfully @@ -172,7 +183,7 @@ void glace_client_fullscreen(GlaceClient* self); /** * glace_client_unfullscreen: * @self: a #GlaceClient - * + * * to get a client out of fullscreen state * it will be ignore if the client is not in a fullscreen state * the `fullscreen` property will get changed if the request was done successfully diff --git a/glace/glace-manager-private.h b/glace/glace-manager-private.h new file mode 100644 index 0000000..6ad8ad3 --- /dev/null +++ b/glace/glace-manager-private.h @@ -0,0 +1,68 @@ +#pragma once + +#ifndef __LIBGLACE_MANAGER_PRIVATE_H__ +#define __LIBGLACE_MANAGER_PRIVATE_H__ + +#include "glace-client-private.h" +#include "glace-client.h" +#include "glace-manager.h" + +#define max(a, b) (a > b ? a : b) +#define min(x, y) ((x) < (y) ? (x) : (y)) + +#define EMPTY_TOKEN + +#define DISPLAY_CHECK_NULL(expr) \ + do { \ + if G_LIKELY ((expr) != NULL) \ + ; \ + else \ + g_error("[ERROR] '" #expr "' should NOT be NULL, please note that Glace can't work under anything other than Wayland"); \ + g_assert_nonnull(expr); \ + } while (0) + +#define G_LIST_FOREACH(item, list) for (GList* __glist = list; __glist && (item = __glist->data, true); __glist = __glist->next) + +typedef struct _GlaceFrameData GlaceFrameData; +typedef struct _GlaceFrameBuffer GlaceFrameBuffer; + +struct _GlaceManagerPrivate { + GdkWaylandDisplay* gdk_display; + struct wl_display* display; + struct wl_shm* wl_shm; + struct zwlr_foreign_toplevel_manager_v1* wlr_manager; + struct hyprland_toplevel_export_manager_v1* hl_export_manager; + struct hyprland_toplevel_mapping_manager_v1* hl_mapping_manager; +}; + +struct _GlaceFrameBuffer { + struct wl_buffer* wl_buffer; + void* raw_buffer; + + size_t size; + uint32_t width; + uint32_t height; + uint32_t stride; + uint32_t format; +}; + +struct _GlaceFrameData { + GlaceManager* manager; + GlaceClient* client; + + GlaceFrameBuffer* buffer; + + GlaceManagerCaptureClientCallback callback; + gpointer callback_data; +}; + +const static struct hyprland_toplevel_export_frame_v1_listener export_manager_frame_listener; + +// static void glace_manager_init(GlaceManager* self); +// static void glace_manager_class_init(GlaceManagerClass* klass); + +// static void glace_manager_signal_changed_emit(GlaceManager* self); +// static void glace_manager_signal_client_added_emit(GlaceManager* self, GlaceClient* client); +// static void glace_manager_signal_client_removed_emit(GlaceManager* self, GlaceClient* client); + +#endif /* __LIBGLACE_MANAGER_PRIVATE_H__ */ diff --git a/glace/glace-manager.c b/glace/glace-manager.c new file mode 100644 index 0000000..525f1d2 --- /dev/null +++ b/glace/glace-manager.c @@ -0,0 +1,431 @@ +#include "buffer-utils.h" +#include "glace-private.h" + +static guint glace_manager_signals[GLACE_MANAGER_N_SIGNALS] = {0}; + +static void glace_manager_signal_changed_emit(GlaceManager* self) { + g_signal_emit( + self, + glace_manager_signals[GLACE_MANAGER_SIGNAL_CHANGED], + 0 + ); +} + +static void glace_manager_signal_client_added_emit(GlaceManager* self, GlaceClient* client) { + g_signal_emit( + self, + glace_manager_signals[GLACE_MANAGER_SIGNAL_CLIENT_ADDED], + 1, + client + ); +} + +static void glace_manager_signal_client_removed_emit(GlaceManager* self, GlaceClient* client) { + g_signal_emit( + self, + glace_manager_signals[GLACE_MANAGER_SIGNAL_CLIENT_REMOVED], + 1, + client + ); +} + +static void on_client_closed_cleanup(GlaceClient* self, gpointer data) { + GlaceManager* manager = (GlaceManager*)data; + + glace_manager_signal_client_removed_emit(manager, self); + glace_manager_signal_changed_emit(manager); +} + +static void on_manager_toplevel( + void* data, + struct zwlr_foreign_toplevel_manager_v1* manager, + struct zwlr_foreign_toplevel_handle_v1* handle +) { + GlaceManager* self = data; + if (!GLACE_IS_MANAGER(self)) { + return; + } + + GlaceClient* client = glace_client_new(handle, self->priv->hl_mapping_manager, self->priv->gdk_display); + RETURN_IF_INVALID_CLIENT(client, EMPTY_TOKEN); + + g_signal_connect(client, "close", G_CALLBACK(on_client_closed_cleanup), self); + + glace_manager_signal_client_added_emit(self, client); + glace_manager_signal_changed_emit(self); + + g_debug("[INFO][MANAGER] got a client with id %u\n", glace_client_get_id(client)); +}; + +static void on_manager_finished( + void* data, + struct zwlr_foreign_toplevel_manager_v1* manager +) {} + +static const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_listener = { + .toplevel = &on_manager_toplevel, + .finished = &on_manager_finished +}; + +static void on_registry_global( + void* data, + struct wl_registry* registry, + uint32_t name, + const char* interface, + uint32_t version +) { + g_debug("[INFO][PROTOCOL] got protocol with name %s\n", interface); + + GlaceManager* self = data; + if (!GLACE_IS_MANAGER(self)) { + return; + } + + if (strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) { + g_debug("[INFO][PROTOCOL] connecting to zwlr_foreign_toplevel_manager_v1\n"); + struct zwlr_foreign_toplevel_manager_v1* manager = wl_registry_bind( + registry, + name, + &zwlr_foreign_toplevel_manager_v1_interface, + max(version, 1) + ); + self->priv->wlr_manager = manager; + + zwlr_foreign_toplevel_manager_v1_add_listener(manager, &toplevel_manager_listener, self); + } else if (strcmp(interface, hyprland_toplevel_export_manager_v1_interface.name) == 0) { + g_debug("[INFO][PROTOCOL] connecting to hyprland_toplevel_export_manager_v1\n"); + + struct hyprland_toplevel_export_manager_v1* export_manager = wl_registry_bind( + registry, + name, + &hyprland_toplevel_export_manager_v1_interface, + max(version, 1) + ); + self->priv->hl_export_manager = export_manager; + } else if (strcmp(interface, hyprland_toplevel_mapping_manager_v1_interface.name) == 0) { + g_debug("[INFO][PROTOCOL] connecting to hyprland_toplevel_mapping_manager_v1\n"); + self->priv->hl_mapping_manager = wl_registry_bind( + registry, + name, + &hyprland_toplevel_mapping_manager_v1_interface, + max(version, 1) + ); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + g_debug("[INFO][PROTOCOL] getting a shared memory buffer\n"); + + self->priv->wl_shm = wl_registry_bind(registry, name, &wl_shm_interface, version); + } + + return; +}; + +static void on_registry_global_remove(void* data, struct wl_registry* registry, uint32_t name) {} + +static const struct wl_registry_listener registry_listener = { + .global = &on_registry_global, + .global_remove = &on_registry_global_remove, +}; + +static void glace_manager_class_init(GlaceManagerClass* klass) { + // GObjectClass* parent_class = G_OBJECT_CLASS(klass); + + g_type_class_add_private(klass, sizeof(GlaceManagerPrivate)); + + // add public methods + klass->capture_client = glace_manager_capture_client; + + glace_manager_signals[GLACE_MANAGER_SIGNAL_CHANGED] = g_signal_new( + "changed", + GLACE_TYPE_MANAGER, + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0 + ); + + glace_manager_signals[GLACE_MANAGER_SIGNAL_CLIENT_ADDED] = g_signal_new( + "client-added", + GLACE_TYPE_MANAGER, + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + GLACE_TYPE_CLIENT + ); + + glace_manager_signals[GLACE_MANAGER_SIGNAL_CLIENT_REMOVED] = g_signal_new( + "client-removed", + GLACE_TYPE_MANAGER, + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + GLACE_TYPE_CLIENT + ); +} + +static void glace_manager_init(GlaceManager* self) { + self->priv = G_TYPE_INSTANCE_GET_PRIVATE( + self, + GLACE_TYPE_MANAGER, + GlaceManagerPrivate + ); + + GdkWaylandDisplay* gdk_display = gdk_display_get_default(); + if (GDK_IS_WAYLAND_DISPLAY(gdk_display) == false) { + // trap it in next check + gdk_display = NULL; + } + DISPLAY_CHECK_NULL(gdk_display); + + g_debug( + "[INFO][MANAGER] got display with name %s", + gdk_display_get_name(gdk_display) + ); + + struct wl_display* display = gdk_wayland_display_get_wl_display(gdk_display); + DISPLAY_CHECK_NULL(display); + + self->priv->display = display; + self->priv->gdk_display = gdk_display; + self->priv->hl_mapping_manager = NULL; + + // all aboard... + struct wl_registry* registry = wl_display_get_registry(self->priv->display); + wl_registry_add_listener(registry, ®istry_listener, self); + + wl_display_roundtrip(self->priv->display); + + if (self->priv->wlr_manager == NULL) + g_warning( + "[WARNING][MANAGER] your compositor does not support the wlr-foreign-toplevel-management protocol, Glace will not work if the protocol support is missing!" + ); + + buffer_utils_log_available_accelerator(); +} + +static inline int anonymous_shm_open() { + char* name; + int retries = 100; + do { + --retries; + name = g_strdup_printf("/glace-hyprland-frame-%i", g_random_int()); + + // shm_open guarantees that O_CLOEXEC is set + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + + return -1; +} + +static inline int create_shm_file(off_t size) { + int fd = anonymous_shm_open(); + if (fd < 0) { + return fd; + } + + if (ftruncate(fd, size) < 0) { + close(fd); + return -1; + } + + return fd; +} + +static GlaceFrameBuffer* glace_frame_buffer_new(struct wl_shm* shm, enum wl_shm_format format, int32_t width, int32_t height, int32_t stride) { + size_t size = stride * height; + + int fd = create_shm_file(size); + if (fd == -1) { + return NULL; + } + + if (shm == NULL) { + return NULL; + } + + void* raw_buffer = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (raw_buffer == MAP_FAILED) { + close(fd); + return NULL; + } + + struct wl_shm_pool* pool = wl_shm_create_pool(shm, fd, size); + + if (!pool) { + munmap(raw_buffer, size); + close(fd); + return NULL; + } + + struct wl_buffer* wl_buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format); + + wl_shm_pool_destroy(pool); + close(fd); + + GlaceFrameBuffer* buffer = calloc(1, sizeof(GlaceFrameBuffer)); + buffer->wl_buffer = wl_buffer; + buffer->raw_buffer = raw_buffer; + buffer->size = size; + buffer->width = width; + buffer->height = height; + buffer->stride = stride; + buffer->format = format; + + return buffer; +} + +static void glace_frame_buffer_destroy(GlaceFrameBuffer* buffer) { + if (buffer == NULL) { + return; + } + munmap(buffer->raw_buffer, buffer->size); + wl_buffer_destroy(buffer->wl_buffer); + free(buffer); +} + +static void glace_frame_data_destroy(GlaceFrameData* data) { + // die and be a hero or live long enough to see yourself become a villain + if (!data) { + return; + } + + if (data->buffer != NULL) { + glace_frame_buffer_destroy(data->buffer); + } + + data->buffer = NULL; + data->client = NULL; + data->manager = NULL; + data->callback = NULL; + data->callback_data = NULL; + + free(data); + return; +} + +static void do_release_pixbuf(guchar* pixels, gpointer user_data) { + GlaceFrameData* data = user_data; + // we'll miss you + glace_frame_data_destroy(data); +} + +static void on_export_manager_frame_buffer(void* user_data, struct hyprland_toplevel_export_frame_v1* export_frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { + GlaceFrameData* data = user_data; + data->buffer = glace_frame_buffer_new(data->manager->priv->wl_shm, format, width, height, stride); + return; +} + +static void on_export_manager_frame_ready(void* user_data, struct hyprland_toplevel_export_frame_v1* export_frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { + GlaceFrameData* data = user_data; + + if (!data) + goto finalize; + if (!data->buffer) { + data->callback(NULL, data->callback_data); + glace_frame_data_destroy(data); + goto finalize; + } + + buffer_utils_bgrx_to_rgbx(data->buffer->raw_buffer, data->buffer->width * data->buffer->height); + GdkPixbuf* pixbuf = gdk_pixbuf_new_from_data( + data->buffer->raw_buffer, + GDK_COLORSPACE_RGB, + TRUE, + 8, + data->buffer->width, + data->buffer->height, + data->buffer->stride, + do_release_pixbuf, + data + ); + + data->callback(pixbuf, data->callback_data); + // gdk_pixbuf_unref(pixbuf); // !FIXME: unsafe, add a function annotation for giving ownership to the caller + +finalize: + hyprland_toplevel_export_frame_v1_destroy(export_frame); + return; +} + +static void on_export_manager_frame_failed(void* user_data, struct hyprland_toplevel_export_frame_v1* export_frame) { + GlaceFrameData* data = user_data; + + data->callback(NULL, data->callback_data); + glace_frame_data_destroy(data); + + hyprland_toplevel_export_frame_v1_destroy(export_frame); + + return; +} + +static void on_export_manager_frame_buffer_done(void* user_data, struct hyprland_toplevel_export_frame_v1* export_frame) { + GlaceFrameData* data = user_data; + + // all aboard... + hyprland_toplevel_export_frame_v1_copy(export_frame, data->buffer->wl_buffer, 1); + + return; +} + +static void on_export_manager_frame_damage(void* user_data, struct hyprland_toplevel_export_frame_v1* export_frame, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {} +static void on_export_manager_frame_flags(void* user_data, struct hyprland_toplevel_export_frame_v1* export_frame, uint32_t flags) {} +static void on_export_manager_frame_linux_dmabuf(void* user_data, struct hyprland_toplevel_export_frame_v1* export_frame, uint32_t format, uint32_t width, uint32_t height) {} + +static const struct hyprland_toplevel_export_frame_v1_listener export_manager_frame_listener = { + .buffer = &on_export_manager_frame_buffer, + .damage = &on_export_manager_frame_damage, + .flags = &on_export_manager_frame_flags, + .ready = &on_export_manager_frame_ready, + .failed = &on_export_manager_frame_failed, + .linux_dmabuf = &on_export_manager_frame_linux_dmabuf, + .buffer_done = &on_export_manager_frame_buffer_done +}; + +void glace_manager_finalize(GObject* object) { + GlaceManager* self = GLACE_MANAGER(object); + G_OBJECT_CLASS(g_type_class_peek_parent(G_OBJECT_GET_CLASS(self)))->finalize(object); +} + +// public methods +GlaceManager* glace_manager_new() { + return g_object_new(GLACE_TYPE_MANAGER, NULL); +} + +void glace_manager_capture_client(GlaceManager* self, GlaceClient* client, gboolean overlay_cursor, GlaceManagerCaptureClientCallback callback, gpointer user_data, GDestroyNotify notify) { + if (!self->priv->hl_export_manager) { + g_warning_once("at the moment, capturing a client is only available for Hyprland users."); + callback(NULL, user_data); + return; + } + + GlaceFrameData* data = calloc(1, sizeof(GlaceFrameData)); + data->manager = self; + data->client = client; + data->callback = callback; + data->callback_data = user_data; + data->buffer = NULL; + + struct hyprland_toplevel_export_frame_v1* frame = hyprland_toplevel_export_manager_v1_capture_toplevel_with_wlr_toplevel_handle( + self->priv->hl_export_manager, (gint)overlay_cursor, client->priv->wlr_handle + ); + + hyprland_toplevel_export_frame_v1_add_listener(frame, &export_manager_frame_listener, data); + + return; +} + +G_DEFINE_TYPE(GlaceManager, glace_manager, G_TYPE_OBJECT); diff --git a/libglace/libglace-manager.h b/glace/glace-manager.h similarity index 51% rename from libglace/libglace-manager.h rename to glace/glace-manager.h index f208062..5ab4ab1 100644 --- a/libglace/libglace-manager.h +++ b/glace/glace-manager.h @@ -1,12 +1,9 @@ +#pragma once + #ifndef __LIBGLACE_MANAGER_H__ #define __LIBGLACE_MANAGER_H__ -#include -#include -#include -#include -#include -#include +#include "glace-client.h" G_BEGIN_DECLS @@ -23,6 +20,15 @@ typedef struct _GlaceManager GlaceManager; typedef struct _GlaceManagerPrivate GlaceManagerPrivate; typedef struct _GlaceManagerClass GlaceManagerClass; +/** + * GlaceManagerCaptureClientCallback: + * @pixbuf: (transfer full): the captured pixbuf, ownership is transferred to the caller + * + * called when a client capture operation completes. + * the caller must unref the @pixbuf once uneeded to avoid leaks. + */ +typedef void (*GlaceManagerCaptureClientCallback)(GdkPixbuf* pixbuf, gpointer user_data); + struct _GlaceManager { GObject parent_instance; GlaceManagerPrivate* priv; @@ -30,12 +36,9 @@ struct _GlaceManager { struct _GlaceManagerClass { GObjectClass parent_class; -}; -struct _GlaceManagerPrivate { - GdkWaylandDisplay* gdk_display; - struct wl_display* display; - struct zwlr_foreign_toplevel_manager_v1* wlr_manager; + // public methods + void (*capture_client)(GlaceManager* self, GlaceClient* client, gboolean overlay_cursor, GlaceManagerCaptureClientCallback callback, gpointer user_data, GDestroyNotify notify); }; enum { @@ -49,6 +52,17 @@ enum { GType glace_manager_get_type(); GlaceManager* glace_manager_new(); +/** + * glace_manager_capture_client: + * @self: a #GlaceManager + * @client: the #GlaceClient instance to capture + * @overlay_cursor: whether or not to render the cursor on the client, optional and defaults to false + * @callback: a callback for receiving the rendered snapshot, this callback should be able of receiving a GdkPixbuf where the data resigns + * + * try and get a snapshot capture of a client, this only works on hyprland currently. + */ +void glace_manager_capture_client(GlaceManager* self, GlaceClient* client, gboolean overlay_cursor, GlaceManagerCaptureClientCallback callback, gpointer user_data, GDestroyNotify notify); + G_END_DECLS #endif /* __LIBGLACE_MANAGER_H__ */ diff --git a/glace/glace-private.h b/glace/glace-private.h new file mode 100644 index 0000000..1a6eedc --- /dev/null +++ b/glace/glace-private.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +#include "glace-client-private.h" +#include "glace-manager-private.h" +#include "hyprland-toplevel-export-v1.h" +#include "hyprland-toplevel-mapping-v1.h" +#include "wlr-foreign-toplevel-management-unstable-v1.h" diff --git a/glace/glace.h b/glace/glace.h new file mode 100644 index 0000000..5e27c94 --- /dev/null +++ b/glace/glace.h @@ -0,0 +1,4 @@ +#pragma once + +#include "glace-client.h" +#include "glace-manager.h" diff --git a/libglace/meson.build b/glace/meson.build similarity index 62% rename from libglace/meson.build rename to glace/meson.build index 5e5bc1a..04ce575 100644 --- a/libglace/meson.build +++ b/glace/meson.build @@ -1,38 +1,32 @@ gnome = import('gnome') pkg_config = import('pkgconfig') +glace_name = meson.project_name() glace_version = meson.project_version() glace_description = 'a library for managing wayland clients' glace_url = 'https://github.com/Fabric-Development/glace' -glace_library_name = 'lib' + meson.project_name() +glace_library_name = glace_name glace_namespace = 'Glace' glace_package_name = glace_namespace + '-' + glace_version -glace_public_src = files( - 'libglace-manager.c', - 'libglace-client.c', - 'libglace.c', +glace_sources = files( + 'glace-manager.c', + 'glace-client.c' ) -glace_private_src = files( - 'libglace-manager-private.c', - 'libglace-client-private.c', -) - -glace_sources = glace_public_src + glace_private_src - glace_public_headers = files( - 'libglace-manager.h', - 'libglace-client.h', - 'libglace.h', + 'glace-manager.h', + 'glace-client.h', + 'glace.h' ) glace_private_headers = files( - 'libglace-manager-private.h', - 'libglace-client-private.h', - 'libglace-private.h', + 'buffer-utils.h', + 'glace-manager-private.h', + 'glace-client-private.h', + 'glace-private.h' ) glace_headers = glace_public_headers + glace_private_headers @@ -52,20 +46,21 @@ all_dependencies = [ libglace = library( glace_library_name, glace_sources, + c_args: ['-O3', '-march=native'], dependencies: all_dependencies, install: true, ) glace_gir = gnome.generate_gir( libglace, - header: 'libglace.h', - sources: glace_public_src + glace_public_headers, + header: 'glace.h', + sources: glace_sources + glace_public_headers, namespace: glace_namespace, nsversion: glace_version, identifier_prefix: glace_namespace, - includes: ['GObject-2.0', 'Gtk-3.0'], + includes: ['GObject-2.0', 'GLib-2.0', 'Gtk-3.0'], dependencies: dependencies, - header: 'libglace/libglace.h', + header: 'glace/glace.h', symbol_prefix: 'glace', install: true, ) @@ -74,18 +69,18 @@ glace_gir = gnome.generate_gir( libgpaste_vapi = gnome.generate_vapi( glace_package_name, sources: [glace_gir[0]], - packages: [ 'gtk+-3.0', 'gio-2.0', 'glib-2.0', 'gobject-2.0' ], + packages: ['gtk+-3.0', 'gio-2.0', 'glib-2.0', 'gobject-2.0'], install: true, ) install_headers( - glace_headers , - subdir: glace_library_name, + glace_public_headers, + subdir: glace_name, ) pkg_config.generate( - name: meson.project_name(), - filebase: glace_library_name, + name: glace_name, + filebase: glace_name, version: meson.project_version(), libraries: libglace, requires: dependencies, diff --git a/libglace/libglace-client-private.h b/libglace/libglace-client-private.h deleted file mode 100644 index c55dfd9..0000000 --- a/libglace/libglace-client-private.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef __LIBGLACE_CLIENT_PRIVATE_H__ -#define __LIBGLACE_CLIENT_PRIVATE_H__ - -#include "libglace-client.h" -#include "libglace-manager-private.h" - -#define CLIENT_SET_CURRENT_PROP(client, prop, value) \ - (client->priv->current_properties.prop = value) - -#define CLIENT_SET_PENDING_PROP(client, prop, value) \ - (client->priv->pending_properties.prop = value) - -#define CLIENT_GET_CURRENT_PROP(client, prop) \ - (client->priv->current_properties.prop) - -#define CLIENT_GET_PENDING_PROP(client, prop) \ - (client->priv->pending_properties.prop) - -#define CLIENT_BRIDGE_PROPS(client, prop, PROP_UPPER) do { \ - if (CLIENT_GET_CURRENT_PROP(client, prop) != CLIENT_GET_PENDING_PROP(client, prop)) { \ - CLIENT_GET_CURRENT_PROP(client, prop) = CLIENT_GET_PENDING_PROP(client, prop); \ - g_object_notify_by_pspec( \ - G_OBJECT(client), \ - glace_client_properties[GLACE_CLIENT_PROPERTY_##PROP_UPPER] \ - ); \ - } \ -} while (0) - -#define CLIENT_SET_STATE_FOR_CASE(client, state, ENUM_M, prop) \ - case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_##ENUM_M: \ - CLIENT_SET_PENDING_PROP(client, prop, true); \ - break; \ - -#define CLIENT_SET_DEFAULT_STATES(client) \ - CLIENT_SET_PENDING_PROP(client, maximized, false); \ - CLIENT_SET_PENDING_PROP(client, minimized, false); \ - CLIENT_SET_PENDING_PROP(client, activated, false); \ - CLIENT_SET_PENDING_PROP(client, fullscreen, false); \ - -#define IF_INVALID_CLIENT(client) if (client == NULL || GLACE_IS_CLIENT(client) == false || client->priv->closed == true) - -#define RETURN_IF_INVALID_CLIENT(client, r_value) do { \ - IF_INVALID_CLIENT(client) {\ - g_warning("[WARNING][CLIENT] function %s got an invalid client", __func__);\ - return r_value; \ - } \ -} while (0) - - -// signal emitters -static void glace_client_signal_changed_emit(GlaceClient* self); -static void glace_client_signal_close_emit(GlaceClient* self); - -// methods -static void glace_client_init(GlaceClient* self); -static void glace_client_class_init(GlaceClientClass* klass); - -GlaceClient* glace_client_new(struct zwlr_foreign_toplevel_handle_v1* wlr_handle, GdkWaylandDisplay* gdk_display); - - -#endif /* __LIBGLACE_CLIENT_PRIVATE_H__ */ diff --git a/libglace/libglace-client.c b/libglace/libglace-client.c deleted file mode 100644 index 3811ac2..0000000 --- a/libglace/libglace-client.c +++ /dev/null @@ -1,129 +0,0 @@ -#include "libglace-private.h" - -// client getters -guint glace_client_get_id(GlaceClient* self) { - return (guint)self->priv->id; -} - -const gchar* glace_client_get_app_id(GlaceClient* self) { - return CLIENT_GET_CURRENT_PROP(self, app_id); -} - -const gchar* glace_client_get_title(GlaceClient* self) { - return CLIENT_GET_CURRENT_PROP(self, title); -} - -gboolean glace_client_get_maximized(GlaceClient* self) { - return CLIENT_GET_CURRENT_PROP(self, maximized); -} - -gboolean glace_client_get_minimized(GlaceClient* self) { - return CLIENT_GET_CURRENT_PROP(self, minimized); -} - -gboolean glace_client_get_activated(GlaceClient* self) { - return CLIENT_GET_CURRENT_PROP(self, activated); -} - -gboolean glace_client_get_fullscreen(GlaceClient* self) { - return CLIENT_GET_CURRENT_PROP(self, fullscreen); -} - -gboolean glace_client_get_closed(GlaceClient* self) { - return self->priv->closed; -} - - -// client methods -void glace_client_maximize(GlaceClient* self) { - // replacing EMPTY_TOKEN with a trailing comma - // will do the trick as well - RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); - - zwlr_foreign_toplevel_handle_v1_set_maximized(self->priv->wlr_handle); -} - - -void glace_client_unmaximize(GlaceClient* self) { - RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); - - zwlr_foreign_toplevel_handle_v1_unset_maximized(self->priv->wlr_handle); -} - - -void glace_client_minimize(GlaceClient* self) { - RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); - - zwlr_foreign_toplevel_handle_v1_set_minimized(self->priv->wlr_handle); -} - - -void glace_client_unminimize(GlaceClient* self) { - RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); - - zwlr_foreign_toplevel_handle_v1_unset_minimized(self->priv->wlr_handle); -} - - -void glace_client_close(GlaceClient* self) { - RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); - - zwlr_foreign_toplevel_handle_v1_close(self->priv->wlr_handle); -} - - -void glace_client_activate(GlaceClient* self) { - RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); - - GdkSeat* gdk_seat = gdk_display_get_default_seat(self->priv->gdk_display); - struct wl_seat* seat = gdk_wayland_seat_get_wl_seat(gdk_seat); - - zwlr_foreign_toplevel_handle_v1_activate( - self->priv->wlr_handle, - seat - ); -} - - -void glace_client_move( - GlaceClient* self, - GdkWindow* window, - const GdkRectangle* rectangle -) { - RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); - - if (window == NULL) { - zwlr_foreign_toplevel_handle_v1_set_rectangle( - self->priv->wlr_handle, - NULL, - rectangle->x, - rectangle->y, - rectangle->width, - rectangle->height - ); - return; - } - - zwlr_foreign_toplevel_handle_v1_set_rectangle( - self->priv->wlr_handle, - gdk_wayland_window_get_wl_surface(window), - rectangle->x, - rectangle->y, - rectangle->width, - rectangle->height - ); -} - - -void glace_client_fullscreen(GlaceClient* self) { - RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); - - zwlr_foreign_toplevel_handle_v1_set_fullscreen(self->priv->wlr_handle, self->priv->output); -} - - -void glace_client_unfullscreen(GlaceClient* self) { - RETURN_IF_INVALID_CLIENT(self, EMPTY_TOKEN); - - zwlr_foreign_toplevel_handle_v1_unset_fullscreen(self->priv->wlr_handle); -} diff --git a/libglace/libglace-manager-private.c b/libglace/libglace-manager-private.c deleted file mode 100644 index 23f5489..0000000 --- a/libglace/libglace-manager-private.c +++ /dev/null @@ -1,198 +0,0 @@ -#include "libglace-private.h" - -static guint glace_manager_signals[GLACE_MANAGER_N_SIGNALS] = {0}; - - -static void glace_manager_signal_changed_emit(GlaceManager* self) { - g_signal_emit( - self, - glace_manager_signals[GLACE_MANAGER_SIGNAL_CHANGED], - 0 - ); -} - - -static void glace_manager_signal_client_added_emit(GlaceManager* self, GlaceClient* client) { - g_signal_emit( - self, - glace_manager_signals[GLACE_MANAGER_SIGNAL_CLIENT_ADDED], - 1, - client - ); -} - - -static void glace_manager_signal_client_removed_emit(GlaceManager* self, GlaceClient* client) { - g_signal_emit( - self, - glace_manager_signals[GLACE_MANAGER_SIGNAL_CLIENT_REMOVED], - 1, - client - ); -} - - -static void on_client_closed_cleanup(GlaceClient* self, gpointer data) { - GlaceManager* manager = (GlaceManager*)data; - - glace_manager_signal_client_removed_emit(manager, self); - glace_manager_signal_changed_emit(manager); -} - - -static void on_manager_toplevel( - void* data, - struct zwlr_foreign_toplevel_manager_v1* manager, - struct zwlr_foreign_toplevel_handle_v1* handle -) { - GlaceManager* self = data; - if (GLACE_IS_MANAGER(self) != true) { - return; - } - - GlaceClient* client = glace_client_new(handle, self->priv->gdk_display); - RETURN_IF_INVALID_CLIENT(client, EMPTY_TOKEN); - - g_signal_connect(client, "close", G_CALLBACK(on_client_closed_cleanup), self); - - glace_manager_signal_client_added_emit(self, client); - glace_manager_signal_changed_emit(self); - - g_debug("[INFO][MANAGER] got a client with id %u\n", glace_client_get_id(client)); -}; - - -static void on_manager_finished( - void* data, - struct zwlr_foreign_toplevel_manager_v1* manager -) {} - - -static const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_listener = { - .toplevel = &on_manager_toplevel, - .finished = &on_manager_finished -}; - - -static void on_registry_global( - void* data, - struct wl_registry* registry, - uint32_t name, - const char* interface, - uint32_t version -) { - g_debug("[INFO][PROTOCOL] got protocol with name %s\n", interface); - - if (strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) { - g_debug("[INFO][PROTOCOL] connecting to zwlr_foreign_toplevel_manager_v1\n"); - GlaceManager* self = data; - - if (GLACE_IS_MANAGER(self) != true) { - return; - } - - struct zwlr_foreign_toplevel_manager_v1* manager = wl_registry_bind( - registry, - name, - &zwlr_foreign_toplevel_manager_v1_interface, - max(version, 1) - ); - self->priv->wlr_manager = manager; - - zwlr_foreign_toplevel_manager_v1_add_listener(manager, &toplevel_manager_listener, self); - } -}; - - -static void on_registry_global_remove(void* data, struct wl_registry *registry, uint32_t name) {} - - -static const struct wl_registry_listener registry_listener = { - .global = &on_registry_global, - .global_remove = &on_registry_global_remove, -}; - - -static void glace_manager_class_init(GlaceManagerClass* klass) { - GObjectClass* parent_class = G_OBJECT_CLASS(klass); - - g_type_class_add_private(klass, sizeof(GlaceManagerPrivate)); - - glace_manager_signals[GLACE_MANAGER_SIGNAL_CHANGED] = g_signal_new( - "changed", - GLACE_TYPE_MANAGER, - G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, - 0, - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0 - ); - - glace_manager_signals[GLACE_MANAGER_SIGNAL_CLIENT_ADDED] = g_signal_new( - "client-added", - GLACE_TYPE_MANAGER, - G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, - 0, - NULL, - NULL, - g_cclosure_marshal_VOID__OBJECT, - G_TYPE_NONE, - 1, - GLACE_TYPE_CLIENT - ); - - glace_manager_signals[GLACE_MANAGER_SIGNAL_CLIENT_REMOVED] = g_signal_new( - "client-removed", - GLACE_TYPE_MANAGER, - G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, - 0, - NULL, - NULL, - g_cclosure_marshal_VOID__OBJECT, - G_TYPE_NONE, - 1, - GLACE_TYPE_CLIENT - ); -} - - -static void glace_manager_init(GlaceManager* self) { - self->priv = G_TYPE_INSTANCE_GET_PRIVATE( - self, - GLACE_TYPE_MANAGER, - GlaceManagerPrivate - ); - - GdkWaylandDisplay* gdk_display = gdk_display_get_default(); - if (GDK_IS_WAYLAND_DISPLAY(gdk_display) == false) { - // trap it in next check - gdk_display = NULL; - } - DISPLAY_CHECK_NULL(gdk_display); - - g_debug( - "[INFO][MANAGER] got display with name %s", - gdk_display_get_name(gdk_display) - ); - - struct wl_display* display = gdk_wayland_display_get_wl_display(gdk_display); - DISPLAY_CHECK_NULL(display); - - self->priv->display = display; - self->priv->gdk_display = gdk_display; - - // all aboard... - struct wl_registry* registry = wl_display_get_registry(self->priv->display); - wl_registry_add_listener(registry, ®istry_listener, self); - - wl_display_roundtrip(self->priv->display); - - if (self->priv->wlr_manager == NULL) g_warning( - "[WARNING][MANAGER] your compositor does not support the wlr-foreign-toplevel-management protocol, Glace will not work if the protocol support is missing!" - ); -} - - -G_DEFINE_TYPE(GlaceManager, glace_manager, G_TYPE_OBJECT); diff --git a/libglace/libglace-manager-private.h b/libglace/libglace-manager-private.h deleted file mode 100644 index d9e1b0f..0000000 --- a/libglace/libglace-manager-private.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef __LIBGLACE_MANAGER_PRIVATE_H__ -#define __LIBGLACE_MANAGER_PRIVATE_H__ - -#include "libglace-manager.h" -#include "libglace-client-private.h" -#include "wlr-foreign-toplevel-management-unstable-v1.h" - -#define max(a, b) (a > b ? a : b) -#define min(x, y) ((x) < (y) ? (x) : (y)) - -#define EMPTY_TOKEN - -#define DISPLAY_CHECK_NULL(expr) do { \ - if G_LIKELY((expr) != NULL); else \ - g_error("[ERROR] '"#expr"' should NOT be NULL, please note that Glace can't work under anything other than Wayland"); \ - g_assert_nonnull(expr); \ -} while (0) - -#define G_LIST_FOREACH(item, list) for (GList *__glist = list; __glist && (item = __glist->data, true); __glist = __glist->next) - -// static void glace_manager_init(GlaceManager* self); -// static void glace_manager_class_init(GlaceManagerClass* klass); - -static void glace_manager_signal_changed_emit(GlaceManager* self); -static void glace_manager_signal_client_added_emit(GlaceManager* self, GlaceClient* client); -static void glace_manager_signal_client_removed_emit(GlaceManager* self, GlaceClient* client); - -#endif /* __LIBGLACE_MANAGER_PRIVATE_H__ */ diff --git a/libglace/libglace-manager.c b/libglace/libglace-manager.c deleted file mode 100644 index e136ebe..0000000 --- a/libglace/libglace-manager.c +++ /dev/null @@ -1,5 +0,0 @@ -#include "libglace-private.h" - -GlaceManager* glace_manager_new() { - return g_object_new(GLACE_TYPE_MANAGER, NULL); -} diff --git a/libglace/libglace-private.h b/libglace/libglace-private.h deleted file mode 100644 index 075b751..0000000 --- a/libglace/libglace-private.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "libglace-client-private.h" -#include "libglace-manager-private.h" diff --git a/libglace/libglace.c b/libglace/libglace.c deleted file mode 100644 index 5e5cb4c..0000000 --- a/libglace/libglace.c +++ /dev/null @@ -1,2 +0,0 @@ -#include "libglace-manager.h" -#include "libglace-client.h" diff --git a/libglace/libglace.h b/libglace/libglace.h deleted file mode 100644 index 5e5cb4c..0000000 --- a/libglace/libglace.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "libglace-manager.h" -#include "libglace-client.h" diff --git a/meson.build b/meson.build index 1ec3de2..0e575a9 100644 --- a/meson.build +++ b/meson.build @@ -1,8 +1,8 @@ project('glace', 'c', version: '0.1', license: 'AGPL-3.0-or-later') if build_machine.system() == 'windows' - error('libglace is not meant for windows users, consider moving to linux.') + error('glace is not meant for windows users, consider moving to linux.') endif subdir('protocol') -subdir('libglace') +subdir('glace') diff --git a/protocol/hyprland-toplevel-export-v1.xml b/protocol/hyprland-toplevel-export-v1.xml new file mode 100644 index 0000000..fd005f3 --- /dev/null +++ b/protocol/hyprland-toplevel-export-v1.xml @@ -0,0 +1,204 @@ + + + + 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. + + + This protocol allows clients to ask for exporting another toplevel's + surface(s) to a buffer. + + Particularly useful for sharing a single window. + + + + This object is a manager which offers requests to start capturing from a + source. + + + + 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. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + Same as capture_toplevel, but with a zwlr_foreign_toplevel_handle_v1 handle. + + + + + + + + + + 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. + + + + 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. + + + + + + + + + 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. + + + + + + + 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. + + + + + + + + + + + + + + + + Provides flags about the frame. This event is sent once before the + "ready" event. + + + + + + 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. + + + + + + + + This event indicates that the attempted frame copy has failed. + + After receiving this event, the client should destroy the object. + + + + + Destroys the frame. This request can be sent at any time by the client. + + + + + 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. + + + + + + + + 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. + + + + diff --git a/protocol/hyprland-toplevel-mapping-v1.xml b/protocol/hyprland-toplevel-mapping-v1.xml new file mode 100644 index 0000000..8fc3174 --- /dev/null +++ b/protocol/hyprland-toplevel-mapping-v1.xml @@ -0,0 +1,124 @@ + + + + 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. + + + + This protocol allows clients to retrieve the mapping of toplevels to hyprland window addresses. + + + + + This object is a manager which offers requests to retrieve a window address + for a toplevel. + + + + + Get the window address for a toplevel. + + + + + + + + Get the window address for a wlr toplevel. + + + + + + + + All objects created by the manager will still remain valid, until their appropriate destroy + request has been called. + + + + + + + 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. + + + + + The full 64bit window address. The `address` field contains the lower 32 bits whilst the + `address_hi` contains the upper 32 bits + + + + + + + + The mapping of the toplevel to a window address failed. Most likely the window does not + exist (anymore). + + + + + + Destroy the handle. This request can be sent at any time by the client. + + + + \ No newline at end of file diff --git a/protocol/meson.build b/protocol/meson.build index f869e4b..30fb6b4 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -1,34 +1,41 @@ wayland_dep = dependency('wayland-client') -protocol_name = 'wlr-foreign-toplevel-management-unstable-v1' -protocol_file = files(protocol_name + '.xml') +protocol_names = [ + 'wlr-foreign-toplevel-management-unstable-v1', + 'hyprland-toplevel-mapping-v1', + 'hyprland-toplevel-export-v1', +] wayland_scanner_dep = dependency('wayland-scanner', native: true) wayland_scanner = find_program( - wayland_scanner_dep.get_variable( - pkgconfig: 'wayland_scanner' - ) + wayland_scanner_dep.get_variable( + pkgconfig: 'wayland_scanner' + ) ) protocol_sources = [] -protocol_sources += custom_target( - protocol_name + '.h', - command: [ wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@' ], - input: protocol_file, - output: protocol_name + '.h', -) - output_type = 'private-code' if wayland_scanner_dep.version().version_compare('< 1.14.91') output_type = 'code' endif -protocol_sources += custom_target( - protocol_name + '.c', - command: [ wayland_scanner, output_type, '@INPUT@', '@OUTPUT@' ], - input: protocol_file, - output: protocol_name + '.c', -) +foreach protocol_name : protocol_names + protocol_file = files(protocol_name + '.xml') + + protocol_sources += custom_target( + protocol_name + '.h', + command: [ wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@' ], + input: protocol_file, + output: protocol_name + '.h', + ) + + protocol_sources += custom_target( + protocol_name + '.c', + command: [ wayland_scanner, output_type, '@INPUT@', '@OUTPUT@' ], + input: protocol_file, + output: protocol_name + '.c', + ) +endforeach toplevel_management_dep = declare_dependency( dependencies: [