/* Event handling for GTK 3 and GTK 4 -*- coding: utf-8 -*- Copyright (C) 2020 Free Software Foundation, Inc. This file is part of GNU Emacs. GNU Emacs 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. GNU Emacs 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 GNU Emacs. If not, see . */ #include "config.h" #include "frame.h" #include "pgtkterm.h" #ifdef HAVE_GTK_EVENT_CONTROLLER #include "pgtkevent.h" #include "blockinput.h" #include "dynlib.h" #include typedef struct _EmacsGtkEventHandlerPrivate { struct frame *f; } EmacsGtkEventHandlerPrivate; #if !defined (HAVE_GTK4) || defined (FIX_GTK_LEGACY_HANDLER_BUG) G_DEFINE_TYPE_WITH_PRIVATE (EmacsGtkEventHandler, emacs_gtk_event_handler, GTK_TYPE_EVENT_CONTROLLER); #else G_DEFINE_TYPE_WITH_PRIVATE (EmacsGtkEventHandler, emacs_gtk_event_handler, G_TYPE_OBJECT); #endif #if defined (HAVE_GTK4) && !defined (FIX_GTK_LEGACY_HANDLER_BUG) static gboolean emacs_gtk_event_handler_handle_event (EmacsGtkEventHandler *handler, GdkEvent *_ev, gdouble x, gdouble y); static gboolean emacs_gtk_event_handler_on_event (GtkEventControllerLegacy *controller, GdkEvent *event, gpointer user_data) { return emacs_gtk_event_handler_handle_event (user_data, event, 0, 0); } #endif #ifdef HAVE_GTK4 static gboolean scroll_cb (GtkEventControllerScroll *controller, gdouble dx, gdouble dy, gpointer user_data) { EmacsGtkEventHandlerPrivate *priv = emacs_gtk_event_handler_get_instance_private (((EmacsGtkEventHandler *) user_data)); if (((EmacsGtkEventHandler *) user_data)->scroll_event) return ((EmacsGtkEventHandler *) user_data) ->scroll_event (controller, dx, dy, priv->f); return false; } static void drag_end_cb (EmacsGtkEventHandler *h, GdkEventSequence *sequence, GtkGesture *gesture) { Vpgtk_last_event_from_touchscreen = Qt; h->s_flag = false; h->hq_flag = false; } static void drag_update_cb (EmacsGtkEventHandler *h, gdouble xo, gdouble yo, GtkGesture *gesture) { Vpgtk_last_event_from_touchscreen = Qt; gdouble dt = fabs (xo) - fabs (yo); if (dt < 1.0 && !h->se_blocked) { scroll_cb (NULL, 0, -yo * 0.5, h); scroll_cb (NULL, -xo * 0.5, 0, h); } else if (!h->se_blocked) scroll_cb (NULL, -xo * 0.5, -yo * 0.5, h); } static void drag_begin_cb (EmacsGtkEventHandler *h, gdouble start_x, gdouble start_y, GtkGesture *gesture) { Vpgtk_last_event_from_touchscreen = Qt; EmacsGtkEventHandlerPrivate *priv = emacs_gtk_event_handler_get_instance_private (h); enum window_part w = 0; window_from_coordinates (priv->f, (int) round (start_x), (int) round (start_y), &w, 0, 0); if (w != ON_TEXT) { h->se_blocked = true; emacs_gtk_event_handler_toggle_scroll_evs (h, 0); } if (h->se_blocked) return; h->dsy = start_y; h->dsx = start_x; h->hq_flag = true; } static void long_press_cb (GtkGestureLongPress *p, gdouble x, gdouble y, EmacsGtkEventHandler *h) { Vpgtk_last_event_from_touchscreen = Qt; if (h->long_press_event) h->long_press_event (((EmacsGtkEventHandlerPrivate *) emacs_gtk_event_handler_get_instance_private (h))->f, x, y); } #endif #ifdef HAVE_GTK4 static void motion_enter (GtkEventControllerMotion *controller, gdouble x, gdouble y, GdkCrossingMode mode, gpointer user_data) { EmacsGtkEventHandler *h = user_data; EmacsGtkEventHandlerPrivate *priv = emacs_gtk_event_handler_get_instance_private (h); h->enter (h->widget, mode, priv->f); } static void motion_leave (GtkEventControllerMotion *controller, GdkCrossingMode mode, gpointer user_data) { EmacsGtkEventHandler *h = user_data; EmacsGtkEventHandlerPrivate *priv = emacs_gtk_event_handler_get_instance_private (h); #ifdef HAVE_GTK4 if (FRAME_X_OUTPUT (priv->f)->ttip_popover) return; #endif h->leave (h->widget, mode, priv->f); } #endif static void emacs_gtk_event_handler_init (EmacsGtkEventHandler *handler) { #if defined (HAVE_GTK4) && !defined (FIX_GTK_LEGACY_HANDLER_BUG) handler->legacy = GTK_EVENT_CONTROLLER_LEGACY (gtk_event_controller_legacy_new ()); g_signal_connect_after (G_OBJECT (handler->legacy), "event", G_CALLBACK (emacs_gtk_event_handler_on_event), handler); gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (handler->legacy), GTK_PHASE_BUBBLE); #endif #ifdef HAVE_GTK4 handler->s_flag = false; handler->hq_flag = false; handler->se_blocked = false; handler->scroll = GTK_EVENT_CONTROLLER_SCROLL (gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES | GTK_EVENT_CONTROLLER_SCROLL_KINETIC)); handler->drag = gtk_gesture_drag_new (); handler->lpress = gtk_gesture_long_press_new (); gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (handler->drag), true); gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (handler->lpress), true); gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (handler->lpress), GTK_PHASE_CAPTURE); gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (handler->scroll), GTK_PHASE_CAPTURE); gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (handler->drag), GTK_PHASE_CAPTURE); g_signal_connect (G_OBJECT (handler->lpress), "pressed", G_CALLBACK (long_press_cb), handler); g_signal_connect (G_OBJECT (handler->scroll), "scroll", G_CALLBACK (scroll_cb), handler); g_signal_connect_swapped (handler->drag, "drag-begin", G_CALLBACK (drag_begin_cb), handler); g_signal_connect_swapped (handler->drag, "drag-update", G_CALLBACK (drag_update_cb), handler); g_signal_connect_swapped (handler->drag, "end", G_CALLBACK (drag_end_cb), handler); handler->focus = (GtkEventControllerMotion *) gtk_event_controller_motion_new (); g_signal_connect (G_OBJECT (handler->focus), "enter", G_CALLBACK (motion_enter), handler); g_signal_connect (G_OBJECT (handler->focus), "leave", G_CALLBACK (motion_leave), handler); g_object_ref (handler->focus); g_object_ref (handler->drag); g_object_ref (handler->lpress); g_object_ref (handler->scroll); #endif } #ifdef HAVE_GTK4 static gboolean gtk_simulate_touchscreen (void) { static dynlib_handle_ptr dyn = NULL; if (!dyn) if (!(dyn = dynlib_open ("libgtk-4"))) return false; gboolean (* fn) (void) = dynlib_sym (dyn, "gtk_simulate_touchscreen"); if (!fn) return gtk_get_debug_flags () & GTK_DEBUG_TOUCHSCREEN; return fn (); } static void emacs_gtk_event_handler_dispose (GObject *o) { EmacsGtkEventHandler *h = (gpointer) o; gtk_widget_remove_controller (h->widget, (gpointer) h->focus); gtk_widget_remove_controller (h->widget, (gpointer) h->scroll); gtk_widget_remove_controller (h->widget, (gpointer) h->drag); gtk_widget_remove_controller (h->widget, (gpointer) h->lpress); g_clear_pointer (&h->focus, g_object_unref); g_clear_pointer (&h->scroll, g_object_unref); g_clear_pointer (&h->drag, g_object_unref); g_clear_pointer (&h->lpress, g_object_unref); G_OBJECT_CLASS (emacs_gtk_event_handler_parent_class)->dispose (o); } #endif void emacs_gtk_event_handler_set_widget (EmacsGtkEventHandler *handler, GtkWidget *w) { #ifdef HAVE_GTK4 if (handler == FRAME_GTK_EV_HANDLER (((EmacsGtkEventHandlerPrivate *) emacs_gtk_event_handler_get_instance_private (handler))->f)) { gtk_widget_add_controller (w, GTK_EVENT_CONTROLLER (handler->scroll)); gtk_widget_add_controller (w, GTK_EVENT_CONTROLLER (handler->drag)); gtk_widget_add_controller (w, GTK_EVENT_CONTROLLER (handler->lpress)); gtk_widget_add_controller (w, GTK_EVENT_CONTROLLER (handler->focus)); } #endif #if !defined (HAVE_GTK4) GTK_EVENT_CONTROLLER_CLASS (G_OBJECT_GET_CLASS (G_OBJECT (handler))) ->set_widget (GTK_EVENT_CONTROLLER (handler), w); #elif !defined (FIX_GTK_LEGACY_HANDLER_BUG) gtk_widget_add_controller (w, GTK_EVENT_CONTROLLER (handler->legacy)); #else gtk_widget_add_controller (w, GTK_EVENT_CONTROLLER (handler)); #endif handler->widget = w; } #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" #ifndef HAVE_GTK4 static gboolean emacs_gtk_event_handler_handle_event (GtkEventController *_handler, GdkEvent *ev) #else static gboolean emacs_gtk_event_handler_handle_event ( #ifdef FIX_GTK_LEGACY_HANDLER_BUG GtkEventController #else EmacsGtkEventHandler #endif *_handler, GdkEvent *ev, gdouble x, gdouble y) #endif { block_input (); EmacsGtkEventHandler *handler = (gpointer) _handler; EmacsGtkEventHandlerPrivate *priv = emacs_gtk_event_handler_get_instance_private (handler); #ifdef HAVE_GTK4 if (handler->s_flag) return false; if (handler->se_blocked || gdk_device_get_source (gdk_event_get_device (ev)) == GDK_SOURCE_TOUCHSCREEN || gtk_simulate_touchscreen ()) Vpgtk_last_event_from_touchscreen = Qt; else Vpgtk_last_event_from_touchscreen = Qnil; if (!GTK_IS_ROOT (handler->widget)) gtk_window_set_focus (GTK_WINDOW (gtk_widget_get_root (handler->widget)), handler->widget); #endif switch (gdk_event_get_event_type (ev)) { case GDK_BUTTON_PRESS: if (handler->button_down && handler->button_down (handler->widget, ev, priv->f)) { unblock_input (); return true; } else goto def; case GDK_BUTTON_RELEASE: if (handler->button_up && handler->button_up (handler->widget, ev, priv->f)) { unblock_input (); return true; } else goto def; case GDK_KEY_PRESS: if (handler->key_down && handler->key_down (handler->widget, ev, priv->f)) { unblock_input (); return true; } else goto def; case GDK_KEY_RELEASE: if (handler->key_up && handler->key_up (handler->widget, ev, priv->f)) { unblock_input (); return true; } else goto def; #ifndef HAVE_GTK4 case GDK_MAP: if (handler->map_event && handler->map_event (handler->widget, ev, priv->f)) { unblock_input (); return true; } case GDK_WINDOW_STATE: if (handler->window_state_event && handler->window_state_event (handler->widget, ev, priv->f)) { unblock_input (); return true; } #endif case GDK_DELETE: if (handler->delete_event && handler->delete_event (handler->widget, ev, priv->f)) { unblock_input (); return true; } else goto def; #ifndef HAVE_GTK4 case GDK_SCROLL: if (handler->scroll_event && handler->scroll_event (handler->widget, ev, priv->f)) { unblock_input (); return true; } else goto def; #endif #ifndef HAVE_GTK4 case GDK_SELECTION_CLEAR: if (handler->selection_lost && handler->selection_lost (handler->widget, (GdkEventSelection *) ev, priv->f)) { unblock_input (); return true; } else goto def; #endif case GDK_CONFIGURE: if (handler->configure && handler->configure (handler->widget, ev, priv->f)) { unblock_input (); return true; } else goto def; #ifndef HAVE_GTK4 case GDK_EXPOSE: case GDK_DAMAGE: if (handler->expose && handler->expose (handler->widget, (GdkEventExpose *) ev, 0)) { unblock_input (); return true; } #endif case GDK_FOCUS_CHANGE: #ifdef HAVE_GTK4 { if (gdk_focus_event_get_in (ev)) #else if (ev->focus_change.in) #endif if (handler->focus_in && handler->focus_in (handler->widget, ev, priv->f)) { unblock_input (); return false; } else goto def; else #ifdef HAVE_GTK4 { if (handler->focus_out && handler->focus_out (handler->widget, ev, priv->f)) { unblock_input (); return false; } else goto def; } #endif #ifdef HAVE_GTK4 } case GDK_ENTER_NOTIFY: if (handler->enter_notify && handler->enter_notify (handler->widget, ev, priv->f)) { unblock_input (); return true; } else goto def; case GDK_LEAVE_NOTIFY: if (handler->leave_notify && handler->leave_notify (handler->widget, ev, priv->f)) { unblock_input (); return true; } else goto def; case GDK_MOTION_NOTIFY: if (handler->motion_notify && handler->motion_notify (handler->widget, ev, priv->f)) { unblock_input (); return true; } else goto def; #endif default: def: unblock_input (); return false; } } #pragma GCC diagnostic push static void emacs_gtk_event_handler_class_init (EmacsGtkEventHandlerClass *clazz) { #if !defined (HAVE_GTK4) || defined (FIX_GTK_LEGACY_HANDLER_BUG) GtkEventControllerClass *cc = GTK_EVENT_CONTROLLER_CLASS (clazz); cc->handle_event = emacs_gtk_event_handler_handle_event; #endif #ifdef HAVE_GTK4 G_OBJECT_CLASS (clazz)->dispose = emacs_gtk_event_handler_dispose; #endif } EmacsGtkEventHandler * emacs_gtk_event_handler_new (struct frame *f) { EmacsGtkEventHandler *handler = (EmacsGtkEventHandler *) g_type_create_instance (EMACS_TYPE_GTK_EVENT_HANDLER); EmacsGtkEventHandlerPrivate *priv = emacs_gtk_event_handler_get_instance_private (handler); priv->f = f; return handler; } #ifdef HAVE_GTK4 void emacs_gtk_event_handler_toggle_scroll_evs (EmacsGtkEventHandler *handler, bool enable) { handler->se_blocked = !enable; if (enable) { gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (handler->scroll), GTK_PHASE_CAPTURE); } else { gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (handler->scroll), GTK_PHASE_NONE); } } #endif G_END_DECLS #endif /* HAVE_GTK_EVENT_CONTROLLER */