From ae56ab59af6982b775eb1d12901984dc6fa726e6 Mon Sep 17 00:00:00 2001 From: Juan Pablo Ugarte Date: Sat, 26 Jul 2025 17:05:01 -0400 Subject: [PATCH] CasildaCompositor: implement scrollable iface Close issue #7 "Implement GtkScrollable iface" --- examples/compositor.c | 7 +- src/casilda-compositor.c | 225 +++++++++++++++++++++++++++++---------- 2 files changed, 176 insertions(+), 56 deletions(-) diff --git a/examples/compositor.c b/examples/compositor.c index 3f55fe8..7ed78b4 100644 --- a/examples/compositor.c +++ b/examples/compositor.c @@ -9,7 +9,7 @@ static void activate (GtkApplication *app, G_GNUC_UNUSED gpointer user_data) { CasildaCompositor *compositor; - GtkWidget *window; + GtkWidget *window, *sw; window = gtk_application_window_new (app); gtk_window_set_title (GTK_WINDOW (window), "Casilda Compositor"); @@ -20,8 +20,11 @@ activate (GtkApplication *app, G_GNUC_UNUSED gpointer user_data) else g_message ("Not listening in any socket, you can pass a socket path as parameter"); + sw = gtk_scrolled_window_new (); compositor = casilda_compositor_new (socket); - gtk_window_set_child (GTK_WINDOW (window), GTK_WIDGET (compositor)); + + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), GTK_WIDGET (compositor)); + gtk_window_set_child (GTK_WINDOW (window), GTK_WIDGET (sw)); gtk_window_present (GTK_WINDOW (window)); gchar *argv[2] = { diff --git a/src/casilda-compositor.c b/src/casilda-compositor.c index 66906a6..990a21b 100644 --- a/src/casilda-compositor.c +++ b/src/casilda-compositor.c @@ -169,6 +169,8 @@ typedef struct /* GObject properties */ gchar *socket; + + GtkAdjustment *adjustment[2]; } CasildaCompositorPrivate; @@ -256,12 +258,19 @@ enum { PROP_SOCKET, PROP_BG_COLOR, - N_PROPERTIES + N_PROPERTIES, + + PROP_HADJUSTMENT, + PROP_VADJUSTMENT, + PROP_HSCROLL_POLICY, + PROP_VSCROLL_POLICY }; static GParamSpec *properties[N_PROPERTIES]; -G_DEFINE_TYPE_WITH_PRIVATE (CasildaCompositor, casilda_compositor, GTK_TYPE_WIDGET); +G_DEFINE_TYPE_WITH_CODE (CasildaCompositor, casilda_compositor, GTK_TYPE_WIDGET, + G_ADD_PRIVATE (CasildaCompositor) + G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL)) #define GET_PRIVATE(d) ((CasildaCompositorPrivate *) casilda_compositor_get_instance_private ((CasildaCompositor *) d)) @@ -449,8 +458,8 @@ wlr_texture_as_gdk (WlrTexture *texture, GdkGLContext *gl_context, pixman_region static gboolean xdg_surface_get_data (struct wlr_xdg_surface *xdg_surface, struct wlr_surface **surface, struct wlr_box *geometry) { - if (!xdg_surface || !(*surface = xdg_surface->surface) || !xdg_surface->surface->buffer) - return TRUE; + if (!xdg_surface || !(*surface = xdg_surface->surface) || !xdg_surface->surface->buffer) + return TRUE; if (geometry) wlr_xdg_surface_get_geometry (xdg_surface, geometry); @@ -477,10 +486,10 @@ debug_snapshot_border (GtkSnapshot *snapshot, #endif typedef struct { - GdkGLContext *gl_context; GtkSnapshot *snapshot; + GdkGLContext *gl_context; gfloat scale; - struct timespec *now; + struct timespec now; } SnapshotData; static void @@ -506,34 +515,22 @@ snapshot_surfaces (struct wlr_surface *surface, int sx, int sy, void *data) gdk_paintable_snapshot (GDK_PAINTABLE (tex), sdata->snapshot, w, h); gtk_snapshot_restore (sdata->snapshot); - wlr_surface_send_frame_done (surface, sdata->now); + wlr_surface_send_frame_done (surface, &sdata->now); g_set_object (&surface->data, tex); } static void -snapshot_surface (GtkSnapshot *snapshot, - GdkGLContext *gl_context, - struct timespec *now, - double scale, - struct wlr_surface *surface, - gint x, - gint y) +snapshot_surface (struct wlr_surface *surface, SnapshotData *data, gint x, gint y) { - gtk_snapshot_save (snapshot); - gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y)); - wlr_surface_for_each_surface (surface, snapshot_surfaces, &(SnapshotData) {gl_context, snapshot, scale, now }); - gtk_snapshot_restore (snapshot); + gtk_snapshot_save (data->snapshot); + gtk_snapshot_translate (data->snapshot, &GRAPHENE_POINT_INIT (x, y)); + wlr_surface_for_each_surface (surface, snapshot_surfaces, data); + gtk_snapshot_restore (data->snapshot); } static void -casilda_compositor_snapshot_popup (CasildaCompositorPopup *popup, - GtkSnapshot *snapshot, - GdkGLContext *gl_context, - struct timespec *now, - double scale, - gint parent_x, - gint parent_y) +casilda_compositor_snapshot_popup (CasildaCompositorPopup *popup, SnapshotData *data, gint parent_x, gint parent_y) { struct wlr_surface *psurface; struct wlr_box pbox = {0, }; @@ -544,19 +541,15 @@ casilda_compositor_snapshot_popup (CasildaCompositorPopup *popup, gint popup_x = parent_x + popup->xdg_popup->current.geometry.x; gint popup_y = parent_y + popup->xdg_popup->current.geometry.y; - snapshot_surface (snapshot, gl_context, now, scale, psurface, popup_x - pbox.x, popup_y - pbox.y); + snapshot_surface (psurface, data, popup_x - pbox.x, popup_y - pbox.y); for (GList *l = popup->popups; l; l = g_list_next (l)) - casilda_compositor_snapshot_popup (l->data, snapshot, gl_context, now, scale, popup_x, popup_y); + casilda_compositor_snapshot_popup (l->data, data, popup_x, popup_y); } static void -casilda_compositor_snapshot_toplevel (CasildaCompositorToplevel *toplevel, - GtkSnapshot *snapshot, - GdkGLContext *gl_context, - struct timespec *now, - double scale) +casilda_compositor_snapshot_toplevel (CasildaCompositorToplevel *toplevel, SnapshotData *data) { struct wlr_surface *surface; struct wlr_box tbox = {0, }; @@ -580,40 +573,51 @@ casilda_compositor_snapshot_toplevel (CasildaCompositorToplevel *toplevel, }); graphene_vec4_init (&offset, 0, 0, 0, 0); - gtk_snapshot_push_color_matrix (snapshot, &matrix, &offset); + gtk_snapshot_push_color_matrix (data->snapshot, &matrix, &offset); } - snapshot_surface (snapshot, gl_context, now, scale, surface, toplevel->x - tbox.x, toplevel->y - tbox.y); + snapshot_surface (surface, data, toplevel->x - tbox.x, toplevel->y - tbox.y); /* Recurse toplevel popups */ for (GList *l = toplevel->popups; l; l = g_list_next (l)) - casilda_compositor_snapshot_popup (l->data, snapshot, gl_context, now, scale, toplevel->x, toplevel->y); + casilda_compositor_snapshot_popup (l->data, data, toplevel->x, toplevel->y); if (has_transient) - gtk_snapshot_pop (snapshot); + gtk_snapshot_pop (data->snapshot); /* Recurse toplevel transient windows */ for (GList *l = toplevel->toplevels; l; l = g_list_next (l)) - casilda_compositor_snapshot_toplevel (l->data, snapshot, gl_context, now, scale); + casilda_compositor_snapshot_toplevel (l->data, data); } static void casilda_compositor_snapshot (GtkWidget *widget, GtkSnapshot *snapshot) { - double scale = gdk_surface_get_scale (gtk_native_get_surface (gtk_widget_get_native (widget))); CasildaCompositorPrivate *priv = GET_PRIVATE (widget); - struct timespec now; - clock_gettime (CLOCK_MONOTONIC, &now); + SnapshotData data = { snapshot, priv->gl_context, 1.0, {0, } }; + gint x, y; + + data.scale = gdk_surface_get_scale (gtk_native_get_surface (gtk_widget_get_native (widget))); + clock_gettime (CLOCK_MONOTONIC, &data.now); + + /* Translate by scrollable offset */ + x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]); + y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]); + + gtk_snapshot_save (snapshot); + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (-x, -y)); /* Iterate all toplevels */ for (GList *l = g_list_last (priv->toplevels); l; l = g_list_previous (l)) - casilda_compositor_snapshot_toplevel (l->data, snapshot, priv->gl_context, &now, scale); + casilda_compositor_snapshot_toplevel (l->data, &data); if (priv->frame_clock_updating) { gdk_frame_clock_end_updating (priv->frame_clock); priv->frame_clock_updating = FALSE; } + + gtk_snapshot_restore (snapshot); } static void @@ -628,6 +632,45 @@ casilda_compositor_queue_draw (CasildaCompositorPrivate *priv) gtk_widget_queue_draw (priv->self); } +static void compositor_update_adjustment_toplevel (CasildaCompositorPrivate *priv, CasildaCompositorToplevel *toplevel); + +static void +compositor_update_adjustment (CasildaCompositorPrivate *priv, GList *toplevels) +{ + for (GList *l = toplevels; l; l = g_list_next (l)) + { + CasildaCompositorToplevel *toplevel = l->data; + + compositor_update_adjustment_toplevel (priv, toplevel); + + if (toplevel->toplevels) + compositor_update_adjustment (priv, toplevel->toplevels); + } +} + +static void +compositor_update_adjustment_toplevel (CasildaCompositorPrivate *priv, CasildaCompositorToplevel *toplevel) +{ + struct wlr_box tbox = {0, }; + + if (!toplevel->xdg_toplevel) + return; + + wlr_xdg_surface_get_geometry (toplevel->xdg_toplevel->base, &tbox); + + gint w = toplevel->x + tbox.width; + gint h = toplevel->y + tbox.height; + + if (w > gtk_adjustment_get_upper (priv->adjustment[GTK_ORIENTATION_HORIZONTAL])) + gtk_adjustment_set_upper (priv->adjustment[GTK_ORIENTATION_HORIZONTAL], w); + + if (h > gtk_adjustment_get_upper (priv->adjustment[GTK_ORIENTATION_VERTICAL])) + gtk_adjustment_set_upper (priv->adjustment[GTK_ORIENTATION_VERTICAL], h); + + if (toplevel->toplevels) + compositor_update_adjustment (priv, toplevel->toplevels); +} + static void casilda_compositor_size_allocate (GtkWidget *widget, int w, int h, G_GNUC_UNUSED int b) { @@ -638,6 +681,17 @@ casilda_compositor_size_allocate (GtkWidget *widget, int w, int h, G_GNUC_UNUSED w = w * scale; h = h * scale; + /* Update scrollable values */ + gtk_adjustment_set_page_size (priv->adjustment[GTK_ORIENTATION_HORIZONTAL], w); + gtk_adjustment_set_page_size (priv->adjustment[GTK_ORIENTATION_VERTICAL], h); + gtk_adjustment_set_upper (priv->adjustment[GTK_ORIENTATION_HORIZONTAL], w); + gtk_adjustment_set_upper (priv->adjustment[GTK_ORIENTATION_VERTICAL], h); + + compositor_update_adjustment (priv, priv->toplevels); + + w = gtk_adjustment_get_upper (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]); + h = gtk_adjustment_get_upper (priv->adjustment[GTK_ORIENTATION_VERTICAL]); + wlr_output_state_init (&state); wlr_output_state_set_enabled (&state, true); wlr_output_state_set_custom_mode (&state, w, h, 0); @@ -649,6 +703,7 @@ casilda_compositor_size_allocate (GtkWidget *widget, int w, int h, G_GNUC_UNUSED for (GList *l = priv->toplevels; l; l = g_list_next (l)) { CasildaCompositorToplevel *toplevel = l->data; + if (toplevel->xdg_toplevel->current.maximized || toplevel->xdg_toplevel->current.fullscreen) casilda_compositor_toplevel_configure (toplevel, 0, 0, w, h); } @@ -797,6 +852,8 @@ casilda_compositor_toplevel_set_position (CasildaCompositorToplevel *toplevel, g toplevel->y = y; casilda_compositor_queue_draw (toplevel->priv); + + compositor_update_adjustment_toplevel (toplevel->priv, toplevel); } static void @@ -1064,8 +1121,8 @@ on_motion_controller_enter (G_GNUC_UNUSED GtkEventControllerMotion *self, gdouble y, CasildaCompositorPrivate *priv) { - priv->pointer_x = x; - priv->pointer_y = y; + priv->pointer_x = x + gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]); + priv->pointer_y = y + gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]); casilda_compositor_handle_pointer_motion (priv); wlr_seat_pointer_notify_frame (priv->seat); } @@ -1084,8 +1141,12 @@ on_motion_controller_motion (G_GNUC_UNUSED GtkEventControllerMotion *self, CasildaCompositorPrivate *priv) { /* Clamp pointer to widget coordinates */ - priv->pointer_x = CLAMP (x, 0, gtk_widget_get_width (priv->self)); - priv->pointer_y = CLAMP (y, 0, gtk_widget_get_height (priv->self)); + x = CLAMP (x, 0, gtk_widget_get_width (priv->self)); + y = CLAMP (y, 0, gtk_widget_get_height (priv->self)); + + /* Add scrollable offset */ + priv->pointer_x = x + gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]); + priv->pointer_y = y + gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]); casilda_compositor_handle_pointer_motion (priv); wlr_seat_pointer_notify_frame (priv->seat); @@ -1646,6 +1707,8 @@ casilda_compositor_dispose (GObject *object) g_clear_object (&priv->scroll_controller); g_clear_object (&priv->key_controller); g_clear_object (&priv->click_gesture); + g_clear_object (&priv->adjustment[GTK_ORIENTATION_HORIZONTAL]); + g_clear_object (&priv->adjustment[GTK_ORIENTATION_VERTICAL]); casilda_compositor_reset_cursor (priv); @@ -1676,6 +1739,34 @@ casilda_compositor_finalize (GObject *object) G_OBJECT_CLASS (casilda_compositor_parent_class)->finalize (object); } +static void +compositor_adjustment_value_changed (G_GNUC_UNUSED GtkAdjustment *adjustment, gpointer data) +{ + gtk_widget_queue_draw (GTK_WIDGET (data)); +} + +static void +compositor_set_adjustment (CasildaCompositor *compositor, GtkOrientation orientation, GtkAdjustment *adjustment) +{ + CasildaCompositorPrivate *priv = GET_PRIVATE (compositor); + GtkAdjustment **adjustment_prop = &priv->adjustment[orientation]; + + if (adjustment && adjustment == *adjustment_prop) + return; + + if (*adjustment_prop) + g_signal_handlers_disconnect_by_func (*adjustment_prop, compositor_adjustment_value_changed, compositor); + + if (!adjustment) + adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + + g_set_object (adjustment_prop, g_object_ref_sink (adjustment)); + + g_signal_connect (adjustment, "value-changed", G_CALLBACK (compositor_adjustment_value_changed), compositor); + + compositor_adjustment_value_changed (adjustment, compositor); +} + static void casilda_compositor_set_property (GObject *object, guint prop_id, @@ -1691,7 +1782,15 @@ casilda_compositor_set_property (GObject *object, case PROP_SOCKET: g_set_str (&priv->socket, g_value_get_string (value)); break; - + case PROP_HADJUSTMENT: + compositor_set_adjustment (CASILDA_COMPOSITOR (object), GTK_ORIENTATION_HORIZONTAL, g_value_get_object (value)); + break; + case PROP_VADJUSTMENT: + compositor_set_adjustment (CASILDA_COMPOSITOR (object), GTK_ORIENTATION_VERTICAL, g_value_get_object (value)); + break; + case PROP_HSCROLL_POLICY: + case PROP_VSCROLL_POLICY: + break; case PROP_BG_COLOR: break; @@ -1717,7 +1816,16 @@ casilda_compositor_get_property (GObject *object, case PROP_SOCKET: g_value_set_string (value, priv->socket); break; - + case PROP_HADJUSTMENT: + g_value_set_object (value, priv->adjustment[GTK_ORIENTATION_HORIZONTAL]); + break; + case PROP_VADJUSTMENT: + g_value_set_object (value, priv->adjustment[GTK_ORIENTATION_VERTICAL]); + break; + case PROP_HSCROLL_POLICY: + case PROP_VSCROLL_POLICY: + g_value_set_enum (value, GTK_SCROLL_NATURAL); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1776,6 +1884,12 @@ casilda_compositor_class_init (CasildaCompositorClass *klass) widget_class->realize = casilda_compositor_realize; widget_class->unrealize = casilda_compositor_unrealize; + /* GtkScrollable implementation */ + g_object_class_override_property (object_class, PROP_HADJUSTMENT, "hadjustment"); + g_object_class_override_property (object_class, PROP_VADJUSTMENT, "vadjustment"); + g_object_class_override_property (object_class, PROP_HSCROLL_POLICY, "hscroll-policy"); + g_object_class_override_property (object_class, PROP_VSCROLL_POLICY, "vscroll-policy"); + /* Properties */ /** @@ -1818,9 +1932,12 @@ static void xdg_toplevel_map (struct wl_listener *listener, G_GNUC_UNUSED void *data) { CasildaCompositorToplevel *toplevel = wl_container_of (listener, toplevel, map); + CasildaCompositorPrivate *priv = toplevel->priv; struct wlr_xdg_toplevel *xdg_toplevel = toplevel->xdg_toplevel; CasildaCompositorToplevelState *state = toplevel->state; struct wlr_box box; + gint offset_x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]); + gint offset_y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]); wlr_xdg_surface_get_geometry (xdg_toplevel->base, &box); @@ -1835,17 +1952,17 @@ xdg_toplevel_map (struct wl_listener *listener, G_GNUC_UNUSED void *data) wlr_xdg_surface_get_geometry (parent->xdg_toplevel->base, &parent_box); /* Start in the middle of the parent window and cap to 0,0 */ - toplevel->x = MAX(0, parent->x + (parent_box.width - box.width) / 2); - toplevel->y = MAX(0, parent->y + (parent_box.height - box.height) / 2); + toplevel->x = MAX(offset_x, parent->x + (parent_box.width - box.width) / 2); + toplevel->y = MAX(offset_y, parent->y + (parent_box.height - box.height) / 2); parent->toplevels = g_list_prepend (parent->toplevels, toplevel); } } else { - /* Start in the middle of the compositor */ - toplevel->x = MAX(0, (gtk_widget_get_width (toplevel->priv->self) - box.width) / 2); - toplevel->y = MAX(0, (gtk_widget_get_height (toplevel->priv->self) - box.height) / 2); + /* Start in the middle of the compositor viewport */ + toplevel->x = offset_x + MAX(0, (gtk_widget_get_width (toplevel->priv->self) - box.width) / 2); + toplevel->y = offset_y + MAX(0, (gtk_widget_get_height (toplevel->priv->self) - box.height) / 2); toplevel->priv->toplevels = g_list_prepend (toplevel->priv->toplevels, toplevel); } @@ -2299,9 +2416,9 @@ on_gl_message_callback (G_GNUC_UNUSED GLenum source, G_GNUC_UNUSED const void* userParam) { if (type == GL_DEBUG_TYPE_ERROR) - g_warning("GL: type = 0x%x, severity = 0x%x, message = %s\n", type, severity, message); + g_warning ("GL: type = 0x%x, severity = 0x%x, message = %s\n", type, severity, message); else - g_message("GL: type = 0x%x, severity = 0x%x, message = %s\n", type, severity, message); + g_message ("GL: type = 0x%x, severity = 0x%x, message = %s\n", type, severity, message); } #endif