diff --git a/lib/meson.build b/lib/meson.build index 652283c..3635ad3 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -26,6 +26,7 @@ libmks_headers = [ libmks_private_sources = [ 'mks-cairo-framebuffer.c', 'mks-dmabuf-paintable.c', + 'mks-display-picture.c', 'mks-css.c', 'mks-gl-context.c', 'mks-read-only-list-model.c', diff --git a/lib/mks-display-picture-private.h b/lib/mks-display-picture-private.h new file mode 100644 index 0000000..a4b1b93 --- /dev/null +++ b/lib/mks-display-picture-private.h @@ -0,0 +1,45 @@ +/* mks-display-picture.h + * + * Copyright 2023 Christian Hergert + * + * 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 + +#include "mks-paintable-private.h" +#include "mks-types.h" + +G_BEGIN_DECLS + +#define MKS_TYPE_DISPLAY_PICTURE (mks_display_picture_get_type()) + +G_DECLARE_FINAL_TYPE (MksDisplayPicture, mks_display_picture, MKS, DISPLAY_PICTURE, GtkWidget) + +GtkWidget *mks_display_picture_new (void); +MksPaintable *mks_display_picture_get_paintable (MksDisplayPicture *self); +void mks_display_picture_set_paintable (MksDisplayPicture *self, + MksPaintable *paintable); +MksMouse *mks_display_picture_get_mouse (MksDisplayPicture *self); +void mks_display_picture_set_mouse (MksDisplayPicture *self, + MksMouse *mouse); +MksKeyboard *mks_display_picture_get_keyboard (MksDisplayPicture *self); +void mks_display_picture_set_keyboard (MksDisplayPicture *self, + MksKeyboard *keyboard); + +G_END_DECLS diff --git a/lib/mks-display-picture.c b/lib/mks-display-picture.c new file mode 100644 index 0000000..06d7049 --- /dev/null +++ b/lib/mks-display-picture.c @@ -0,0 +1,731 @@ +/* mks-display-picture.c + * + * Copyright 2023 Christian Hergert + * + * 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 + */ + +#define G_LOG_DOMAIN "mks-display-picture" + +#include "config.h" + +#include "mks-display-picture-private.h" +#include "mks-keyboard.h" +#include "mks-mouse.h" +#include "mks-util-private.h" + +#include "mks-keymap-xorgevdev2qnum-private.h" + +struct _MksDisplayPicture +{ + GtkWidget parent_instance; + + GSignalGroup *paintable_signals; + MksPaintable *paintable; + MksKeyboard *keyboard; + MksMouse *mouse; + + double last_mouse_x; + double last_mouse_y; +}; + +enum { + PROP_0, + PROP_PAINTABLE, + PROP_KEYBOARD, + PROP_MOUSE, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (MksDisplayPicture, mks_display_picture, GTK_TYPE_WIDGET) + +static GParamSpec *properties [N_PROPS]; + +static void +mks_display_picture_translate_keycode (MksDisplayPicture *self, + guint keyval, + guint keycode, + guint *translated) +{ + g_assert (MKS_IS_DISPLAY_PICTURE (self)); + g_assert (translated != NULL); + + if (keycode < xorgevdev_to_qnum_len && + xorgevdev_to_qnum[keycode] != 0) + *translated = xorgevdev_to_qnum[keycode]; + else + *translated = keycode; +} + +static void +mks_display_picture_keyboard_press_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + MksKeyboard *keyboard = (MksKeyboard *)object; + g_autoptr(MksDisplayPicture) self = user_data; + g_autoptr(GError) error = NULL; + + g_assert (MKS_IS_KEYBOARD (keyboard)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (MKS_IS_DISPLAY_PICTURE (self)); + + if (!mks_keyboard_press_finish (keyboard, result, &error)) + g_warning ("Keyboard press failed: %s", error->message); +} + +static void +mks_display_picture_keyboard_release_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + MksKeyboard *keyboard = (MksKeyboard *)object; + g_autoptr(MksDisplayPicture) self = user_data; + g_autoptr(GError) error = NULL; + + g_assert (MKS_IS_KEYBOARD (keyboard)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (MKS_IS_DISPLAY_PICTURE (self)); + + if (!mks_keyboard_release_finish (keyboard, result, &error)) + g_warning ("Keyboard release failed: %s", error->message); +} + +static void +mks_display_picture_mouse_move_to_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + MksMouse *mouse = (MksMouse *)object; + g_autoptr(MksDisplayPicture) self = user_data; + g_autoptr(GError) error = NULL; + + g_assert (MKS_IS_MOUSE (mouse)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (MKS_IS_DISPLAY_PICTURE (self)); + + if (!mks_mouse_move_to_finish (mouse, result, &error)) + g_warning ("Failed move_to: %s", error->message); +} + +static void +mks_display_picture_mouse_move_by_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + MksMouse *mouse = (MksMouse *)object; + g_autoptr(MksDisplayPicture) self = user_data; + g_autoptr(GError) error = NULL; + + g_assert (MKS_IS_MOUSE (mouse)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (MKS_IS_DISPLAY_PICTURE (self)); + + if (!mks_mouse_move_by_finish (mouse, result, &error)) + g_warning ("Failed move_by: %s", error->message); +} + +static void +mks_display_picture_translate_button (MksDisplayPicture *self, + int *button) +{ + g_assert (MKS_IS_DISPLAY_PICTURE (self)); + g_assert (button != NULL); + + switch (*button) + { + case 1: *button = MKS_MOUSE_BUTTON_LEFT; break; + case 2: *button = MKS_MOUSE_BUTTON_MIDDLE; break; + case 3: *button = MKS_MOUSE_BUTTON_RIGHT; break; + case 8: *button = MKS_MOUSE_BUTTON_SIDE; break; + case 9: *button = MKS_MOUSE_BUTTON_EXTRA; break; + default: break; + } +} + +static void +mks_display_picture_mouse_press_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + MksMouse *mouse = (MksMouse *)object; + g_autoptr(MksDisplayPicture) self = user_data; + g_autoptr(GError) error = NULL; + + g_assert (MKS_IS_MOUSE (mouse)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (MKS_IS_DISPLAY_PICTURE (self)); + + if (!mks_mouse_press_finish (mouse, result, &error)) + g_warning ("Mouse press failed: %s", error->message); +} + +static void +mks_display_picture_mouse_release_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + MksMouse *mouse = (MksMouse *)object; + g_autoptr(MksDisplayPicture) self = user_data; + g_autoptr(GError) error = NULL; + + g_assert (MKS_IS_MOUSE (mouse)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (MKS_IS_DISPLAY_PICTURE (self)); + + if (!mks_mouse_release_finish (mouse, result, &error)) + g_warning ("Mouse release failed: %s", error->message); +} + + +static gboolean +mks_display_picture_legacy_event_cb (MksDisplayPicture *self, + GdkEvent *event, + GtkEventControllerLegacy *controller) +{ + GdkPaintable *paintable; + GdkEventType event_type; + + g_assert (MKS_IS_DISPLAY_PICTURE (self)); + g_assert (event != NULL); + g_assert (GTK_IS_EVENT_CONTROLLER_LEGACY (controller)); + + if (self->keyboard == NULL || self->mouse == NULL || self->paintable == NULL) + return GDK_EVENT_PROPAGATE; + + event_type = gdk_event_get_event_type (event); + paintable = GDK_PAINTABLE (self->paintable); + + switch ((int)event_type) + { + case GDK_MOTION_NOTIFY: + { + GdkSurface *surface = gdk_event_get_surface (event); + GtkNative *native = gtk_widget_get_native (GTK_WIDGET (self)); + int guest_width = gdk_paintable_get_intrinsic_width (paintable); + int guest_height = gdk_paintable_get_intrinsic_height (paintable); + graphene_rect_t area; + GtkAllocation alloc; + double translate_x; + double translate_y; + + g_assert (MKS_IS_MOUSE (self->mouse)); + g_assert (GDK_IS_SURFACE (surface)); + + gtk_widget_get_allocation (GTK_WIDGET (self), &alloc); + area = GRAPHENE_RECT_INIT (0, 0, alloc.width, alloc.height); + + gtk_native_get_surface_transform (native, &translate_x, &translate_y); + + if (mks_mouse_get_is_absolute (self->mouse)) + { + gdouble x, y; + + if (gdk_event_get_position (event, &x, &y)) + { + double guest_x, guest_y; + + x -= translate_x; + y -= translate_y; + + gtk_widget_translate_coordinates (GTK_WIDGET (native), + GTK_WIDGET (self), + x, y, &x, &y); + + guest_x = floor (x) / area.size.width * guest_width; + guest_y = floor (y) / area.size.height * guest_height; + + guest_x = CLAMP (guest_x, 0, guest_width); + guest_y = CLAMP (guest_y, 0, guest_width); + + mks_mouse_move_to (self->mouse, + guest_x, + guest_y, + NULL, + mks_display_picture_mouse_move_to_cb, + g_object_ref (self)); + + return GDK_EVENT_STOP; + } + } + else + { + double x, y; + + if (gdk_event_get_axis (event, GDK_AXIS_X, &x) && + gdk_event_get_axis (event, GDK_AXIS_Y, &y)) + { + double delta_x = self->last_mouse_x - (x / area.size.width) * guest_width; + double delta_y = self->last_mouse_y - (y / area.size.height) * guest_height; + + mks_mouse_move_by (self->mouse, + delta_x, + delta_y, + NULL, + mks_display_picture_mouse_move_by_cb, + g_object_ref (self)); + + return GDK_EVENT_STOP; + } + } + + break; + } + + case GDK_BUTTON_PRESS: + case GDK_BUTTON_RELEASE: + { + int button = gdk_button_event_get_button (event); + + g_assert (MKS_IS_MOUSE (self->mouse)); + + mks_display_picture_translate_button (self, &button); + + if (event_type == GDK_BUTTON_PRESS) + mks_mouse_press (self->mouse, + button, + NULL, + mks_display_picture_mouse_press_cb, + g_object_ref (self)); + else + mks_mouse_release (self->mouse, + button, + NULL, + mks_display_picture_mouse_release_cb, + g_object_ref (self)); + + return GDK_EVENT_STOP; + } + + case GDK_KEY_PRESS: + case GDK_KEY_RELEASE: + { + guint keycode = gdk_key_event_get_keycode (event); + guint keyval = gdk_key_event_get_keycode (event); + guint qkeycode; + + g_assert (MKS_IS_KEYBOARD (self->keyboard)); + + mks_display_picture_translate_keycode (self, keyval, keycode, &qkeycode); + + if (event_type == GDK_KEY_PRESS) + mks_keyboard_press (self->keyboard, + qkeycode, + NULL, + mks_display_picture_keyboard_press_cb, + g_object_ref (self)); + else + mks_keyboard_release (self->keyboard, + qkeycode, + NULL, + mks_display_picture_keyboard_release_cb, + g_object_ref (self)); + + return GDK_EVENT_STOP; + } + + case GDK_SCROLL: + { + GdkScrollDirection direction = gdk_scroll_event_get_direction (event); + gboolean inverted = mks_scroll_event_is_inverted (event); + int button = -1; + + g_assert (MKS_IS_MOUSE (self->mouse)); + + switch (direction) + { + case GDK_SCROLL_UP: + button = MKS_MOUSE_BUTTON_WHEEL_UP; + break; + + case GDK_SCROLL_DOWN: + button = MKS_MOUSE_BUTTON_WHEEL_DOWN; + break; + + case GDK_SCROLL_LEFT: + case GDK_SCROLL_RIGHT: + case GDK_SCROLL_SMOOTH: + default: + break; + } + + if (button != -1) + { + if (inverted) + { + if (button == MKS_MOUSE_BUTTON_WHEEL_UP) + button = MKS_MOUSE_BUTTON_WHEEL_DOWN; + else if (button == MKS_MOUSE_BUTTON_WHEEL_DOWN) + button = MKS_MOUSE_BUTTON_WHEEL_UP; + } + + mks_mouse_press (self->mouse, + button, + NULL, + mks_display_picture_mouse_press_cb, + g_object_ref (self)); + + return GDK_EVENT_STOP; + } + + break; + } + + default: + break; + } + + return GDK_EVENT_PROPAGATE; +} + +static void +mks_display_picture_invalidate_contents_cb (MksDisplayPicture *self, + MksPaintable *paintable) +{ + g_assert (MKS_IS_DISPLAY_PICTURE (self)); + g_assert (MKS_IS_PAINTABLE (paintable)); + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +mks_display_picture_invalidate_size_cb (MksDisplayPicture *self, + MksPaintable *paintable) +{ + g_assert (MKS_IS_DISPLAY_PICTURE (self)); + g_assert (MKS_IS_PAINTABLE (paintable)); + + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +static void +mks_display_picture_notify_cursor_cb (MksDisplayPicture *self, + GParamSpec *pspec, + MksPaintable *paintable) +{ + GdkCursor *cursor; + + g_assert (MKS_IS_DISPLAY_PICTURE (self)); + g_assert (MKS_IS_PAINTABLE (paintable)); + + cursor = _mks_paintable_get_cursor (paintable); + + gtk_widget_set_cursor (GTK_WIDGET (self), cursor); +} + +static void +mks_display_picture_mouse_set_cb (MksDisplayPicture *self, + int x, + int y, + MksPaintable *paintable) +{ + g_assert (MKS_IS_DISPLAY_PICTURE (self)); + g_assert (MKS_IS_PAINTABLE (paintable)); + + self->last_mouse_x = x; + self->last_mouse_y = y; +} + +static void +mks_display_picture_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + MksDisplayPicture *self = (MksDisplayPicture *)widget; + GdkPaintable *paintable; + double min_width, min_height, nat_width, nat_height; + int default_width; + int default_height; + + g_assert (MKS_IS_DISPLAY_PICTURE (self)); + + if (self->paintable == NULL || for_size == 0) + { + *minimum = 0; + *natural = 0; + return; + } + + paintable = GDK_PAINTABLE (self->paintable); + + default_width = gdk_paintable_get_intrinsic_width (paintable); + default_height = gdk_paintable_get_intrinsic_width (paintable); + + if (default_width <= 0) + default_width = 640; + + if (default_height <= 0) + default_height = 480; + + gdk_paintable_compute_concrete_size (paintable, + 0, 0, + default_width, default_height, + &min_width, &min_height); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + gdk_paintable_compute_concrete_size (paintable, + 0, + for_size < 0 ? 0 : for_size, + default_width, default_height, + &nat_width, &nat_height); + *minimum = 0; + *natural = ceil (nat_width); + } + else + { + gdk_paintable_compute_concrete_size (paintable, + for_size < 0 ? 0 : for_size, + 0, + default_width, default_height, + &nat_width, &nat_height); + *minimum = 0; + *natural = ceil (nat_height); + } +} + +static GtkSizeRequestMode +mks_display_picture_get_request_mode (GtkWidget *widget) +{ + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; +} + +static void +mks_display_picture_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + MksDisplayPicture *self = (MksDisplayPicture *)widget; + GtkAllocation alloc; + + if (self->paintable == NULL) + return; + + gtk_widget_get_allocation (widget, &alloc); + gdk_paintable_snapshot (GDK_PAINTABLE (self->paintable), + snapshot, + alloc.width, + alloc.height); +} + +static void +mks_display_picture_dispose (GObject *object) +{ + MksDisplayPicture *self = (MksDisplayPicture *)object; + + g_clear_object (&self->paintable); + g_clear_object (&self->keyboard); + g_clear_object (&self->mouse); + g_clear_object (&self->paintable_signals); + + G_OBJECT_CLASS (mks_display_picture_parent_class)->dispose (object); +} + +static void +mks_display_picture_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MksDisplayPicture *self = MKS_DISPLAY_PICTURE (object); + + switch (prop_id) + { + case PROP_KEYBOARD: + g_value_set_object (value, mks_display_picture_get_keyboard (self)); + break; + + case PROP_MOUSE: + g_value_set_object (value, mks_display_picture_get_mouse (self)); + break; + + case PROP_PAINTABLE: + g_value_set_object (value, mks_display_picture_get_paintable (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +mks_display_picture_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MksDisplayPicture *self = MKS_DISPLAY_PICTURE (object); + + switch (prop_id) + { + case PROP_KEYBOARD: + mks_display_picture_set_keyboard (self, g_value_get_object (value)); + break; + + case PROP_MOUSE: + mks_display_picture_set_mouse (self, g_value_get_object (value)); + break; + + case PROP_PAINTABLE: + mks_display_picture_set_paintable (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +mks_display_picture_class_init (MksDisplayPictureClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = mks_display_picture_dispose; + object_class->get_property = mks_display_picture_get_property; + object_class->set_property = mks_display_picture_set_property; + + widget_class->measure = mks_display_picture_measure; + widget_class->get_request_mode = mks_display_picture_get_request_mode; + widget_class->snapshot = mks_display_picture_snapshot; + + properties[PROP_KEYBOARD] = + g_param_spec_object ("keyboard", NULL, NULL, + MKS_TYPE_KEYBOARD, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_MOUSE] = + g_param_spec_object ("mouse", NULL, NULL, + MKS_TYPE_MOUSE, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_PAINTABLE] = + g_param_spec_object ("paintable", NULL, NULL, + MKS_TYPE_PAINTABLE, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +mks_display_picture_init (MksDisplayPicture *self) +{ + GtkEventController *controller; + + controller = gtk_event_controller_legacy_new (); + g_signal_connect_object (controller, + "event", + G_CALLBACK (mks_display_picture_legacy_event_cb), + self, + G_CONNECT_SWAPPED); + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE); + gtk_widget_add_controller (GTK_WIDGET (self), controller); + + self->paintable_signals = g_signal_group_new (MKS_TYPE_PAINTABLE); + g_signal_group_connect_object (self->paintable_signals, + "invalidate-contents", + G_CALLBACK (mks_display_picture_invalidate_contents_cb), + self, + G_CONNECT_SWAPPED); + g_signal_group_connect_object (self->paintable_signals, + "invalidate-size", + G_CALLBACK (mks_display_picture_invalidate_size_cb), + self, + G_CONNECT_SWAPPED); + g_signal_group_connect_object (self->paintable_signals, + "notify::cursor", + G_CALLBACK (mks_display_picture_notify_cursor_cb), + self, + G_CONNECT_SWAPPED); + g_signal_group_connect_object (self->paintable_signals, + "mouse-set", + G_CALLBACK (mks_display_picture_mouse_set_cb), + self, + G_CONNECT_SWAPPED); + + gtk_widget_set_focusable (GTK_WIDGET (self), TRUE); + gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "none"); +} + +GtkWidget * +mks_display_picture_new (void) +{ + return g_object_new (MKS_TYPE_DISPLAY_PICTURE, NULL); +} + +MksPaintable * +mks_display_picture_get_paintable (MksDisplayPicture *self) +{ + g_return_val_if_fail (MKS_IS_DISPLAY_PICTURE (self), NULL); + + return self->paintable; +} + +void +mks_display_picture_set_paintable (MksDisplayPicture *self, + MksPaintable *paintable) +{ + g_return_if_fail (MKS_IS_DISPLAY_PICTURE (self)); + g_return_if_fail (!paintable || MKS_IS_PAINTABLE (paintable)); + + if (g_set_object (&self->paintable, paintable)) + { + g_signal_group_set_target (self->paintable_signals, paintable); + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PAINTABLE]); + gtk_widget_queue_resize (GTK_WIDGET (self)); + } +} + +MksMouse * +mks_display_picture_get_mouse (MksDisplayPicture *self) +{ + g_return_val_if_fail (MKS_IS_DISPLAY_PICTURE (self), NULL); + + return self->mouse; +} + +void +mks_display_picture_set_mouse (MksDisplayPicture *self, + MksMouse *mouse) +{ + g_return_if_fail (MKS_IS_DISPLAY_PICTURE (self)); + g_return_if_fail (!mouse || MKS_IS_MOUSE (mouse)); + + if (g_set_object (&self->mouse, mouse)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MOUSE]); +} + +MksKeyboard * +mks_display_picture_get_keyboard (MksDisplayPicture *self) +{ + g_return_val_if_fail (MKS_IS_DISPLAY_PICTURE (self), NULL); + + return self->keyboard; +} + +void +mks_display_picture_set_keyboard (MksDisplayPicture *self, + MksKeyboard *keyboard) +{ + g_return_if_fail (MKS_IS_DISPLAY_PICTURE (self)); + g_return_if_fail (!keyboard || MKS_IS_KEYBOARD (keyboard)); + + if (g_set_object (&self->keyboard, keyboard)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_KEYBOARD]); +} diff --git a/lib/mks-display.c b/lib/mks-display.c index 6ce595a..93dd70e 100644 --- a/lib/mks-display.c +++ b/lib/mks-display.c @@ -24,38 +24,20 @@ #include "mks-css-private.h" #include "mks-display.h" +#include "mks-display-picture-private.h" #include "mks-keyboard.h" #include "mks-mouse.h" #include "mks-paintable-private.h" #include "mks-screen.h" -#include "mks-util-private.h" - -#include "mks-keymap-xorgevdev2qnum-private.h" typedef struct { - /* The screen being displayed. We've gotten a GdkPaintable from it - * which is connected to @picture for display. - */ - MksScreen *screen; - - /* The paintable containing the screen content */ - GdkPaintable *paintable; - gulong invalidate_contents_handler; - gulong invalidate_size_handler; - gulong notify_cursor_handler; - gulong mouse_set_handler; - - /* Tracking the last known positions of mouse events so that we may - * do something "reasonable" if the pointer is not absolute. - */ - double last_mouse_x; - double last_mouse_y; + MksScreen *screen; + MksDisplayPicture *picture; } MksDisplayPrivate; enum { PROP_0, - PROP_PAINTABLE, PROP_SCREEN, N_PROPS }; @@ -69,6 +51,7 @@ mks_display_get_paintable_area (MksDisplay *self, graphene_rect_t *area) { MksDisplayPrivate *priv = mks_display_get_instance_private (self); + MksPaintable *paintable; int x, y, width, height; double display_ratio; double ratio; @@ -80,7 +63,11 @@ mks_display_get_paintable_area (MksDisplay *self, width = gtk_widget_get_width (GTK_WIDGET (self)); height = gtk_widget_get_height (GTK_WIDGET (self)); display_ratio = (double)width / (double)height; - ratio = gdk_paintable_get_intrinsic_aspect_ratio (priv->paintable); + + if ((paintable = mks_display_picture_get_paintable (priv->picture))) + ratio = gdk_paintable_get_intrinsic_aspect_ratio (GDK_PAINTABLE (paintable)); + else + ratio = 1.; if (ratio > display_ratio) { @@ -99,442 +86,6 @@ mks_display_get_paintable_area (MksDisplay *self, *area = GRAPHENE_RECT_INIT (x, y, w, h); } -static void -mks_display_invalidate_contents_cb (MksDisplay *self, - MksPaintable *paintable) -{ - g_assert (MKS_IS_DISPLAY (self)); - g_assert (MKS_IS_PAINTABLE (paintable)); - - gtk_widget_queue_draw (GTK_WIDGET (self)); -} - -static void -mks_display_invalidate_size_cb (MksDisplay *self, - MksPaintable *paintable) -{ - g_assert (MKS_IS_DISPLAY (self)); - g_assert (MKS_IS_PAINTABLE (paintable)); - - gtk_widget_queue_resize (GTK_WIDGET (self)); -} - -static void -mks_display_notify_cursor_cb (MksDisplay *self, - GParamSpec *pspec, - MksPaintable *paintable) -{ - GdkCursor *cursor; - - g_assert (MKS_IS_DISPLAY (self)); - g_assert (MKS_IS_PAINTABLE (paintable)); - - cursor = _mks_paintable_get_cursor (paintable); - - gtk_widget_set_cursor (GTK_WIDGET (self), cursor); -} - -static void -mks_display_mouse_set_cb (MksDisplay *self, - int x, - int y, - MksPaintable *paintable) -{ - MksDisplayPrivate *priv = mks_display_get_instance_private (self); - - g_assert (MKS_IS_DISPLAY (self)); - g_assert (MKS_IS_PAINTABLE (paintable)); - - priv->last_mouse_x = x; - priv->last_mouse_y = y; -} - -static void -mks_display_set_paintable (MksDisplay *self, - GdkPaintable *paintable) -{ - MksDisplayPrivate *priv = mks_display_get_instance_private (self); - - g_assert (MKS_IS_DISPLAY (self)); - g_assert (!paintable || GDK_IS_PAINTABLE (paintable)); - - if (priv->paintable == paintable) - return; - - if (priv->paintable != NULL) - { - g_clear_signal_handler (&priv->invalidate_contents_handler, priv->paintable); - g_clear_signal_handler (&priv->invalidate_size_handler, priv->paintable); - g_clear_signal_handler (&priv->notify_cursor_handler, priv->paintable); - g_clear_signal_handler (&priv->mouse_set_handler, priv->paintable); - g_clear_object (&priv->paintable); - } - - if (paintable != NULL) - { - priv->paintable = g_object_ref (paintable); - priv->invalidate_contents_handler = - g_signal_connect_object (paintable, - "invalidate-contents", - G_CALLBACK (mks_display_invalidate_contents_cb), - self, - G_CONNECT_SWAPPED); - priv->invalidate_size_handler = - g_signal_connect_object (paintable, - "invalidate-size", - G_CALLBACK (mks_display_invalidate_size_cb), - self, - G_CONNECT_SWAPPED); - priv->notify_cursor_handler = - g_signal_connect_object (paintable, - "notify::cursor", - G_CALLBACK (mks_display_notify_cursor_cb), - self, - G_CONNECT_SWAPPED); - priv->notify_cursor_handler = - g_signal_connect_object (paintable, - "mouse-set", - G_CALLBACK (mks_display_mouse_set_cb), - self, - G_CONNECT_SWAPPED); - } - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PAINTABLE]); -} - -static void -mks_display_translate_keycode (MksDisplay *self, - guint keyval, - guint keycode, - guint *translated) -{ - g_assert (MKS_IS_DISPLAY (self)); - g_assert (translated != NULL); - - if (keycode < xorgevdev_to_qnum_len && - xorgevdev_to_qnum[keycode] != 0) - *translated = xorgevdev_to_qnum[keycode]; - else - *translated = keycode; -} - -static void -mks_display_keyboard_press_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - MksKeyboard *keyboard = (MksKeyboard *)object; - g_autoptr(MksDisplay) self = user_data; - g_autoptr(GError) error = NULL; - - g_assert (MKS_IS_KEYBOARD (keyboard)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (MKS_IS_DISPLAY (self)); - - if (!mks_keyboard_press_finish (keyboard, result, &error)) - g_warning ("Keyboard press failed: %s", error->message); -} - -static void -mks_display_keyboard_release_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - MksKeyboard *keyboard = (MksKeyboard *)object; - g_autoptr(MksDisplay) self = user_data; - g_autoptr(GError) error = NULL; - - g_assert (MKS_IS_KEYBOARD (keyboard)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (MKS_IS_DISPLAY (self)); - - if (!mks_keyboard_release_finish (keyboard, result, &error)) - g_warning ("Keyboard release failed: %s", error->message); -} - -static void -mks_display_mouse_move_to_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - MksMouse *mouse = (MksMouse *)object; - g_autoptr(MksDisplay) self = user_data; - g_autoptr(GError) error = NULL; - - g_assert (MKS_IS_MOUSE (mouse)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (MKS_IS_DISPLAY (self)); - - if (!mks_mouse_move_to_finish (mouse, result, &error)) - g_warning ("Failed move_to: %s", error->message); -} - -static void -mks_display_mouse_move_by_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - MksMouse *mouse = (MksMouse *)object; - g_autoptr(MksDisplay) self = user_data; - g_autoptr(GError) error = NULL; - - g_assert (MKS_IS_MOUSE (mouse)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (MKS_IS_DISPLAY (self)); - - if (!mks_mouse_move_by_finish (mouse, result, &error)) - g_warning ("Failed move_by: %s", error->message); -} - -static void -mks_display_translate_button (MksDisplay *self, - int *button) -{ - g_assert (MKS_IS_DISPLAY (self)); - g_assert (button != NULL); - - switch (*button) - { - case 1: *button = MKS_MOUSE_BUTTON_LEFT; break; - case 2: *button = MKS_MOUSE_BUTTON_MIDDLE; break; - case 3: *button = MKS_MOUSE_BUTTON_RIGHT; break; - case 8: *button = MKS_MOUSE_BUTTON_SIDE; break; - case 9: *button = MKS_MOUSE_BUTTON_EXTRA; break; - default: break; - } -} - -static void -mks_display_mouse_press_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - MksMouse *mouse = (MksMouse *)object; - g_autoptr(MksDisplay) self = user_data; - g_autoptr(GError) error = NULL; - - g_assert (MKS_IS_MOUSE (mouse)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (MKS_IS_DISPLAY (self)); - - if (!mks_mouse_press_finish (mouse, result, &error)) - g_warning ("Mouse press failed: %s", error->message); -} - -static void -mks_display_mouse_release_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - MksMouse *mouse = (MksMouse *)object; - g_autoptr(MksDisplay) self = user_data; - g_autoptr(GError) error = NULL; - - g_assert (MKS_IS_MOUSE (mouse)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (MKS_IS_DISPLAY (self)); - - if (!mks_mouse_release_finish (mouse, result, &error)) - g_warning ("Mouse release failed: %s", error->message); -} - -static gboolean -mks_display_legacy_event_cb (MksDisplay *self, - GdkEvent *event, - GtkEventControllerLegacy *controller) -{ - MksDisplayPrivate *priv = mks_display_get_instance_private (self); - GdkEventType event_type; - - g_assert (MKS_IS_DISPLAY (self)); - g_assert (event != NULL); - g_assert (GTK_IS_EVENT_CONTROLLER_LEGACY (controller)); - - if (priv->screen == NULL || priv->paintable == NULL) - return GDK_EVENT_PROPAGATE; - - event_type = gdk_event_get_event_type (event); - - switch ((int)event_type) - { - case GDK_MOTION_NOTIFY: - { - MksMouse *mouse = mks_screen_get_mouse (priv->screen); - GdkSurface *surface = gdk_event_get_surface (event); - GtkNative *native = gtk_widget_get_native (GTK_WIDGET (self)); - int guest_width = gdk_paintable_get_intrinsic_width (priv->paintable); - int guest_height = gdk_paintable_get_intrinsic_height (priv->paintable); - graphene_rect_t area; - double translate_x; - double translate_y; - - g_assert (MKS_IS_MOUSE (mouse)); - g_assert (GDK_IS_SURFACE (surface)); - - mks_display_get_paintable_area (self, &area); - - gtk_native_get_surface_transform (native, &translate_x, &translate_y); - - if (mks_mouse_get_is_absolute (mouse)) - { - gdouble x, y; - - if (gdk_event_get_position (event, &x, &y)) - { - x -= translate_x; - y -= translate_y; - - gtk_widget_translate_coordinates (GTK_WIDGET (native), - GTK_WIDGET (self), - x, y, &x, &y); - - if (graphene_rect_contains_point (&area, &GRAPHENE_POINT_INIT (x, y))) - { - double guest_x = floor (x - area.origin.x) / area.size.width * guest_width; - double guest_y = floor (y - area.origin.y) / area.size.height * guest_height; - - if (guest_x < 0 || guest_y < 0 || - guest_x >= guest_width || guest_y >= guest_height) - return GDK_EVENT_PROPAGATE; - - mks_mouse_move_to (mouse, - guest_x, - guest_y, - NULL, - mks_display_mouse_move_to_cb, - g_object_ref (self)); - - return GDK_EVENT_STOP; - } - } - } - else - { - double x, y; - - if (gdk_event_get_axis (event, GDK_AXIS_X, &x) && - gdk_event_get_axis (event, GDK_AXIS_Y, &y)) - { - double delta_x = priv->last_mouse_x - (x / area.size.width) * guest_width; - double delta_y = priv->last_mouse_y - (y / area.size.height) * guest_height; - - mks_mouse_move_by (mouse, - delta_x, - delta_y, - NULL, - mks_display_mouse_move_by_cb, - g_object_ref (self)); - - return GDK_EVENT_STOP; - } - } - - break; - } - - case GDK_BUTTON_PRESS: - case GDK_BUTTON_RELEASE: - { - MksMouse *mouse = mks_screen_get_mouse (priv->screen); - int button = gdk_button_event_get_button (event); - - mks_display_translate_button (self, &button); - - if (event_type == GDK_BUTTON_PRESS) - mks_mouse_press (mouse, - button, - NULL, - mks_display_mouse_press_cb, - g_object_ref (self)); - else - mks_mouse_release (mouse, - button, - NULL, - mks_display_mouse_release_cb, - g_object_ref (self)); - - return GDK_EVENT_STOP; - } - - case GDK_KEY_PRESS: - case GDK_KEY_RELEASE: - { - MksKeyboard *keyboard = mks_screen_get_keyboard (priv->screen); - guint keycode = gdk_key_event_get_keycode (event); - guint keyval = gdk_key_event_get_keycode (event); - guint qkeycode; - - mks_display_translate_keycode (self, keyval, keycode, &qkeycode); - - if (event_type == GDK_KEY_PRESS) - mks_keyboard_press (keyboard, - qkeycode, - NULL, - mks_display_keyboard_press_cb, - g_object_ref (self)); - else - mks_keyboard_release (keyboard, - qkeycode, - NULL, - mks_display_keyboard_release_cb, - g_object_ref (self)); - - return GDK_EVENT_STOP; - } - - case GDK_SCROLL: - { - MksMouse *mouse = mks_screen_get_mouse (priv->screen); - GdkScrollDirection direction = gdk_scroll_event_get_direction (event); - gboolean inverted = mks_scroll_event_is_inverted (event); - int button = -1; - - switch (direction) - { - case GDK_SCROLL_UP: - button = MKS_MOUSE_BUTTON_WHEEL_UP; - break; - - case GDK_SCROLL_DOWN: - button = MKS_MOUSE_BUTTON_WHEEL_DOWN; - break; - - case GDK_SCROLL_LEFT: - case GDK_SCROLL_RIGHT: - case GDK_SCROLL_SMOOTH: - default: - break; - } - - if (button != -1) - { - if (inverted) - { - if (button == MKS_MOUSE_BUTTON_WHEEL_UP) - button = MKS_MOUSE_BUTTON_WHEEL_DOWN; - else if (button == MKS_MOUSE_BUTTON_WHEEL_DOWN) - button = MKS_MOUSE_BUTTON_WHEEL_UP; - } - - mks_mouse_press (mouse, - button, - NULL, - mks_display_mouse_press_cb, - g_object_ref (self)); - - return GDK_EVENT_STOP; - } - - break; - } - - default: - break; - } - - return GDK_EVENT_PROPAGATE; -} - static void mks_display_attach_cb (GObject *object, GAsyncResult *result, @@ -555,7 +106,7 @@ mks_display_attach_cb (GObject *object, if (priv->screen != screen) return; - mks_display_set_paintable (self, paintable); + mks_display_picture_set_paintable (priv->picture, MKS_PAINTABLE (paintable)); } static void @@ -565,14 +116,20 @@ mks_display_connect (MksDisplay *self, MksDisplayPrivate *priv = mks_display_get_instance_private (self); g_assert (MKS_IS_DISPLAY (self)); - g_assert (priv->screen == NULL); + g_assert (!screen || MKS_IS_SCREEN (screen)); - g_set_object (&priv->screen, screen); + if (g_set_object (&priv->screen, screen)) + { + 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_attach (screen, - NULL, - mks_display_attach_cb, - g_object_ref (self)); + mks_screen_attach (screen, + NULL, + mks_display_attach_cb, + g_object_ref (self)); + + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SCREEN]); + } } static void @@ -581,11 +138,15 @@ mks_display_disconnect (MksDisplay *self) MksDisplayPrivate *priv = mks_display_get_instance_private (self); g_assert (MKS_IS_DISPLAY (self)); - g_assert (priv->screen != NULL); - - mks_display_set_paintable (self, NULL); g_clear_object (&priv->screen); + + if (priv->picture != NULL) + { + mks_display_picture_set_paintable (priv->picture, NULL); + mks_display_picture_set_keyboard (priv->picture, NULL); + mks_display_picture_set_mouse (priv->picture, NULL); + } } static void @@ -594,8 +155,9 @@ mks_display_dispose (GObject *object) MksDisplay *self = (MksDisplay *)object; MksDisplayPrivate *priv = mks_display_get_instance_private (self); - if (priv->screen != NULL) - mks_display_disconnect (self); + mks_display_disconnect (self); + + g_clear_pointer ((GtkWidget **)&priv->picture, gtk_widget_unparent); G_OBJECT_CLASS (mks_display_parent_class)->dispose (object); } @@ -606,17 +168,11 @@ mks_display_snapshot (GtkWidget *widget, { MksDisplay *self = (MksDisplay *)widget; MksDisplayPrivate *priv = mks_display_get_instance_private (self); - graphene_rect_t area; - if (priv->paintable == NULL) - return; + g_assert (MKS_IS_DISPLAY (self)); + g_assert (GTK_IS_SNAPSHOT (snapshot)); - mks_display_get_paintable_area (self, &area); - - gtk_snapshot_save (snapshot); - gtk_snapshot_translate (snapshot, &area.origin); - gdk_paintable_snapshot (priv->paintable, snapshot, area.size.width, area.size.height); - gtk_snapshot_restore (snapshot); + gtk_widget_snapshot_child (widget, GTK_WIDGET (priv->picture), snapshot); } static GtkSizeRequestMode @@ -628,61 +184,45 @@ mks_display_get_request_mode (GtkWidget *widget) static void mks_display_measure (GtkWidget *widget, GtkOrientation orientation, - int for_size, - int *minimum, - int *natural, - int *minimum_baseline, - int *natural_baseline) + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) { MksDisplay *self = (MksDisplay *)widget; MksDisplayPrivate *priv = mks_display_get_instance_private (self); - double min_width, min_height, nat_width, nat_height; - int default_width; - int default_height; g_assert (MKS_IS_DISPLAY (self)); - if (priv->paintable == NULL || for_size == 0) - { - *minimum = 0; - *natural = 0; - return; - } + gtk_widget_measure (GTK_WIDGET (priv->picture), orientation, for_size, + minimum, natural, minimum_baseline, natural_baseline); +} - default_width = gdk_paintable_get_intrinsic_width (priv->paintable); - default_height = gdk_paintable_get_intrinsic_width (priv->paintable); +static void +mks_display_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + MksDisplay *self = (MksDisplay *)widget; + MksDisplayPrivate *priv = mks_display_get_instance_private (self); + graphene_rect_t area; - if (default_width <= 0) - default_width = 640; + g_assert (MKS_IS_DISPLAY (self)); - if (default_height <= 0) - default_height = 480; + GTK_WIDGET_CLASS (mks_display_parent_class)->size_allocate (widget, width, height, baseline); - gdk_paintable_compute_concrete_size (priv->paintable, - 0, 0, - default_width, default_height, - &min_width, &min_height); + mks_display_get_paintable_area (self, &area); - if (orientation == GTK_ORIENTATION_HORIZONTAL) - { - gdk_paintable_compute_concrete_size (priv->paintable, - 0, - for_size < 0 ? 0 : for_size, - default_width, default_height, - &nat_width, &nat_height); - *minimum = 0; - *natural = ceil (nat_width); - } - else - { - gdk_paintable_compute_concrete_size (priv->paintable, - for_size < 0 ? 0 : for_size, - 0, - default_width, default_height, - &nat_width, &nat_height); - *minimum = 0; - *natural = ceil (nat_height); - } + gtk_widget_size_allocate (GTK_WIDGET (priv->picture), + &(GtkAllocation) { + area.origin.x, + area.origin.y, + area.size.width, + area.size.height + }, + -1); } static void @@ -692,14 +232,9 @@ mks_display_get_property (GObject *object, GParamSpec *pspec) { MksDisplay *self = MKS_DISPLAY (object); - MksDisplayPrivate *priv = mks_display_get_instance_private (self); switch (prop_id) { - case PROP_PAINTABLE: - g_value_set_object (value, priv->paintable); - break; - case PROP_SCREEN: g_value_set_object (value, mks_display_get_screen (self)); break; @@ -740,13 +275,9 @@ mks_display_class_init (MksDisplayClass *klass) widget_class->get_request_mode = mks_display_get_request_mode; widget_class->measure = mks_display_measure; + widget_class->size_allocate = mks_display_size_allocate; widget_class->snapshot = mks_display_snapshot; - properties [PROP_PAINTABLE] = - g_param_spec_object ("paintable", NULL, NULL, - GDK_TYPE_PAINTABLE, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - properties[PROP_SCREEN] = g_param_spec_object ("screen", NULL, NULL, MKS_TYPE_SCREEN, @@ -762,18 +293,10 @@ mks_display_class_init (MksDisplayClass *klass) static void mks_display_init (MksDisplay *self) { - GtkEventController *controller; + MksDisplayPrivate *priv = mks_display_get_instance_private (self); - controller = gtk_event_controller_legacy_new (); - g_signal_connect_object (controller, - "event", - G_CALLBACK (mks_display_legacy_event_cb), - self, - G_CONNECT_SWAPPED); - gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE); - gtk_widget_add_controller (GTK_WIDGET (self), controller); - - gtk_widget_set_focusable (GTK_WIDGET (self), TRUE); + priv->picture = g_object_new (MKS_TYPE_DISPLAY_PICTURE, NULL); + gtk_widget_set_parent (GTK_WIDGET (priv->picture), GTK_WIDGET (self)); } GtkWidget *