diff --git a/lib/meson.build b/lib/meson.build index 1402c32..bb46ee0 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -34,6 +34,7 @@ libmks_private_sources = [ 'mks-gl-context.c', 'mks-inhibitor.c', 'mks-read-only-list-model.c', + 'mks-screen-resizer.c', 'mks-util.c', gnome.gdbus_codegen('mks-qemu', diff --git a/lib/mks-display.c b/lib/mks-display.c index 9bde48c..b15921a 100644 --- a/lib/mks-display.c +++ b/lib/mks-display.c @@ -30,6 +30,8 @@ #include "mks-mouse.h" #include "mks-paintable-private.h" #include "mks-screen.h" +#include "mks-screen-attributes.h" +#include "mks-screen-resizer-private.h" #include "mks-util-private.h" #define DEFAULT_UNGRAB_TRIGGER "g" @@ -37,6 +39,7 @@ typedef struct { MksScreen *screen; + MksScreenResizer *resizer; MksDisplayPicture *picture; MksInhibitor *inhibitor; GtkShortcutTrigger *ungrab_trigger; @@ -135,6 +138,7 @@ mks_display_connect (MksDisplay *self, { mks_display_picture_set_keyboard (priv->picture, mks_screen_get_keyboard (screen)); mks_display_picture_set_mouse (priv->picture, mks_screen_get_mouse (screen)); + mks_screen_resizer_set_screen (priv->resizer, screen); mks_screen_attach (screen, NULL, @@ -157,6 +161,7 @@ mks_display_disconnect (MksDisplay *self) g_assert (MKS_IS_DISPLAY (self)); g_clear_object (&priv->screen); + mks_screen_resizer_set_screen (priv->resizer, NULL); g_clear_object (&priv->inhibitor); if (priv->picture != NULL) @@ -228,6 +233,7 @@ mks_display_dispose (GObject *object) mks_display_disconnect (self); g_clear_pointer ((GtkWidget **)&priv->picture, gtk_widget_unparent); + g_clear_object (&priv->resizer); G_OBJECT_CLASS (mks_display_parent_class)->dispose (object); } @@ -288,6 +294,7 @@ mks_display_size_allocate (GtkWidget *widget, MksDisplay *self = (MksDisplay *)widget; MksDisplayPrivate *priv = mks_display_get_instance_private (self); graphene_rect_t area; + MksScreenAttributes *attributes; g_assert (MKS_IS_DISPLAY (self)); @@ -295,6 +302,13 @@ mks_display_size_allocate (GtkWidget *widget, mks_display_get_paintable_area (self, &area); + attributes = mks_screen_attributes_new (); + mks_screen_attributes_set_width (attributes, width); + mks_screen_attributes_set_height (attributes, height); + + mks_screen_resizer_queue_resize (priv->resizer, + g_steal_pointer (&attributes)); + gtk_widget_size_allocate (GTK_WIDGET (priv->picture), &(GtkAllocation) { area.origin.x, @@ -303,6 +317,7 @@ mks_display_size_allocate (GtkWidget *widget, area.size.height }, -1); + mks_screen_attributes_free (attributes); } static void @@ -391,6 +406,7 @@ mks_display_init (MksDisplay *self) GtkEventController *controller; priv->picture = g_object_new (MKS_TYPE_DISPLAY_PICTURE, NULL); + priv->resizer = mks_screen_resizer_new (); gtk_widget_set_parent (GTK_WIDGET (priv->picture), GTK_WIDGET (self)); controller = gtk_event_controller_legacy_new (); diff --git a/lib/mks-screen-attributes.c b/lib/mks-screen-attributes.c index 3edc519..c957ebb 100644 --- a/lib/mks-screen-attributes.c +++ b/lib/mks-screen-attributes.c @@ -72,6 +72,28 @@ mks_screen_attributes_free (MksScreenAttributes *self) g_free (self); } +/** + * mks_screen_attributes_equal: + * @self: a #MksScreenAttributes + * @other: a #MksScreenAttributes + * + * Returns `true` if the two attributes are equal, `false` otherwise. + */ +gboolean +mks_screen_attributes_equal (MksScreenAttributes *self, + MksScreenAttributes *other) +{ + if (self == NULL || other == NULL) + return FALSE; + + return (self->width == other->width && + self->height == other->height && + self->x_offset == other->x_offset && + self->y_offset == other->y_offset && + self->width_mm == other->width_mm && + self->height_mm == other->height_mm); +} + void mks_screen_attributes_set_width_mm (MksScreenAttributes *self, guint16 width_mm) diff --git a/lib/mks-screen-attributes.h b/lib/mks-screen-attributes.h index 22e20e6..2b0d091 100644 --- a/lib/mks-screen-attributes.h +++ b/lib/mks-screen-attributes.h @@ -43,6 +43,9 @@ MksScreenAttributes *mks_screen_attributes_copy (MksScreenAttributes *s MKS_AVAILABLE_IN_ALL void mks_screen_attributes_free (MksScreenAttributes *self); MKS_AVAILABLE_IN_ALL +gboolean mks_screen_attributes_equal (MksScreenAttributes *self, + MksScreenAttributes *other); +MKS_AVAILABLE_IN_ALL void mks_screen_attributes_set_width_mm (MksScreenAttributes *self, guint16 width_mm); MKS_AVAILABLE_IN_ALL diff --git a/lib/mks-screen-resizer-private.h b/lib/mks-screen-resizer-private.h new file mode 100644 index 0000000..445c68c --- /dev/null +++ b/lib/mks-screen-resizer-private.h @@ -0,0 +1,38 @@ +/* + * mks-screen-resizer-private.h + * + * Copyright 2023 Bilal Elmoussaoui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "mks-screen.h" + +G_BEGIN_DECLS + +#define MKS_TYPE_SCREEN_RESIZER (mks_screen_resizer_get_type()) + +G_DECLARE_FINAL_TYPE (MksScreenResizer, mks_screen_resizer, MKS, SCREEN_RESIZER, GObject) + +MksScreenResizer *mks_screen_resizer_new (void); +void mks_screen_resizer_set_screen (MksScreenResizer *self, + MksScreen *screen); +void mks_screen_resizer_queue_resize (MksScreenResizer *self, + MksScreenAttributes *attributes); + +G_END_DECLS diff --git a/lib/mks-screen-resizer.c b/lib/mks-screen-resizer.c new file mode 100644 index 0000000..746a70d --- /dev/null +++ b/lib/mks-screen-resizer.c @@ -0,0 +1,238 @@ +/* + * mks-screen-resizer.c + * + * Copyright 2023 Bilal Elmoussaoui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "mks-screen-attributes.h" +#include "mks-screen-resizer-private.h" +#include "mks-util-private.h" + +static void +mks_screen_resizer_reconfigure (MksScreenResizer *self, + MksScreenAttributes *attributes); + +struct _MksScreenResizer +{ + GObject parent_instance; + + MksScreen *screen; + + /* Remember our last operation */ + MksScreenAttributes *next_op; + MksScreenAttributes *previous_op; + gboolean in_progress; +}; + +enum { + PROP_0, + PROP_SCREEN, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (MksScreenResizer, mks_screen_resizer, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +mks_screen_resizer_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MksScreenResizer *self = MKS_SCREEN_RESIZER (object); + + switch (prop_id) + { + case PROP_SCREEN: + g_value_set_object (value, self->screen); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +mks_screen_resizer_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MksScreenResizer *self = MKS_SCREEN_RESIZER (object); + + switch (prop_id) + { + case PROP_SCREEN: + mks_screen_resizer_set_screen (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +mks_screen_resizer_dispose (GObject *object) +{ + MksScreenResizer *self = (MksScreenResizer *)object; + + g_clear_object (&self->screen); + g_clear_pointer (&self->next_op, mks_screen_attributes_free); + g_clear_pointer (&self->previous_op, mks_screen_attributes_free); + + G_OBJECT_CLASS (mks_screen_resizer_parent_class)->dispose (object); +} + +static void +mks_screen_resizer_class_init (MksScreenResizerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->dispose = mks_screen_resizer_dispose; + object_class->get_property = mks_screen_resizer_get_property; + object_class->set_property = mks_screen_resizer_set_property; + + properties[PROP_SCREEN] = + g_param_spec_object ("screen", NULL, NULL, + MKS_TYPE_SCREEN, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +on_screen_configure_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + MksScreen *screen = (MksScreen *)object; + g_autoptr(MksScreenResizer) self = user_data; + g_autoptr(GError) error = NULL; + g_autoptr(MksScreenAttributes) attributes = NULL; + + MKS_ENTRY; + + g_assert (MKS_IS_SCREEN (screen)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (MKS_IS_SCREEN_RESIZER (self)); + + if (!mks_screen_configure_finish (screen, result, &error)) + g_warning ("Screen configure failed: %s", error->message); + + self->in_progress = FALSE; + attributes = g_steal_pointer (&self->next_op); + if (attributes != NULL && + !mks_screen_attributes_equal (attributes, self->previous_op)) + { + mks_screen_resizer_reconfigure (self, + g_steal_pointer (&attributes)); + } + MKS_EXIT; +} + +/** + * mks_screen_resizer_new: + * + * Returns: (transfer full): a new #MksScreenResizer + */ +MksScreenResizer * +mks_screen_resizer_new (void) +{ + MksScreenResizer *self; + + self = g_object_new (MKS_TYPE_SCREEN_RESIZER, NULL); + self->next_op = NULL; + self->previous_op = NULL; + self->in_progress = FALSE; + return self; +} + + +static void +mks_screen_resizer_init (MksScreenResizer *self) +{ +} + +/** + * mks_screen_resizer_set_screen: + * @self: A `MksScreenResizer` + * @screen: A `MksScreen` + * + * Sets the screen to resize when a resize is queued. +*/ +void +mks_screen_resizer_set_screen (MksScreenResizer *self, + MksScreen *screen) +{ + g_return_if_fail (MKS_IS_SCREEN_RESIZER (self)); + g_return_if_fail (!screen || MKS_IS_SCREEN (screen)); + + if (g_set_object (&self->screen, screen)) + { + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SCREEN]); + } +} + +/** + * mks_screen_resizer_queue_resize: + * @self: A `MksScreenResizer` + * @attributes: (transfer full): The new attributes to queue + * + * Schedule the VM display configuration with the passed attributes if + * there is no ongoing operation. Otherwise, add the attributes to + * a queue of updates. +*/ +void +mks_screen_resizer_queue_resize (MksScreenResizer *self, + MksScreenAttributes *attributes) +{ + g_return_if_fail (MKS_IS_SCREEN_RESIZER (self)); + + if (mks_screen_attributes_equal (attributes, self->previous_op)) + return; + + if (self->in_progress) + { + g_clear_pointer (&self->next_op, mks_screen_attributes_free); + self->next_op = g_steal_pointer (&attributes); + return; + } + mks_screen_resizer_reconfigure (self, attributes); +} + +/** + * mks_screen_resizer_reconfigure: + * @self: A `MksScreenResizer` + * @attributes: (transfer full): The attributes to reconfigure + * + * Configure the screen with the passed attributes. +*/ +static void +mks_screen_resizer_reconfigure (MksScreenResizer *self, + MksScreenAttributes *attributes) +{ + g_assert (MKS_IS_SCREEN_RESIZER (self)); + + self->in_progress = TRUE; + mks_screen_configure (self->screen, + g_steal_pointer (&attributes), + NULL, + on_screen_configure_cb, + g_object_ref (self)); + g_clear_pointer (&self->previous_op, mks_screen_attributes_free); + self->previous_op = g_steal_pointer (&attributes); +}