10 #include <gtk/gtk-a11y.h>
14 #include "flutter/common/constants.h"
95 FlPluginRegistryInterface* iface);
98 FlKeyboardViewDelegateInterface* iface);
101 FlScrollingViewDelegateInterface* iface);
104 FlTextInputViewDelegateInterface* iface);
110 G_IMPLEMENT_INTERFACE(fl_plugin_registry_get_type(),
112 G_IMPLEMENT_INTERFACE(fl_keyboard_view_delegate_get_type(),
114 G_IMPLEMENT_INTERFACE(fl_scrolling_view_delegate_get_type(),
116 G_IMPLEMENT_INTERFACE(fl_text_input_view_delegate_get_type(),
120 static gboolean first_frame_idle_cb(gpointer
user_data) {
140 gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(
self)));
141 g_return_if_fail(GDK_IS_WINDOW(window));
142 g_autoptr(GtkIMContext) im_context = gtk_im_multicontext_new();
143 gtk_im_context_set_client_window(im_context, window);
145 g_clear_object(&self->text_input_handler);
147 messenger, im_context, FL_TEXT_INPUT_VIEW_DELEGATE(
self));
148 g_clear_object(&self->keyboard_handler);
149 self->keyboard_handler =
154 g_clear_object(&self->scrolling_manager);
155 self->scrolling_manager =
160 GdkDevice* device = gdk_event_get_source_device(
event);
161 GdkInputSource source = gdk_device_get_source(device);
164 case GDK_SOURCE_ERASER:
165 case GDK_SOURCE_CURSOR:
166 case GDK_SOURCE_TABLET_PAD:
167 return kFlutterPointerDeviceKindStylus;
168 case GDK_SOURCE_TOUCHSCREEN:
169 return kFlutterPointerDeviceKindTouch;
170 case GDK_SOURCE_TOUCHPAD:
171 case GDK_SOURCE_TRACKPOINT:
172 case GDK_SOURCE_KEYBOARD:
173 case GDK_SOURCE_MOUSE:
174 return kFlutterPointerDeviceKindMouse;
180 guint event_time = gdk_event_get_time(
event);
181 GdkEventType event_type = gdk_event_get_event_type(
event);
182 GdkModifierType event_state =
static_cast<GdkModifierType
>(0);
183 gdk_event_get_state(
event, &event_state);
184 guint event_button = 0;
185 gdk_event_get_button(
event, &event_button);
186 gdouble event_x = 0.0, event_y = 0.0;
187 gdk_event_get_coords(
event, &event_x, &event_y);
190 switch (event_button) {
192 button = kFlutterPointerButtonMousePrimary;
195 button = kFlutterPointerButtonMouseMiddle;
198 button = kFlutterPointerButtonMouseSecondary;
203 int old_button_state =
self->button_state;
204 FlutterPointerPhase phase = kMove;
205 if (event_type == GDK_BUTTON_PRESS) {
207 if ((self->button_state & button) != 0) {
210 self->button_state ^= button;
212 phase = old_button_state == 0 ? kDown : kMove;
213 }
else if (event_type == GDK_BUTTON_RELEASE) {
215 if ((self->button_state & button) == 0) {
218 self->button_state ^= button;
220 phase =
self->button_state == 0 ? kUp : kMove;
223 if (self->engine ==
nullptr) {
227 gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(
self));
229 self->scrolling_manager, event_x * scale_factor, event_y * scale_factor);
231 event_state, event_time);
233 self->engine, self->view_id, phase,
243 if (!self->pointer_inside) {
244 self->pointer_inside =
TRUE;
247 if (gdk_event_get_coords(
event, &x, &y)) {
248 gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(
self));
251 self->engine, self->view_id, kAdd,
261 GtkAllocation allocation;
262 gtk_widget_get_allocation(GTK_WIDGET(
self), &allocation);
263 gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(
self));
265 self->engine, self->view_id, allocation.width * scale_factor,
266 allocation.height * scale_factor, scale_factor);
274 if (allocation.width > 1 && allocation.height > 1 &&
275 gtk_widget_get_realized(GTK_WIDGET(
self))) {
277 allocation.width * scale_factor,
278 allocation.height * scale_factor);
287 g_autoptr(GError)
error =
nullptr;
289 if (g_error_matches(
error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
293 g_warning(
"Failed to add view: %s",
error->message);
303 const FlutterSemanticsUpdate2* update,
322 FlPluginRegistry* registry,
324 FlView*
self = FL_VIEW(registry);
332 FlPluginRegistryInterface* iface) {
337 FlKeyboardViewDelegateInterface* iface) {
338 iface->send_key_event =
339 [](FlKeyboardViewDelegate* view_delegate,
const FlutterKeyEvent*
event,
341 FlView*
self = FL_VIEW(view_delegate);
342 if (self->engine !=
nullptr) {
347 iface->text_filter_key_press = [](FlKeyboardViewDelegate* view_delegate,
349 FlView*
self = FL_VIEW(view_delegate);
354 iface->get_messenger = [](FlKeyboardViewDelegate* view_delegate) {
355 FlView*
self = FL_VIEW(view_delegate);
359 iface->redispatch_event = [](FlKeyboardViewDelegate* view_delegate,
361 GdkEventType event_type =
363 g_return_if_fail(event_type == GDK_KEY_PRESS ||
364 event_type == GDK_KEY_RELEASE);
368 iface->subscribe_to_layout_change = [](FlKeyboardViewDelegate* view_delegate,
370 FlView*
self = FL_VIEW(view_delegate);
371 self->keyboard_layout_notifier = std::move(notifier);
374 iface->lookup_key = [](FlKeyboardViewDelegate* view_delegate,
375 const GdkKeymapKey* key) -> guint {
376 FlView*
self = FL_VIEW(view_delegate);
377 g_return_val_if_fail(self->keymap !=
nullptr, 0);
378 return gdk_keymap_lookup_key(self->keymap, key);
381 iface->get_keyboard_state =
382 [](FlKeyboardViewDelegate* view_delegate) -> GHashTable* {
383 FlView*
self = FL_VIEW(view_delegate);
389 FlScrollingViewDelegateInterface* iface) {
390 iface->send_mouse_pointer_event =
391 [](FlScrollingViewDelegate* view_delegate, FlutterPointerPhase phase,
392 size_t timestamp,
double x,
double y,
393 FlutterPointerDeviceKind device_kind,
double scroll_delta_x,
394 double scroll_delta_y, int64_t buttons) {
395 FlView*
self = FL_VIEW(view_delegate);
396 if (self->engine !=
nullptr) {
398 self->engine, self->view_id, phase, timestamp, x, y, device_kind,
399 scroll_delta_x, scroll_delta_y, buttons);
402 iface->send_pointer_pan_zoom_event =
403 [](FlScrollingViewDelegate* view_delegate,
size_t timestamp,
double x,
404 double y, FlutterPointerPhase phase,
double pan_x,
double pan_y,
405 double scale,
double rotation) {
406 FlView*
self = FL_VIEW(view_delegate);
407 if (self->engine !=
nullptr) {
409 timestamp, x, y, phase, pan_x,
410 pan_y, scale, rotation);
416 FlTextInputViewDelegateInterface* iface) {
417 iface->translate_coordinates = [](FlTextInputViewDelegate* delegate,
418 gint view_x, gint view_y, gint* window_x,
420 FlView*
self = FL_VIEW(delegate);
421 gtk_widget_translate_coordinates(GTK_WIDGET(
self),
422 gtk_widget_get_toplevel(GTK_WIDGET(
self)),
423 view_x, view_y, window_x, window_y);
429 GdkEventButton* button_event) {
430 GdkEvent*
event =
reinterpret_cast<GdkEvent*
>(button_event);
433 GdkEventType event_type = gdk_event_get_event_type(
event);
434 if (event_type == GDK_DOUBLE_BUTTON_PRESS ||
435 event_type == GDK_TRIPLE_BUTTON_PRESS) {
446 GdkEventButton* button_event) {
447 GdkEvent*
event =
reinterpret_cast<GdkEvent*
>(button_event);
457 self->scrolling_manager,
event,
458 gtk_widget_get_scale_factor(GTK_WIDGET(
self)));
464 GdkEventMotion* motion_event) {
465 GdkEvent*
event =
reinterpret_cast<GdkEvent*
>(motion_event);
467 if (self->engine ==
nullptr) {
471 guint event_time = gdk_event_get_time(
event);
472 GdkModifierType event_state =
static_cast<GdkModifierType
>(0);
473 gdk_event_get_state(
event, &event_state);
474 gdouble event_x = 0.0, event_y = 0.0;
475 gdk_event_get_coords(
event, &event_x, &event_y);
479 gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(
self));
482 event_state, event_time);
484 self->engine, self->view_id, self->button_state != 0 ? kMove : kHover,
494 GdkEventCrossing* crossing_event) {
495 GdkEvent*
event =
reinterpret_cast<GdkEvent*
>(crossing_event);
497 if (self->engine ==
nullptr) {
508 GdkEventCrossing* crossing_event) {
509 GdkEvent*
event =
reinterpret_cast<GdkEvent*
>(crossing_event);
511 guint event_time = gdk_event_get_time(
event);
512 gdouble event_x = 0.0, event_y = 0.0;
513 gdk_event_get_coords(
event, &event_x, &event_y);
515 if (crossing_event->mode != GDK_CROSSING_NORMAL) {
519 if (self->engine ==
nullptr) {
526 if (self->pointer_inside && self->button_state == 0) {
527 gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(
self));
529 self->engine, self->view_id, kRemove,
533 self->pointer_inside = FALSE;
540 if (self->keyboard_layout_notifier ==
nullptr) {
544 self->keyboard_layout_notifier();
576 gtk_widget_get_parent_window(GTK_WIDGET(
self)));
579 self->keymap = gdk_keymap_get_for_display(gdk_display_get_default());
580 self->keymap_keys_changed_cb_id = g_signal_connect_swapped(
589 g_autoptr(GError)
error =
nullptr;
591 gtk_gl_area_set_error(self->gl_area,
error);
595 return GDK_GL_CONTEXT(
600 g_autoptr(GError)
error =
nullptr;
604 GError* gl_error = gtk_gl_area_get_error(self->gl_area);
605 if (gl_error != NULL) {
606 g_warning(
"Failed to initialize GLArea: %s", gl_error->message);
612 GtkWidget* toplevel_window = gtk_widget_get_toplevel(GTK_WIDGET(
self));
614 self->window_state_monitor =
616 GTK_WINDOW(toplevel_window));
619 g_signal_connect_swapped(toplevel_window,
"delete-event",
627 g_warning(
"Failed to start Flutter engine: %s",
error->message);
635 FL_SOCKET_ACCESSIBLE(gtk_widget_get_accessible(GTK_WIDGET(
self))),
636 atk_plug_get_id(ATK_PLUG(self->view_accessible)));
639 static gboolean
render_cb(FlView*
self, GdkGLContext* context) {
640 if (gtk_gl_area_get_error(self->gl_area) != NULL) {
644 int width = gtk_widget_get_allocated_width(GTK_WIDGET(self->gl_area));
645 int height = gtk_widget_get_allocated_height(GTK_WIDGET(self->gl_area));
646 gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self->gl_area));
649 self->background_color);
655 g_autoptr(GError)
error =
nullptr;
659 GError* gl_error = gtk_gl_area_get_error(self->gl_area);
660 if (gl_error != NULL) {
661 g_warning(
"Failed to uninitialize GLArea: %s", gl_error->message);
673 FlView*
self = FL_VIEW(
object);
675 if (strcmp(
pspec->name,
"scale-factor") == 0) {
679 if (G_OBJECT_CLASS(fl_view_parent_class)->notify !=
nullptr) {
680 G_OBJECT_CLASS(fl_view_parent_class)->notify(
object,
pspec);
685 FlView*
self = FL_VIEW(
object);
687 g_cancellable_cancel(self->cancellable);
689 if (self->engine !=
nullptr) {
701 if (self->on_pre_engine_restart_handler != 0) {
702 g_signal_handler_disconnect(self->engine,
703 self->on_pre_engine_restart_handler);
704 self->on_pre_engine_restart_handler = 0;
707 g_clear_object(&self->engine);
708 g_clear_object(&self->renderer);
709 g_clear_pointer(&self->background_color, gdk_rgba_free);
710 g_clear_object(&self->window_state_monitor);
711 g_clear_object(&self->scrolling_manager);
712 g_clear_object(&self->keyboard_handler);
713 if (self->keymap_keys_changed_cb_id != 0) {
714 g_signal_handler_disconnect(self->keymap, self->keymap_keys_changed_cb_id);
715 self->keymap_keys_changed_cb_id = 0;
717 g_clear_object(&self->mouse_cursor_handler);
718 g_clear_object(&self->platform_handler);
719 g_clear_object(&self->view_accessible);
720 g_clear_object(&self->cancellable);
722 G_OBJECT_CLASS(fl_view_parent_class)->dispose(
object);
727 FlView*
self = FL_VIEW(widget);
729 GTK_WIDGET_CLASS(fl_view_parent_class)->realize(widget);
732 gtk_widget_realize(GTK_WIDGET(self->gl_area));
737 FlView*
self = FL_VIEW(widget);
741 reinterpret_cast<GdkEvent*
>(
event))));
746 GdkEventKey*
event) {
747 FlView*
self = FL_VIEW(widget);
750 reinterpret_cast<GdkEvent*
>(
event))));
754 GObjectClass* object_class = G_OBJECT_CLASS(klass);
758 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
764 g_signal_new(
"first-frame", fl_view_get_type(), G_SIGNAL_RUN_LAST, 0,
765 NULL, NULL, NULL, G_TYPE_NONE, 0);
767 gtk_widget_class_set_accessible_type(GTK_WIDGET_CLASS(klass),
768 fl_socket_accessible_get_type());
772 self->cancellable = g_cancellable_new();
774 gtk_widget_set_can_focus(GTK_WIDGET(
self),
TRUE);
778 GdkRGBA default_background = {
779 .red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0};
780 self->background_color = gdk_rgba_copy(&default_background);
782 self->event_box = gtk_event_box_new();
783 gtk_widget_set_hexpand(self->event_box,
TRUE);
784 gtk_widget_set_vexpand(self->event_box,
TRUE);
785 gtk_container_add(GTK_CONTAINER(
self), self->event_box);
786 gtk_widget_show(self->event_box);
787 gtk_widget_add_events(self->event_box,
788 GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
789 GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK |
790 GDK_SMOOTH_SCROLL_MASK);
792 g_signal_connect_swapped(self->event_box,
"button-press-event",
794 g_signal_connect_swapped(self->event_box,
"button-release-event",
796 g_signal_connect_swapped(self->event_box,
"scroll-event",
798 g_signal_connect_swapped(self->event_box,
"motion-notify-event",
800 g_signal_connect_swapped(self->event_box,
"enter-notify-event",
802 g_signal_connect_swapped(self->event_box,
"leave-notify-event",
804 GtkGesture* zoom = gtk_gesture_zoom_new(self->event_box);
807 g_signal_connect_swapped(zoom,
"scale-changed",
810 GtkGesture* rotate = gtk_gesture_rotate_new(self->event_box);
811 g_signal_connect_swapped(rotate,
"begin",
813 g_signal_connect_swapped(rotate,
"angle-changed",
818 self->gl_area = GTK_GL_AREA(gtk_gl_area_new());
819 gtk_gl_area_set_has_alpha(self->gl_area,
TRUE);
820 gtk_widget_show(GTK_WIDGET(self->gl_area));
821 gtk_container_add(GTK_CONTAINER(self->event_box), GTK_WIDGET(self->gl_area));
822 g_signal_connect_swapped(self->gl_area,
"render", G_CALLBACK(
render_cb),
825 g_signal_connect_swapped(
self,
"size-allocate", G_CALLBACK(
size_allocate_cb),
831 FlView*
self = FL_VIEW(g_object_new(fl_view_get_type(),
nullptr));
833 self->view_id = flutter::kFlutterImplicitViewId;
834 self->engine = FL_ENGINE(g_object_ref(engine));
836 g_assert(FL_IS_RENDERER_GDK(renderer));
837 self->renderer = FL_RENDERER_GDK(g_object_ref(renderer));
841 self->on_pre_engine_restart_handler =
842 g_signal_connect_swapped(engine,
"on-pre-engine-restart",
845 g_signal_connect_swapped(self->gl_area,
"create-context",
847 g_signal_connect_swapped(self->gl_area,
"realize", G_CALLBACK(
realize_cb),
849 g_signal_connect_swapped(self->gl_area,
"unrealize", G_CALLBACK(
unrealize_cb),
856 FlView*
self = FL_VIEW(g_object_new(fl_view_get_type(),
nullptr));
858 self->engine = FL_ENGINE(g_object_ref(engine));
860 g_assert(FL_IS_RENDERER_GDK(renderer));
861 self->renderer = FL_RENDERER_GDK(g_object_ref(renderer));
863 self->on_pre_engine_restart_handler =
864 g_signal_connect_swapped(engine,
"on-pre-engine-restart",
875 g_return_val_if_fail(FL_IS_VIEW(
self),
nullptr);
881 g_return_val_if_fail(FL_IS_VIEW(
self), -1);
882 return self->view_id;
886 const GdkRGBA* color) {
887 g_return_if_fail(FL_IS_VIEW(
self));
888 gdk_rgba_free(self->background_color);
889 self->background_color = gdk_rgba_copy(color);
893 g_return_if_fail(FL_IS_VIEW(
self));
895 gtk_widget_queue_draw(GTK_WIDGET(self->gl_area));
897 if (!self->have_first_frame) {
898 self->have_first_frame =
TRUE;
901 g_idle_add(first_frame_idle_cb,
self);
906 g_return_val_if_fail(FL_IS_VIEW(
self),
nullptr);