/* 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 */