gtk/gdk/wayland/gdkeventsource.c
Benjamin Otte 239f45ce8e wayland: Split event source into two
We need to guarantee that we call wayland_display_read_events() after a
poll() and before any other source runs, including any source with
higher priority.

As GSourceFuncs doesn't have a after_poll() vfunc and check() is not
guaranteed to be called for anything but the highest priority, we only
have once chance:
Run with the highest priority

But because we don't want event delivery with ultrahigh priority, we
split the source into two:
 * a poll source that polls while blocking wayland reading and
   then immediately calls read_events() with priority G_MININT
 * our old trusty event source with PRIORITY_EVENTS that dispatches
   events

Fixes !7859
Fixes #7091
2024-10-28 13:34:09 +01:00

336 lines
9.6 KiB
C

/* GDK - The GIMP Drawing Kit
* Copyright (C) 2009 Carlos Garnacho <carlosg@gnome.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gdkprivate-wayland.h"
#include "gdkeventsprivate.h"
#include <unistd.h>
#include <errno.h>
typedef struct _GdkWaylandEventSource GdkWaylandEventSource;
typedef struct _GdkWaylandPollSource GdkWaylandPollSource;
struct _GdkWaylandEventSource
{
GSource source;
GdkWaylandDisplay *display;
};
struct _GdkWaylandPollSource
{
GSource source;
GPollFD pfd;
GdkWaylandDisplay *display;
guint reading : 1;
guint can_dispatch : 1;
};
/* If we should try wl_display_dispatch_pending() before
* polling the Wayland fd
*/
static gboolean
gdk_wayland_display_can_dispatch (GdkWaylandDisplay *display_wayland)
{
GdkWaylandPollSource *poll_source = (GdkWaylandPollSource *) display_wayland->poll_source;
return poll_source->can_dispatch;
}
/* If we still have events to process and don't need to poll */
static gboolean
gdk_wayland_display_has_events_pending (GdkWaylandDisplay *display_wayland)
{
return gdk_wayland_display_can_dispatch (display_wayland) ||
_gdk_event_queue_find_first (GDK_DISPLAY (display_wayland)) != NULL;
}
static gboolean
gdk_wayland_event_source_prepare (GSource *base,
int *timeout)
{
GdkWaylandEventSource *source = (GdkWaylandEventSource *) base;
*timeout = -1;
return gdk_wayland_display_has_events_pending (source->display);
}
static gboolean
gdk_wayland_event_source_check (GSource *base)
{
GdkWaylandEventSource *source = (GdkWaylandEventSource *) base;
return gdk_wayland_display_has_events_pending (source->display);
}
static gboolean
gdk_wayland_event_source_dispatch (GSource *base,
GSourceFunc callback,
gpointer data)
{
GdkWaylandEventSource *source = (GdkWaylandEventSource *) base;
GdkEvent *event;
event = gdk_display_get_event (GDK_DISPLAY (source->display));
if (event)
{
_gdk_event_emit (event);
gdk_event_unref (event);
}
return G_SOURCE_CONTINUE;
}
static void
gdk_wayland_event_source_finalize (GSource *base)
{
}
static GSourceFuncs gdk_wayland_event_source_funcs = {
gdk_wayland_event_source_prepare,
gdk_wayland_event_source_check,
gdk_wayland_event_source_dispatch,
gdk_wayland_event_source_finalize
};
static gboolean
gdk_wayland_poll_source_prepare (GSource *base,
int *timeout)
{
GdkWaylandPollSource *source = (GdkWaylandPollSource *) base;
GdkWaylandDisplay *display = (GdkWaylandDisplay *) source->display;
GList *l;
*timeout = -1;
if (gdk_wayland_display_has_events_pending (source->display))
return FALSE;
/* wl_display_prepare_read() needs to be balanced with either
* wl_display_read_events() or wl_display_cancel_read()
* (in gdk_wayland_event_source_check() */
if (source->reading)
return FALSE;
/* if prepare_read() returns non-zero, there are events to be dispatched */
if (wl_display_prepare_read (display->wl_display) != 0)
{
source->can_dispatch = TRUE;
return TRUE;
}
/* We need to check whether there are pending events on the surface queues as well,
* but we also need to make sure to only have one active "read" in the end,
* or none if we immediately return TRUE, as multiple reads expect reads from
* as many threads.
*/
for (l = display->event_queues; l; l = l->next)
{
struct wl_event_queue *queue = l->data;
if (wl_display_prepare_read_queue (display->wl_display, queue) != 0)
{
source->can_dispatch = TRUE;
/* cancel the read from before the for loop */
wl_display_cancel_read (display->wl_display);
return TRUE;
}
wl_display_cancel_read (display->wl_display);
}
source->reading = TRUE;
if (wl_display_flush (display->wl_display) < 0)
{
g_message ("Error flushing display: %s", g_strerror (errno));
_exit (1);
}
return FALSE;
}
static gboolean
gdk_wayland_poll_source_check (GSource *base)
{
GdkWaylandPollSource *source = (GdkWaylandPollSource *) base;
GdkWaylandDisplay *display_wayland = (GdkWaylandDisplay *) source->display;
/* read the events from the wayland fd into their respective queues if we have data */
if (source->reading)
{
if (source->pfd.revents & (G_IO_ERR | G_IO_HUP))
{
g_message ("Lost connection to Wayland compositor.");
_exit (1);
}
if (source->pfd.revents & G_IO_IN)
{
if (wl_display_read_events (display_wayland->wl_display) < 0)
{
g_message ("Error reading events from display: %s", g_strerror (errno));
_exit (1);
}
source->pfd.revents = 0;
source->can_dispatch = TRUE;
}
else
wl_display_cancel_read (display_wayland->wl_display);
source->reading = FALSE;
}
return FALSE;
}
static gboolean
gdk_wayland_poll_source_dispatch (GSource *base,
GSourceFunc callback,
gpointer data)
{
return G_SOURCE_CONTINUE;
}
static void
gdk_wayland_poll_source_finalize (GSource *base)
{
GdkWaylandPollSource *source = (GdkWaylandPollSource *) base;
if (source->reading)
wl_display_cancel_read (source->display->wl_display);
source->reading = FALSE;
}
static GSourceFuncs gdk_wayland_poll_source_funcs = {
gdk_wayland_poll_source_prepare,
gdk_wayland_poll_source_check,
gdk_wayland_poll_source_dispatch,
gdk_wayland_poll_source_finalize
};
void
_gdk_wayland_display_deliver_event (GdkDisplay *display,
GdkEvent *event)
{
GList *node;
node = _gdk_event_queue_append (display, event);
_gdk_windowing_got_event (display, node, event,
_gdk_display_get_next_serial (display));
}
void
gdk_wayland_display_install_gsources (GdkWaylandDisplay *display_wayland)
{
GdkDisplay *display = GDK_DISPLAY (display_wayland);
GSource *source;
GdkWaylandEventSource *event_source;
GdkWaylandPollSource *poll_source;
char *name;
/* SOURCE 1 */
source = g_source_new (&gdk_wayland_event_source_funcs,
sizeof (GdkWaylandEventSource));
display_wayland->event_source = source;
event_source = (GdkWaylandEventSource *) source;
name = g_strdup_printf ("GDK Wayland Event source (%s)",
gdk_display_get_name (display));
g_source_set_name (source, name);
g_free (name);
event_source->display = display_wayland;
g_source_set_priority (source, GDK_PRIORITY_EVENTS);
g_source_set_can_recurse (source, TRUE);
g_source_attach (source, NULL);
/* SOURCE 2 */
source = g_source_new (&gdk_wayland_poll_source_funcs,
sizeof (GdkWaylandPollSource));
display_wayland->poll_source = source;
poll_source = (GdkWaylandPollSource *) source;
name = g_strdup_printf ("GDK Wayland Poll source (%s)",
gdk_display_get_name (display));
g_source_set_name (source, name);
g_free (name);
poll_source->display = display_wayland;
poll_source->pfd.fd = wl_display_get_fd (display_wayland->wl_display);
poll_source->pfd.events = G_IO_IN | G_IO_ERR | G_IO_HUP;
g_source_add_poll (source, &poll_source->pfd);
/* We must guarantee to ALWAYS be called and called FIRST after
* every poll - or rather: after every prepare().
* Any other source might call Wayland functions and in turn
* block while waiting for us.
* And GSource has no after_pool() vfunc, check() is not guaranteed
* to be called.
*/
g_source_set_priority (source, G_MININT);
g_source_attach (source, NULL);
}
void
gdk_wayland_display_uninstall_gsources (GdkWaylandDisplay *display_wayland)
{
if (display_wayland->event_source)
{
g_source_destroy (display_wayland->event_source);
g_source_unref (display_wayland->event_source);
display_wayland->event_source = NULL;
}
if (display_wayland->poll_source)
{
g_source_destroy (display_wayland->poll_source);
g_source_unref (display_wayland->poll_source);
display_wayland->poll_source = NULL;
}
}
void
_gdk_wayland_display_queue_events (GdkDisplay *display)
{
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
GdkWaylandPollSource *poll_source;
GList *l;
if (wl_display_dispatch_pending (display_wayland->wl_display) < 0)
{
g_message ("Error %d (%s) dispatching to Wayland display.",
errno, g_strerror (errno));
_exit (1);
}
for (l = display_wayland->event_queues; l; l = l->next)
{
struct wl_event_queue *queue = l->data;
if (wl_display_dispatch_queue_pending (display_wayland->wl_display, queue) < 0)
{
g_message ("Error %d (%s) dispatching to Wayland display.",
errno, g_strerror (errno));
_exit (1);
}
}
poll_source = (GdkWaylandPollSource *) display_wayland->poll_source;
poll_source->can_dispatch = FALSE;
}