diff --git a/src/spice-widget.c b/src/spice-widget.c
index 63111158355ba28d55920f636950cba9be7dedf9..8a269bc2955ed7138c3b3eff2bab1ea80d2254cc 100644
--- a/src/spice-widget.c
+++ b/src/spice-widget.c
@@ -479,6 +479,12 @@ static void spice_display_finalize(GObject *obj)
 
     DISPLAY_DEBUG(display, "Finalize spice display");
 
+#ifdef HAVE_WAYLAND_PROTOCOLS
+    GtkWidget *widget = GTK_WIDGET(display);
+    if GDK_IS_WAYLAND_DISPLAY(gtk_widget_get_display(widget))
+        spice_wayland_extensions_finalize(widget);
+#endif
+
     g_clear_pointer(&d->grabseq, spice_grab_sequence_free);
     g_clear_pointer(&d->activeseq, g_free);
 
diff --git a/src/wayland-extensions.c b/src/wayland-extensions.c
index 64b5139a41467e3e8439cb6d44ecb66c80a9a787..dec69ce4ea4d0cfc44f436497686c05ab21735f6 100644
--- a/src/wayland-extensions.c
+++ b/src/wayland-extensions.c
@@ -24,16 +24,18 @@
 #include <gtk/gtk.h>
 
 #include <gdk/gdkwayland.h>
+#include <wayland-client-core.h>
+#include <glib-unix.h>
 #include "pointer-constraints-unstable-v1-client-protocol.h"
 #include "relative-pointer-unstable-v1-client-protocol.h"
 
 #include "wayland-extensions.h"
 
 static void *
-gtk_wl_registry_bind(GtkWidget *widget,
-                     uint32_t name,
-                     const struct wl_interface *interface,
-                     uint32_t version)
+registry_bind_gtk(GtkWidget *widget,
+                  uint32_t name,
+                  const struct wl_interface *interface,
+                  uint32_t version)
 {
     GdkDisplay *gdk_display = gtk_widget_get_display(widget);
     struct wl_display *display;
@@ -49,24 +51,6 @@ gtk_wl_registry_bind(GtkWidget *widget,
     return wl_registry_bind(registry, name, interface, version);
 }
 
-static void
-gtk_wl_registry_add_listener(GtkWidget *widget, const struct wl_registry_listener *listener)
-{
-    GdkDisplay *gdk_display = gtk_widget_get_display(widget);
-    struct wl_display *display;
-    struct wl_registry *registry;
-
-    if (!GDK_IS_WAYLAND_DISPLAY(gdk_display)) {
-        return;
-    }
-
-    display = gdk_wayland_display_get_wl_display(gdk_display);
-    registry = wl_display_get_registry(display);
-    wl_registry_add_listener(registry, listener, widget);
-    wl_display_roundtrip(display);
-}
-
-
 static void
 registry_handle_global(void *data,
                        struct wl_registry *registry,
@@ -76,24 +60,26 @@ registry_handle_global(void *data,
 {
     GtkWidget *widget = GTK_WIDGET(data);
 
-    if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) {
+    if (g_strcmp0(interface, "zwp_relative_pointer_manager_v1") == 0) {
         struct zwp_relative_pointer_manager_v1 *relative_pointer_manager;
-        relative_pointer_manager = gtk_wl_registry_bind(widget, name,
-                                                        &zwp_relative_pointer_manager_v1_interface,
-                                                        1);
+        relative_pointer_manager = registry_bind_gtk(widget, name,
+                                                     &zwp_relative_pointer_manager_v1_interface,
+                                                     1);
         g_object_set_data_full(G_OBJECT(widget),
                                "zwp_relative_pointer_manager_v1",
                                relative_pointer_manager,
                                (GDestroyNotify)zwp_relative_pointer_manager_v1_destroy);
-    } else if (strcmp(interface, "zwp_pointer_constraints_v1") == 0) {
+        g_object_set_data(G_OBJECT(widget), "zwp_relative_pointer_v1_name", GUINT_TO_POINTER(name));
+    } else if (g_strcmp0(interface, "zwp_pointer_constraints_v1") == 0) {
         struct zwp_pointer_constraints_v1 *pointer_constraints;
-        pointer_constraints = gtk_wl_registry_bind(widget, name,
-                                                   &zwp_pointer_constraints_v1_interface,
-                                                   1);
+        pointer_constraints = registry_bind_gtk(widget, name,
+                                                &zwp_pointer_constraints_v1_interface,
+                                                1);
         g_object_set_data_full(G_OBJECT(widget),
                                "zwp_pointer_constraints_v1",
                                pointer_constraints,
                                (GDestroyNotify)zwp_pointer_constraints_v1_destroy);
+        g_object_set_data(G_OBJECT(widget), "zwp_pointer_constraints_v1_name", GUINT_TO_POINTER(name));
     }
 }
 
@@ -102,6 +88,25 @@ registry_handle_global_remove(void *data,
                               struct wl_registry *registry,
                               uint32_t name)
 {
+    GtkWidget *widget = GTK_WIDGET(data);
+
+    struct zwp_relative_pointer_manager_v1 *relative_pointer_manager;
+    uint32_t relative_pointer_manager_name = 0;
+    relative_pointer_manager = g_object_get_data(G_OBJECT(widget), "zwp_relative_pointer_manager_v1");
+    relative_pointer_manager_name = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(widget), "zwp_relative_pointer_v1_name"));
+    if (relative_pointer_manager && relative_pointer_manager_name == name) {
+        g_object_set_data_full(G_OBJECT(widget), "zwp_relative_pointer_manager_v1", NULL, NULL);
+        g_object_steal_data(G_OBJECT(widget), "zwp_relative_pointer_v1_name");
+    }
+
+    struct zwp_pointer_constraints_v1 *pointer_constraints;
+    uint32_t pointer_constraints_name = 0;
+    pointer_constraints = g_object_get_data(G_OBJECT(widget), "zwp_pointer_constraints_v1");
+    pointer_constraints_name = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(widget), "zwp_pointer_constraints_v1_name"));
+    if (pointer_constraints && pointer_constraints_name == name) {
+        g_object_set_data_full(G_OBJECT(widget), "zwp_pointer_constraints_v1", NULL, NULL);
+        g_object_steal_data(G_OBJECT(widget), "zwp_pointer_constraints_v1_name");
+    }
 }
 
 static const struct wl_registry_listener registry_listener = {
@@ -109,12 +114,109 @@ static const struct wl_registry_listener registry_listener = {
     registry_handle_global_remove
 };
 
+static gpointer
+spice_wayland_thread_run(gpointer data)
+{
+    GtkWidget *widget = GTK_WIDGET(data);
+    struct wl_display *display = gdk_wayland_display_get_wl_display(gtk_widget_get_display(widget));
+    struct wl_event_queue *queue = wl_display_create_queue(display);
+    gint *control_fds = g_object_get_data(G_OBJECT(widget), "control_fds");
+    gint control_read_fd = control_fds[0];
+    GPollFD pollfd[2] = {
+        { wl_display_get_fd(display), G_IO_IN, 0 },
+        { control_read_fd, G_IO_HUP, 0 }
+    };
+    while (1) {
+        while (wl_display_prepare_read_queue(display, queue) != 0) {
+            wl_display_dispatch_queue_pending(display, queue);
+        }
+        wl_display_flush(display);
+        if (g_poll(pollfd, 2, -1) == -1) {
+            wl_display_cancel_read(display);
+            goto error;
+            break;
+        }
+        if (pollfd[1].revents & G_IO_HUP) {
+            // The write end of the pipe is closed, exit the thread
+            wl_display_cancel_read(display);
+            break;
+        }
+        if (wl_display_read_events(display) == -1) {
+            goto error;
+            break;
+        }
+        wl_display_dispatch_queue_pending(display, queue);
+    }
+    return NULL;
+error:
+    g_warning("Failed to run event queue in spice-wayland-thread");
+    return NULL;
+}
+
 void
 spice_wayland_extensions_init(GtkWidget *widget)
 {
     g_return_if_fail(GTK_IS_WIDGET(widget));
 
-    gtk_wl_registry_add_listener(widget, &registry_listener);
+    GdkDisplay *gdk_display = gtk_widget_get_display(widget);
+    if (!GDK_IS_WAYLAND_DISPLAY(gdk_display)) {
+        return;
+    }
+    struct wl_display *display = gdk_wayland_display_get_wl_display(gdk_display);
+    struct wl_event_queue *queue = wl_display_create_queue(display);
+    struct wl_display *display_wrapper = wl_proxy_create_wrapper(display);
+    wl_proxy_set_queue((struct wl_proxy *)display_wrapper, queue);
+    struct wl_registry *registry = wl_display_get_registry(display_wrapper);
+    wl_registry_add_listener(registry, &registry_listener, widget);
+
+    wl_display_roundtrip_queue(display, queue);
+    wl_display_roundtrip_queue(display, queue);
+
+    g_object_set_data_full(G_OBJECT(widget),
+                           "wl_display_wrapper",
+                           display_wrapper,
+                           (GDestroyNotify)wl_proxy_wrapper_destroy);
+    g_object_set_data_full(G_OBJECT(widget),
+                           "wl_event_queue",
+                           queue,
+                           (GDestroyNotify)wl_event_queue_destroy);
+    g_object_set_data_full(G_OBJECT(widget),
+                           "wl_registry",
+                           registry,
+                           (GDestroyNotify)wl_registry_destroy);
+
+    // control_fds is used to communicate between the main thread and the created event queue thread
+    // When the write end of the pipe is closed, the event queue thread will exit
+    gint *control_fds = g_new(gint, 2);
+    GError *error = NULL;
+    if (!g_unix_open_pipe(control_fds, O_CLOEXEC, &error)) {
+        g_warning("Failed to create control pipe: %s", error->message);
+        g_error_free(error);
+        return;
+    }
+    g_object_set_data(G_OBJECT(widget), "control_fds", control_fds);
+
+    GThread *thread = g_thread_new("spice-wayland-thread",
+                                   spice_wayland_thread_run,
+                                   widget);
+    g_object_set_data(G_OBJECT(widget),
+                      "spice-wayland-thread",
+                      thread);
+}
+
+void
+spice_wayland_extensions_finalize(GtkWidget *widget)
+{
+    g_return_if_fail(GTK_IS_WIDGET(widget));
+
+    gint *control_fds = g_object_get_data(G_OBJECT(widget), "control_fds");
+    if (control_fds == NULL) {
+        return;
+    }
+    gint control_write_fd = control_fds[1];
+    close(control_write_fd);
+    g_thread_join(g_object_get_data(G_OBJECT(widget), "spice-wayland-thread"));
+    g_free(control_fds);
 }
 
 
diff --git a/src/wayland-extensions.h b/src/wayland-extensions.h
index bf34044387e15676813210a5832809a5e772ecd5..1bd9a65129bf326cbe718f8ede5c76fd19aabaf4 100644
--- a/src/wayland-extensions.h
+++ b/src/wayland-extensions.h
@@ -20,6 +20,7 @@
 #include <gtk/gtk.h>
 
 void spice_wayland_extensions_init(GtkWidget *widget);
+void spice_wayland_extensions_finalize(GtkWidget *widget);
 int spice_wayland_extensions_enable_relative_pointer(GtkWidget *widget,
                                                      void (*cb)(void *,
                                                                 struct zwp_relative_pointer_v1 *,