From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-10.5 required=3.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,HEADER_FROM_DIFFERENT_DOMAINS,HTML_MESSAGE,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 58A04C433ED for ; Wed, 19 May 2021 09:48:56 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 7A41B60FE8 for ; Wed, 19 May 2021 09:48:55 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 7A41B60FE8 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Received: from localhost ([::1]:44376 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1ljIow-0006MP-9M for qemu-devel@archiver.kernel.org; Wed, 19 May 2021 05:48:54 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:44554) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ljInM-0004L3-6A for qemu-devel@nongnu.org; Wed, 19 May 2021 05:47:17 -0400 Received: from us-smtp-delivery-124.mimecast.com ([216.205.24.124]:31710) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ljInE-00077G-G9 for qemu-devel@nongnu.org; Wed, 19 May 2021 05:47:15 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1621417626; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=xRDANo6K2Su0lUksuKn5+D774zRXLkjOwHav8ogB2wM=; b=YdS6v6IDJ6EzBZHs2ARj6JsO6yB5n0fmeIwhPvLrkNKjWgDYzyxm8xICaIN+Dbleea+n0c I7yxjbwmpxpsjqP3uSoiRhVYXHGGZj4kxuLF4c+s3vn6UnWAkVNrNqlzH02EMOj3ouD6bp KlUFjZyO/1Z5Mb9CArvVN0WhxUPakDM= Received: from mail-pj1-f72.google.com (mail-pj1-f72.google.com [209.85.216.72]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-409-RMq3FzDcNhSVhIW57xSIXg-1; Wed, 19 May 2021 05:47:02 -0400 X-MC-Unique: RMq3FzDcNhSVhIW57xSIXg-1 Received: by mail-pj1-f72.google.com with SMTP id w12-20020a17090a528cb029015d7f990752so3575896pjh.0 for ; Wed, 19 May 2021 02:47:02 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:cc; bh=xRDANo6K2Su0lUksuKn5+D774zRXLkjOwHav8ogB2wM=; b=IMmC/DX+YcyFgw/90yluUMl9hKjSRDMJdkNU3ItmZikfTBC/kV3Zxhjp8qW95Xb8va fuYOID2yvk5XOt3TVAj/tiIcNCZanAas0YKJwQcloPUAcCos6YzzBjBid1yL51h8uqFu 7tg+dvM920DOB6myO8DvyL1Bjy3wQWc77y6n75cpv16cvdKNdj3VIzWc/lMjlL3ftBDs AlSqr2cQ2PlghjIfzgXZ/Y2xd/2q34o5dnNCgQo/eTKp4AUXt1rYSNmP2K+TJzfzEk6C fAzX9gl8ew0ot2aklRE8sVzkmuEY2BTaHhs/DD683rC8uQYiOvpVeYjSa1u7Cws/xaw0 phFA== X-Gm-Message-State: AOAM531ubefQ0ZhHGabvwUrT1me7wtZq0xFJEkZZ0AkyLobYcUuNaKH+ FB1RuLJV4irIs5LxxqIQKRs3iRy7q2Tjg8uHlUJ8Mkhet/fiJTHMfigu3JHUuLeRvuRalgBur7Q idyGjZyQlu6uAsTrtGFdxV3lZNoW5WsU= X-Received: by 2002:a17:903:31d3:b029:ee:bccd:e686 with SMTP id v19-20020a17090331d3b02900eebccde686mr10051419ple.1.1621417621676; Wed, 19 May 2021 02:47:01 -0700 (PDT) X-Google-Smtp-Source: ABdhPJzKO004VBosWXYDZpRl/4XbF+UO0/6yFgEcRp+MFi5c4F6gSo1IbfpGigQ104UZB9Zh0n5HFt7D0/Tw0jw1J4c= X-Received: by 2002:a17:903:31d3:b029:ee:bccd:e686 with SMTP id v19-20020a17090331d3b02900eebccde686mr10051401ple.1.1621417621377; Wed, 19 May 2021 02:47:01 -0700 (PDT) MIME-Version: 1.0 References: <20210519053940.1888907-1-kraxel@redhat.com> <20210519053940.1888907-10-kraxel@redhat.com> In-Reply-To: <20210519053940.1888907-10-kraxel@redhat.com> From: =?UTF-8?B?TWFyYy1BbmRyw6kgTHVyZWF1?= Date: Wed, 19 May 2021 13:46:50 +0400 Message-ID: Subject: Re: [PATCH v6 9/9] ui/gtk: add clipboard support To: Gerd Hoffmann Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=mlureau@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: multipart/alternative; boundary="0000000000005ae8a605c2abb551" Received-SPF: pass client-ip=216.205.24.124; envelope-from=mlureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -31 X-Spam_score: -3.2 X-Spam_bar: --- X-Spam_report: (-3.2 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.374, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, HTML_MESSAGE=0.001, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Paolo Bonzini , qemu-devel , Markus Armbruster Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" --0000000000005ae8a605c2abb551 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Wed, May 19, 2021 at 9:40 AM Gerd Hoffmann wrote: > This patch adds clipboard support to the qemu gtk ui. > > Signed-off-by: Gerd Hoffmann > Reviewed-by: Marc-Andr=C3=A9 Lureau --- > include/ui/gtk.h | 10 +++ > ui/gtk-clipboard.c | 192 +++++++++++++++++++++++++++++++++++++++++++++ > ui/gtk.c | 1 + > ui/meson.build | 2 +- > 4 files changed, 204 insertions(+), 1 deletion(-) > create mode 100644 ui/gtk-clipboard.c > > diff --git a/include/ui/gtk.h b/include/ui/gtk.h > index 6e751794043f..9516670ebc87 100644 > --- a/include/ui/gtk.h > +++ b/include/ui/gtk.h > @@ -18,6 +18,7 @@ > #include > #endif > > +#include "ui/clipboard.h" > #include "ui/console.h" > #include "ui/kbd-state.h" > #if defined(CONFIG_OPENGL) > @@ -137,6 +138,12 @@ struct GtkDisplayState { > > bool external_pause_update; > > + QemuClipboardPeer cbpeer; > + QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT]; > + uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT]; > + GtkClipboard *gtkcb[QEMU_CLIPBOARD_SELECTION__COUNT]; > + bool cbowner[QEMU_CLIPBOARD_SELECTION__COUNT]; > + > DisplayOptions *opts; > }; > > @@ -207,4 +214,7 @@ void gtk_gl_area_init(void); > int gd_gl_area_make_current(DisplayChangeListener *dcl, > QEMUGLContext ctx); > > +/* gtk-clipboard.c */ > +void gd_clipboard_init(GtkDisplayState *gd); > + > #endif /* UI_GTK_H */ > diff --git a/ui/gtk-clipboard.c b/ui/gtk-clipboard.c > new file mode 100644 > index 000000000000..bff28d203014 > --- /dev/null > +++ b/ui/gtk-clipboard.c > @@ -0,0 +1,192 @@ > +/* > + * GTK UI -- clipboard support > + * > + * Copyright (C) 2021 Gerd Hoffmann > + * > + * This program 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 2 of the License, or > + * (at your option) any later version. > + * > + * This program 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 this program; if not, see . > + * > + */ > + > +#include "qemu/osdep.h" > +#include "qemu-common.h" > +#include "qemu/main-loop.h" > + > +#include "ui/gtk.h" > + > +static QemuClipboardSelection gd_find_selection(GtkDisplayState *gd, > + GtkClipboard *clipboard) > +{ > + QemuClipboardSelection s; > + > + for (s =3D 0; s < QEMU_CLIPBOARD_SELECTION__COUNT; s++) { > + if (gd->gtkcb[s] =3D=3D clipboard) { > + return s; > + } > + } > + return QEMU_CLIPBOARD_SELECTION_CLIPBOARD; > +} > + > +static void gd_clipboard_get_data(GtkClipboard *clipboard, > + GtkSelectionData *selection_data, > + guint selection_info, > + gpointer data) > +{ > + GtkDisplayState *gd =3D data; > + QemuClipboardSelection s =3D gd_find_selection(gd, clipboard); > + QemuClipboardType type =3D QEMU_CLIPBOARD_TYPE_TEXT; > + QemuClipboardInfo *info =3D qemu_clipboard_info_ref(gd->cbinfo[s]); > + > + qemu_clipboard_request(info, type); > + while (info =3D=3D gd->cbinfo[s] && > + info->types[type].available && > + info->types[type].data =3D=3D NULL) { > + main_loop_wait(false); > + } > + > + if (info =3D=3D gd->cbinfo[s] && gd->cbowner[s]) { > + gtk_selection_data_set_text(selection_data, > + info->types[type].data, > + info->types[type].size); > + } else { > + /* clipboard owner changed while waiting for the data */ > + } > + > + qemu_clipboard_info_unref(info); > +} > + > +static void gd_clipboard_clear(GtkClipboard *clipboard, > + gpointer data) > +{ > + GtkDisplayState *gd =3D data; > + QemuClipboardSelection s =3D gd_find_selection(gd, clipboard); > + > + gd->cbowner[s] =3D false; > +} > + > +static void gd_clipboard_notify(Notifier *notifier, void *data) > +{ > + GtkDisplayState *gd =3D container_of(notifier, GtkDisplayState, > cbpeer.update); > + QemuClipboardInfo *info =3D data; > + QemuClipboardSelection s =3D info->selection; > + bool self_update =3D info->owner =3D=3D &gd->cbpeer; > + > + if (info !=3D gd->cbinfo[s]) { > + qemu_clipboard_info_unref(gd->cbinfo[s]); > + gd->cbinfo[s] =3D qemu_clipboard_info_ref(info); > + gd->cbpending[s] =3D 0; > + if (!self_update) { > + GtkTargetList *list; > + GtkTargetEntry *targets; > + gint n_targets; > + > + list =3D gtk_target_list_new(NULL, 0); > + if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) { > + gtk_target_list_add_text_targets(list, 0); > + } > + targets =3D gtk_target_table_new_from_list(list, &n_targets)= ; > + > + gtk_clipboard_clear(gd->gtkcb[s]); > + gd->cbowner[s] =3D true; > + gtk_clipboard_set_with_data(gd->gtkcb[s], > + targets, n_targets, > + gd_clipboard_get_data, > + gd_clipboard_clear, > + gd); > + > + gtk_target_table_free(targets, n_targets); > + gtk_target_list_unref(list); > + } > + return; > + } > + > + if (self_update) { > + return; > + } > + > + /* > + * Clipboard got updated, with data probably. No action here, we > + * are waiting for updates in gd_clipboard_get_data(). > + */ > +} > + > +static void gd_clipboard_request(QemuClipboardInfo *info, > + QemuClipboardType type) > +{ > + GtkDisplayState *gd =3D container_of(info->owner, GtkDisplayState, > cbpeer); > + char *text; > + > + switch (type) { > + case QEMU_CLIPBOARD_TYPE_TEXT: > + text =3D gtk_clipboard_wait_for_text(gd->gtkcb[info->selection])= ; > + if (text) { > + qemu_clipboard_set_data(&gd->cbpeer, info, type, > + strlen(text), text, true); > + g_free(text); > + } > + break; > + default: > + break; > + } > +} > + > +static void gd_owner_change(GtkClipboard *clipboard, > + GdkEvent *event, > + gpointer data) > +{ > + GtkDisplayState *gd =3D data; > + QemuClipboardSelection s =3D gd_find_selection(gd, clipboard); > + QemuClipboardInfo *info; > + > + if (gd->cbowner[s]) { > + /* ignore notifications about our own grabs */ > + return; > + } > + > + > + switch (event->owner_change.reason) { > + case GDK_SETTING_ACTION_NEW: > + info =3D qemu_clipboard_info_new(&gd->cbpeer, s); > + if (gtk_clipboard_wait_is_text_available(clipboard)) { > + info->types[QEMU_CLIPBOARD_TYPE_TEXT].available =3D true; > + } > + > + qemu_clipboard_update(info); > + qemu_clipboard_info_unref(info); > + break; > + default: > + break; > + } > +} > + > +void gd_clipboard_init(GtkDisplayState *gd) > +{ > + gd->cbpeer.name =3D "gtk"; > + gd->cbpeer.update.notify =3D gd_clipboard_notify; > + gd->cbpeer.request =3D gd_clipboard_request; > + qemu_clipboard_peer_register(&gd->cbpeer); > + > + gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD] =3D > + gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE)); > + gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMARY] =3D > + gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE)); > + gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY] =3D > + gtk_clipboard_get(gdk_atom_intern("SECONDARY", FALSE)); > + > + g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD], > + "owner-change", G_CALLBACK(gd_owner_change), gd); > + g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMARY], > + "owner-change", G_CALLBACK(gd_owner_change), gd); > + g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY], > + "owner-change", G_CALLBACK(gd_owner_change), gd); > +} > diff --git a/ui/gtk.c b/ui/gtk.c > index 7da288a25156..98046f577b9d 100644 > --- a/ui/gtk.c > +++ b/ui/gtk.c > @@ -2267,6 +2267,7 @@ static void gtk_display_init(DisplayState *ds, > DisplayOptions *opts) > opts->u.gtk.grab_on_hover) { > gtk_menu_item_activate(GTK_MENU_ITEM(s->grab_on_hover_item)); > } > + gd_clipboard_init(s); > } > > static void early_gtk_display_init(DisplayOptions *opts) > diff --git a/ui/meson.build b/ui/meson.build > index f37ef882e0e3..b5aed14886cf 100644 > --- a/ui/meson.build > +++ b/ui/meson.build > @@ -65,7 +65,7 @@ if gtk.found() > softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('win32-kbd-hook.c'= )) > > gtk_ss =3D ss.source_set() > - gtk_ss.add(gtk, vte, pixman, files('gtk.c')) > + gtk_ss.add(gtk, vte, pixman, files('gtk.c', 'gtk-clipboard.c')) > gtk_ss.add(when: x11, if_true: files('x_keymap.c')) > gtk_ss.add(when: [opengl, 'CONFIG_OPENGL'], if_true: > files('gtk-gl-area.c')) > gtk_ss.add(when: [x11, opengl, 'CONFIG_OPENGL'], if_true: > files('gtk-egl.c')) > -- > 2.31.1 > > --0000000000005ae8a605c2abb551 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable


=
On Wed, May 19, 2021 at 9:40 AM Gerd = Hoffmann <kraxel@redhat.com>= wrote:
This pat= ch adds clipboard support to the qemu gtk ui.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>

= Reviewed-by: Marc-Andr=C3=A9 Lureau <marcandre.lureau@redhat.com>=C2=A0

---
=C2=A0include/ui/gtk.h=C2=A0 =C2=A0|=C2=A0 10 +++
=C2=A0ui/gtk-clipboard.c | 192 ++++++++++++++++++++++++++++++++++++++++++++= +
=C2=A0ui/gtk.c=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0|=C2=A0 =C2=A01 + =C2=A0ui/meson.build=C2=A0 =C2=A0 =C2=A0|=C2=A0 =C2=A02 +-
=C2=A04 files changed, 204 insertions(+), 1 deletion(-)
=C2=A0create mode 100644 ui/gtk-clipboard.c

diff --git a/include/ui/gtk.h b/include/ui/gtk.h
index 6e751794043f..9516670ebc87 100644
--- a/include/ui/gtk.h
+++ b/include/ui/gtk.h
@@ -18,6 +18,7 @@
=C2=A0#include <gdk/gdkwayland.h>
=C2=A0#endif

+#include "ui/clipboard.h"
=C2=A0#include "ui/console.h"
=C2=A0#include "ui/kbd-state.h"
=C2=A0#if defined(CONFIG_OPENGL)
@@ -137,6 +138,12 @@ struct GtkDisplayState {

=C2=A0 =C2=A0 =C2=A0bool external_pause_update;

+=C2=A0 =C2=A0 QemuClipboardPeer cbpeer;
+=C2=A0 =C2=A0 QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT];<= br> +=C2=A0 =C2=A0 uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT];
+=C2=A0 =C2=A0 GtkClipboard *gtkcb[QEMU_CLIPBOARD_SELECTION__COUNT];
+=C2=A0 =C2=A0 bool cbowner[QEMU_CLIPBOARD_SELECTION__COUNT];
+
=C2=A0 =C2=A0 =C2=A0DisplayOptions *opts;
=C2=A0};

@@ -207,4 +214,7 @@ void gtk_gl_area_init(void);
=C2=A0int gd_gl_area_make_current(DisplayChangeListener *dcl,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0QEMUGLContext ctx);

+/* gtk-clipboard.c */
+void gd_clipboard_init(GtkDisplayState *gd);
+
=C2=A0#endif /* UI_GTK_H */
diff --git a/ui/gtk-clipboard.c b/ui/gtk-clipboard.c
new file mode 100644
index 000000000000..bff28d203014
--- /dev/null
+++ b/ui/gtk-clipboard.c
@@ -0,0 +1,192 @@
+/*
+ * GTK UI -- clipboard support
+ *
+ * Copyright (C) 2021 Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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.=C2=A0 See the GNU<= br> + * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses= />.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/main-loop.h"
+
+#include "ui/gtk.h"
+
+static QemuClipboardSelection gd_find_selection(GtkDisplayState *gd,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 GtkClipboard *clipboard)
+{
+=C2=A0 =C2=A0 QemuClipboardSelection s;
+
+=C2=A0 =C2=A0 for (s =3D 0; s < QEMU_CLIPBOARD_SELECTION__COUNT; s++) {=
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (gd->gtkcb[s] =3D=3D clipboard) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return s;
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 return QEMU_CLIPBOARD_SELECTION_CLIPBOARD;
+}
+
+static void gd_clipboard_get_data(GtkClipboard=C2=A0 =C2=A0 =C2=A0*clipboa= rd,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 GtkSelectionData *selection_d= ata,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 guint=C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0selection_info,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gpointer=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 data)
+{
+=C2=A0 =C2=A0 GtkDisplayState *gd =3D data;
+=C2=A0 =C2=A0 QemuClipboardSelection s =3D gd_find_selection(gd, clipboard= );
+=C2=A0 =C2=A0 QemuClipboardType type =3D QEMU_CLIPBOARD_TYPE_TEXT;
+=C2=A0 =C2=A0 QemuClipboardInfo *info =3D qemu_clipboard_info_ref(gd->c= binfo[s]);
+
+=C2=A0 =C2=A0 qemu_clipboard_request(info, type);
+=C2=A0 =C2=A0 while (info =3D=3D gd->cbinfo[s] &&
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0info->types[type].available &a= mp;&
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0info->types[type].data =3D=3D = NULL) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 main_loop_wait(false);
+=C2=A0 =C2=A0 }
+
+=C2=A0 =C2=A0 if (info =3D=3D gd->cbinfo[s] && gd->cbowner[s= ]) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 gtk_selection_data_set_text(selection_data, +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 info->types[type].d= ata,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 info->types[type].s= ize);
+=C2=A0 =C2=A0 } else {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 /* clipboard owner changed while waiting for t= he data */
+=C2=A0 =C2=A0 }
+
+=C2=A0 =C2=A0 qemu_clipboard_info_unref(info);
+}
+
+static void gd_clipboard_clear(GtkClipboard *clipboard,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0gpointer data)
+{
+=C2=A0 =C2=A0 GtkDisplayState *gd =3D data;
+=C2=A0 =C2=A0 QemuClipboardSelection s =3D gd_find_selection(gd, clipboard= );
+
+=C2=A0 =C2=A0 gd->cbowner[s] =3D false;
+}
+
+static void gd_clipboard_notify(Notifier *notifier, void *data)
+{
+=C2=A0 =C2=A0 GtkDisplayState *gd =3D container_of(notifier, GtkDisplaySta= te, cbpeer.update);
+=C2=A0 =C2=A0 QemuClipboardInfo *info =3D data;
+=C2=A0 =C2=A0 QemuClipboardSelection s =3D info->selection;
+=C2=A0 =C2=A0 bool self_update =3D info->owner =3D=3D &gd->cbpee= r;
+
+=C2=A0 =C2=A0 if (info !=3D gd->cbinfo[s]) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 qemu_clipboard_info_unref(gd->cbinfo[s]); +=C2=A0 =C2=A0 =C2=A0 =C2=A0 gd->cbinfo[s] =3D qemu_clipboard_info_ref(i= nfo);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 gd->cbpending[s] =3D 0;
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (!self_update) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 GtkTargetList *list;
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 GtkTargetEntry *targets;
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gint n_targets;
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 list =3D gtk_target_list_new(NUL= L, 0);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if (info->types[QEMU_CLIPBOAR= D_TYPE_TEXT].available) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gtk_target_list_ad= d_text_targets(list, 0);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 targets =3D gtk_target_table_new= _from_list(list, &n_targets);
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gtk_clipboard_clear(gd->gtkcb= [s]);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gd->cbowner[s] =3D true;
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gtk_clipboard_set_with_data(gd-&= gt;gtkcb[s],
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 targets,= n_targets,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gd_clipb= oard_get_data,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gd_clipb= oard_clear,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gd);
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gtk_target_table_free(targets, n= _targets);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gtk_target_list_unref(list);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return;
+=C2=A0 =C2=A0 }
+
+=C2=A0 =C2=A0 if (self_update) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return;
+=C2=A0 =C2=A0 }
+
+=C2=A0 =C2=A0 /*
+=C2=A0 =C2=A0 =C2=A0* Clipboard got updated, with data probably.=C2=A0 No = action here, we
+=C2=A0 =C2=A0 =C2=A0* are waiting for updates in gd_clipboard_get_data().<= br> +=C2=A0 =C2=A0 =C2=A0*/
+}
+
+static void gd_clipboard_request(QemuClipboardInfo *info,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0QemuClipboardType type)
+{
+=C2=A0 =C2=A0 GtkDisplayState *gd =3D container_of(info->owner, GtkDisp= layState, cbpeer);
+=C2=A0 =C2=A0 char *text;
+
+=C2=A0 =C2=A0 switch (type) {
+=C2=A0 =C2=A0 case QEMU_CLIPBOARD_TYPE_TEXT:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 text =3D gtk_clipboard_wait_for_text(gd->gt= kcb[info->selection]);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (text) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 qemu_clipboard_set_data(&gd-= >cbpeer, info, type,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 strlen(text), text, tr= ue);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 g_free(text);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 break;
+=C2=A0 =C2=A0 default:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 break;
+=C2=A0 =C2=A0 }
+}
+
+static void gd_owner_change(GtkClipboard *clipboard,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 GdkEvent *event,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 gpointer data)
+{
+=C2=A0 =C2=A0 GtkDisplayState *gd =3D data;
+=C2=A0 =C2=A0 QemuClipboardSelection s =3D gd_find_selection(gd, clipboard= );
+=C2=A0 =C2=A0 QemuClipboardInfo *info;
+
+=C2=A0 =C2=A0 if (gd->cbowner[s]) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 /* ignore notifications about our own grabs */=
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return;
+=C2=A0 =C2=A0 }
+
+
+=C2=A0 =C2=A0 switch (event->owner_change.reason) {
+=C2=A0 =C2=A0 case GDK_SETTING_ACTION_NEW:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 info =3D qemu_clipboard_info_new(&gd->c= bpeer, s);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (gtk_clipboard_wait_is_text_available(clipb= oard)) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 info->types[QEMU_CLIPBOARD_TY= PE_TEXT].available =3D true;
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 qemu_clipboard_update(info);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 qemu_clipboard_info_unref(info);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 break;
+=C2=A0 =C2=A0 default:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 break;
+=C2=A0 =C2=A0 }
+}
+
+void gd_clipboard_init(GtkDisplayState *gd)
+{
+=C2=A0 =C2=A0 gd->cbpeer.name =3D "gtk";
+=C2=A0 =C2=A0 gd->cbpeer.update.notify =3D gd_clipboard_notify;
+=C2=A0 =C2=A0 gd->cbpeer.request =3D gd_clipboard_request;
+=C2=A0 =C2=A0 qemu_clipboard_peer_register(&gd->cbpeer);
+
+=C2=A0 =C2=A0 gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD] =3D
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 gtk_clipboard_get(gdk_atom_intern("CLIPBO= ARD", FALSE));
+=C2=A0 =C2=A0 gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMARY] =3D
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 gtk_clipboard_get(gdk_atom_intern("PRIMAR= Y", FALSE));
+=C2=A0 =C2=A0 gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY] =3D
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 gtk_clipboard_get(gdk_atom_intern("SECOND= ARY", FALSE));
+
+=C2=A0 =C2=A0 g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPB= OARD],
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0"owner-change", G_CALLBACK(gd_owner_change), gd);
+=C2=A0 =C2=A0 g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMA= RY],
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0"owner-change", G_CALLBACK(gd_owner_change), gd);
+=C2=A0 =C2=A0 g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECON= DARY],
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0"owner-change", G_CALLBACK(gd_owner_change), gd);
+}
diff --git a/ui/gtk.c b/ui/gtk.c
index 7da288a25156..98046f577b9d 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -2267,6 +2267,7 @@ static void gtk_display_init(DisplayState *ds, Displa= yOptions *opts)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0opts->u.gtk.grab_on_hover) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0gtk_menu_item_activate(GTK_MENU_ITEM(s-&g= t;grab_on_hover_item));
=C2=A0 =C2=A0 =C2=A0}
+=C2=A0 =C2=A0 gd_clipboard_init(s);
=C2=A0}

=C2=A0static void early_gtk_display_init(DisplayOptions *opts)
diff --git a/ui/meson.build b/ui/meson.build
index f37ef882e0e3..b5aed14886cf 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -65,7 +65,7 @@ if gtk.found()
=C2=A0 =C2=A0softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files(&#= 39;win32-kbd-hook.c'))

=C2=A0 =C2=A0gtk_ss =3D ss.source_set()
-=C2=A0 gtk_ss.add(gtk, vte, pixman, files('gtk.c'))
+=C2=A0 gtk_ss.add(gtk, vte, pixman, files('gtk.c', 'gtk-clipbo= ard.c'))
=C2=A0 =C2=A0gtk_ss.add(when: x11, if_true: files('x_keymap.c')) =C2=A0 =C2=A0gtk_ss.add(when: [opengl, 'CONFIG_OPENGL'], if_true: f= iles('gtk-gl-area.c'))
=C2=A0 =C2=A0gtk_ss.add(when: [x11, opengl, 'CONFIG_OPENGL'], if_tr= ue: files('gtk-egl.c'))
--
2.31.1

--0000000000005ae8a605c2abb551--