All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Marc-André Lureau" <marcandre.lureau@redhat.com>
To: Gerd Hoffmann <kraxel@redhat.com>
Cc: Paolo Bonzini <pbonzini@redhat.com>,
	qemu-devel <qemu-devel@nongnu.org>,
	Markus Armbruster <armbru@redhat.com>
Subject: Re: [PATCH v6 9/9] ui/gtk: add clipboard support
Date: Wed, 19 May 2021 13:46:50 +0400	[thread overview]
Message-ID: <CAMxuvaxfQchy8zuqY1J4yjbNCSj1UnL=AxOfvJaQAuECDjV59g@mail.gmail.com> (raw)
In-Reply-To: <20210519053940.1888907-10-kraxel@redhat.com>

[-- Attachment #1: Type: text/plain, Size: 9948 bytes --]

On Wed, May 19, 2021 at 9:40 AM Gerd Hoffmann <kraxel@redhat.com> wrote:

> This patch adds clipboard support to the qemu gtk ui.
>
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
>

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>

---
>  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 <gdk/gdkwayland.h>
>  #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 <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.  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 <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,
> +                                                GtkClipboard *clipboard)
> +{
> +    QemuClipboardSelection s;
> +
> +    for (s = 0; s < QEMU_CLIPBOARD_SELECTION__COUNT; s++) {
> +        if (gd->gtkcb[s] == 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 = data;
> +    QemuClipboardSelection s = gd_find_selection(gd, clipboard);
> +    QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
> +    QemuClipboardInfo *info = qemu_clipboard_info_ref(gd->cbinfo[s]);
> +
> +    qemu_clipboard_request(info, type);
> +    while (info == gd->cbinfo[s] &&
> +           info->types[type].available &&
> +           info->types[type].data == NULL) {
> +        main_loop_wait(false);
> +    }
> +
> +    if (info == 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 = data;
> +    QemuClipboardSelection s = gd_find_selection(gd, clipboard);
> +
> +    gd->cbowner[s] = false;
> +}
> +
> +static void gd_clipboard_notify(Notifier *notifier, void *data)
> +{
> +    GtkDisplayState *gd = container_of(notifier, GtkDisplayState,
> cbpeer.update);
> +    QemuClipboardInfo *info = data;
> +    QemuClipboardSelection s = info->selection;
> +    bool self_update = info->owner == &gd->cbpeer;
> +
> +    if (info != gd->cbinfo[s]) {
> +        qemu_clipboard_info_unref(gd->cbinfo[s]);
> +        gd->cbinfo[s] = qemu_clipboard_info_ref(info);
> +        gd->cbpending[s] = 0;
> +        if (!self_update) {
> +            GtkTargetList *list;
> +            GtkTargetEntry *targets;
> +            gint n_targets;
> +
> +            list = gtk_target_list_new(NULL, 0);
> +            if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
> +                gtk_target_list_add_text_targets(list, 0);
> +            }
> +            targets = gtk_target_table_new_from_list(list, &n_targets);
> +
> +            gtk_clipboard_clear(gd->gtkcb[s]);
> +            gd->cbowner[s] = 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 = container_of(info->owner, GtkDisplayState,
> cbpeer);
> +    char *text;
> +
> +    switch (type) {
> +    case QEMU_CLIPBOARD_TYPE_TEXT:
> +        text = 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 = data;
> +    QemuClipboardSelection s = 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 = qemu_clipboard_info_new(&gd->cbpeer, s);
> +        if (gtk_clipboard_wait_is_text_available(clipboard)) {
> +            info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
> +        }
> +
> +        qemu_clipboard_update(info);
> +        qemu_clipboard_info_unref(info);
> +        break;
> +    default:
> +        break;
> +    }
> +}
> +
> +void gd_clipboard_init(GtkDisplayState *gd)
> +{
> +    gd->cbpeer.name = "gtk";
> +    gd->cbpeer.update.notify = gd_clipboard_notify;
> +    gd->cbpeer.request = gd_clipboard_request;
> +    qemu_clipboard_peer_register(&gd->cbpeer);
> +
> +    gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD] =
> +        gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE));
> +    gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMARY] =
> +        gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE));
> +    gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY] =
> +        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 = 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
>
>

[-- Attachment #2: Type: text/html, Size: 12434 bytes --]

  reply	other threads:[~2021-05-19  9:48 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-05-19  5:39 [PATCH v6 0/9] ui: add vdagent implementation and clipboard support Gerd Hoffmann
2021-05-19  5:39 ` [PATCH v6 1/9] build: add separate spice-protocol config option Gerd Hoffmann
2021-05-19  5:39 ` [PATCH v6 2/9] ui: add clipboard infrastructure Gerd Hoffmann
2021-05-19  5:39 ` [PATCH v6 3/9] ui: add clipboard documentation Gerd Hoffmann
2021-05-19  5:39 ` [PATCH v6 4/9] ui/vdagent: core infrastructure Gerd Hoffmann
2021-05-19  9:47   ` Marc-André Lureau
2021-05-19  5:39 ` [PATCH v6 5/9] ui/vdagent: add mouse support Gerd Hoffmann
2021-05-19 12:06   ` Markus Armbruster
2021-05-19  5:39 ` [PATCH v6 6/9] ui/vdagent: add clipboard support Gerd Hoffmann
2021-05-19  9:47   ` Marc-André Lureau
2021-05-19  5:39 ` [PATCH v6 7/9] ui/vnc: " Gerd Hoffmann
2021-05-19  5:39 ` [PATCH v6 8/9] ui/gtk: move struct GtkDisplayState to ui/gtk.h Gerd Hoffmann
2021-05-19  5:39 ` [PATCH v6 9/9] ui/gtk: add clipboard support Gerd Hoffmann
2021-05-19  9:46   ` Marc-André Lureau [this message]
2021-05-19  5:54 ` [PATCH v6 0/9] ui: add vdagent implementation and " no-reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CAMxuvaxfQchy8zuqY1J4yjbNCSj1UnL=AxOfvJaQAuECDjV59g@mail.gmail.com' \
    --to=marcandre.lureau@redhat.com \
    --cc=armbru@redhat.com \
    --cc=kraxel@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.