qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

dbus-clipboard.c (13992B)


      1 /*
      2  * QEMU DBus display
      3  *
      4  * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
      5  *
      6  * Permission is hereby granted, free of charge, to any person obtaining a copy
      7  * of this software and associated documentation files (the "Software"), to deal
      8  * in the Software without restriction, including without limitation the rights
      9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     10  * copies of the Software, and to permit persons to whom the Software is
     11  * furnished to do so, subject to the following conditions:
     12  *
     13  * The above copyright notice and this permission notice shall be included in
     14  * all copies or substantial portions of the Software.
     15  *
     16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
     19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     22  * THE SOFTWARE.
     23  */
     24 #include "qemu/osdep.h"
     25 #include "qemu/dbus.h"
     26 #include "qemu/main-loop.h"
     27 #include "qom/object_interfaces.h"
     28 #include "sysemu/sysemu.h"
     29 #include "qapi/error.h"
     30 #include "trace.h"
     31 
     32 #include "dbus.h"
     33 
     34 #define MIME_TEXT_PLAIN_UTF8 "text/plain;charset=utf-8"
     35 
     36 static void
     37 dbus_clipboard_complete_request(
     38     DBusDisplay *dpy,
     39     GDBusMethodInvocation *invocation,
     40     QemuClipboardInfo *info,
     41     QemuClipboardType type)
     42 {
     43     GVariant *v_data = g_variant_new_from_data(
     44         G_VARIANT_TYPE("ay"),
     45         info->types[type].data,
     46         info->types[type].size,
     47         TRUE,
     48         (GDestroyNotify)qemu_clipboard_info_unref,
     49         qemu_clipboard_info_ref(info));
     50 
     51     qemu_dbus_display1_clipboard_complete_request(
     52         dpy->clipboard, invocation,
     53         MIME_TEXT_PLAIN_UTF8, v_data);
     54 }
     55 
     56 static void
     57 dbus_clipboard_update_info(DBusDisplay *dpy, QemuClipboardInfo *info)
     58 {
     59     bool self_update = info->owner == &dpy->clipboard_peer;
     60     const char *mime[QEMU_CLIPBOARD_TYPE__COUNT + 1] = { 0, };
     61     DBusClipboardRequest *req;
     62     int i = 0;
     63 
     64     if (info->owner == NULL) {
     65         if (dpy->clipboard_proxy) {
     66             qemu_dbus_display1_clipboard_call_release(
     67                 dpy->clipboard_proxy,
     68                 info->selection,
     69                 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
     70         }
     71         return;
     72     }
     73 
     74     if (self_update || !info->has_serial) {
     75         return;
     76     }
     77 
     78     req = &dpy->clipboard_request[info->selection];
     79     if (req->invocation && info->types[req->type].data) {
     80         dbus_clipboard_complete_request(dpy, req->invocation, info, req->type);
     81         g_clear_object(&req->invocation);
     82         g_source_remove(req->timeout_id);
     83         req->timeout_id = 0;
     84         return;
     85     }
     86 
     87     if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
     88         mime[i++] = MIME_TEXT_PLAIN_UTF8;
     89     }
     90 
     91     if (i > 0) {
     92         if (dpy->clipboard_proxy) {
     93             qemu_dbus_display1_clipboard_call_grab(
     94                 dpy->clipboard_proxy,
     95                 info->selection,
     96                 info->serial,
     97                 mime,
     98                 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
     99         }
    100     }
    101 }
    102 
    103 static void
    104 dbus_clipboard_reset_serial(DBusDisplay *dpy)
    105 {
    106     if (dpy->clipboard_proxy) {
    107         qemu_dbus_display1_clipboard_call_register(
    108             dpy->clipboard_proxy,
    109             G_DBUS_CALL_FLAGS_NONE,
    110             -1, NULL, NULL, NULL);
    111     }
    112 }
    113 
    114 static void
    115 dbus_clipboard_notify(Notifier *notifier, void *data)
    116 {
    117     DBusDisplay *dpy =
    118         container_of(notifier, DBusDisplay, clipboard_peer.notifier);
    119     QemuClipboardNotify *notify = data;
    120 
    121     switch (notify->type) {
    122     case QEMU_CLIPBOARD_UPDATE_INFO:
    123         dbus_clipboard_update_info(dpy, notify->info);
    124         return;
    125     case QEMU_CLIPBOARD_RESET_SERIAL:
    126         dbus_clipboard_reset_serial(dpy);
    127         return;
    128     }
    129 }
    130 
    131 static void
    132 dbus_clipboard_qemu_request(QemuClipboardInfo *info,
    133                             QemuClipboardType type)
    134 {
    135     DBusDisplay *dpy = container_of(info->owner, DBusDisplay, clipboard_peer);
    136     g_autofree char *mime = NULL;
    137     g_autoptr(GVariant) v_data = NULL;
    138     g_autoptr(GError) err = NULL;
    139     const char *data = NULL;
    140     const char *mimes[] = { MIME_TEXT_PLAIN_UTF8, NULL };
    141     size_t n;
    142 
    143     if (type != QEMU_CLIPBOARD_TYPE_TEXT) {
    144         /* unsupported atm */
    145         return;
    146     }
    147 
    148     if (dpy->clipboard_proxy) {
    149         if (!qemu_dbus_display1_clipboard_call_request_sync(
    150                 dpy->clipboard_proxy,
    151                 info->selection,
    152                 mimes,
    153                 G_DBUS_CALL_FLAGS_NONE, -1, &mime, &v_data, NULL, &err)) {
    154             error_report("Failed to request clipboard: %s", err->message);
    155             return;
    156         }
    157 
    158         if (g_strcmp0(mime, MIME_TEXT_PLAIN_UTF8)) {
    159             error_report("Unsupported returned MIME: %s", mime);
    160             return;
    161         }
    162 
    163         data = g_variant_get_fixed_array(v_data, &n, 1);
    164         qemu_clipboard_set_data(&dpy->clipboard_peer, info, type,
    165                                 n, data, true);
    166     }
    167 }
    168 
    169 static void
    170 dbus_clipboard_request_cancelled(DBusClipboardRequest *req)
    171 {
    172     if (!req->invocation) {
    173         return;
    174     }
    175 
    176     g_dbus_method_invocation_return_error(
    177         req->invocation,
    178         DBUS_DISPLAY_ERROR,
    179         DBUS_DISPLAY_ERROR_FAILED,
    180         "Cancelled clipboard request");
    181 
    182     g_clear_object(&req->invocation);
    183     g_source_remove(req->timeout_id);
    184     req->timeout_id = 0;
    185 }
    186 
    187 static void
    188 dbus_clipboard_unregister_proxy(DBusDisplay *dpy)
    189 {
    190     const char *name = NULL;
    191     int i;
    192 
    193     for (i = 0; i < G_N_ELEMENTS(dpy->clipboard_request); ++i) {
    194         dbus_clipboard_request_cancelled(&dpy->clipboard_request[i]);
    195     }
    196 
    197     if (!dpy->clipboard_proxy) {
    198         return;
    199     }
    200 
    201     name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
    202     trace_dbus_clipboard_unregister(name);
    203     g_clear_object(&dpy->clipboard_proxy);
    204 }
    205 
    206 static void
    207 dbus_on_clipboard_proxy_name_owner_changed(
    208     DBusDisplay *dpy,
    209     GObject *object,
    210     GParamSpec *pspec)
    211 {
    212     dbus_clipboard_unregister_proxy(dpy);
    213 }
    214 
    215 static gboolean
    216 dbus_clipboard_register(
    217     DBusDisplay *dpy,
    218     GDBusMethodInvocation *invocation)
    219 {
    220     g_autoptr(GError) err = NULL;
    221     const char *name = NULL;
    222 
    223     if (dpy->clipboard_proxy) {
    224         g_dbus_method_invocation_return_error(
    225             invocation,
    226             DBUS_DISPLAY_ERROR,
    227             DBUS_DISPLAY_ERROR_FAILED,
    228             "Clipboard peer already registered!");
    229         return DBUS_METHOD_INVOCATION_HANDLED;
    230     }
    231 
    232     dpy->clipboard_proxy =
    233         qemu_dbus_display1_clipboard_proxy_new_sync(
    234             g_dbus_method_invocation_get_connection(invocation),
    235             G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
    236             g_dbus_method_invocation_get_sender(invocation),
    237             "/org/qemu/Display1/Clipboard",
    238             NULL,
    239             &err);
    240     if (!dpy->clipboard_proxy) {
    241         g_dbus_method_invocation_return_error(
    242             invocation,
    243             DBUS_DISPLAY_ERROR,
    244             DBUS_DISPLAY_ERROR_FAILED,
    245             "Failed to setup proxy: %s", err->message);
    246         return DBUS_METHOD_INVOCATION_HANDLED;
    247     }
    248 
    249     name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
    250     trace_dbus_clipboard_register(name);
    251 
    252     g_object_connect(dpy->clipboard_proxy,
    253                      "swapped-signal::notify::g-name-owner",
    254                      dbus_on_clipboard_proxy_name_owner_changed, dpy,
    255                      NULL);
    256     qemu_clipboard_reset_serial();
    257 
    258     qemu_dbus_display1_clipboard_complete_register(dpy->clipboard, invocation);
    259     return DBUS_METHOD_INVOCATION_HANDLED;
    260 }
    261 
    262 static gboolean
    263 dbus_clipboard_check_caller(DBusDisplay *dpy, GDBusMethodInvocation *invocation)
    264 {
    265     if (!dpy->clipboard_proxy ||
    266         g_strcmp0(g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)),
    267                   g_dbus_method_invocation_get_sender(invocation))) {
    268         g_dbus_method_invocation_return_error(
    269             invocation,
    270             DBUS_DISPLAY_ERROR,
    271             DBUS_DISPLAY_ERROR_FAILED,
    272             "Unregistered caller");
    273         return FALSE;
    274     }
    275 
    276     return TRUE;
    277 }
    278 
    279 static gboolean
    280 dbus_clipboard_unregister(
    281     DBusDisplay *dpy,
    282     GDBusMethodInvocation *invocation)
    283 {
    284     if (!dbus_clipboard_check_caller(dpy, invocation)) {
    285         return DBUS_METHOD_INVOCATION_HANDLED;
    286     }
    287 
    288     dbus_clipboard_unregister_proxy(dpy);
    289 
    290     qemu_dbus_display1_clipboard_complete_unregister(
    291         dpy->clipboard, invocation);
    292 
    293     return DBUS_METHOD_INVOCATION_HANDLED;
    294 }
    295 
    296 static gboolean
    297 dbus_clipboard_grab(
    298     DBusDisplay *dpy,
    299     GDBusMethodInvocation *invocation,
    300     gint arg_selection,
    301     guint arg_serial,
    302     const gchar *const *arg_mimes)
    303 {
    304     QemuClipboardSelection s = arg_selection;
    305     g_autoptr(QemuClipboardInfo) info = NULL;
    306 
    307     if (!dbus_clipboard_check_caller(dpy, invocation)) {
    308         return DBUS_METHOD_INVOCATION_HANDLED;
    309     }
    310 
    311     if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
    312         g_dbus_method_invocation_return_error(
    313             invocation,
    314             DBUS_DISPLAY_ERROR,
    315             DBUS_DISPLAY_ERROR_FAILED,
    316             "Invalid clipboard selection: %d", arg_selection);
    317         return DBUS_METHOD_INVOCATION_HANDLED;
    318     }
    319 
    320     info = qemu_clipboard_info_new(&dpy->clipboard_peer, s);
    321     if (g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8)) {
    322         info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
    323     }
    324     info->serial = arg_serial;
    325     info->has_serial = true;
    326     if (qemu_clipboard_check_serial(info, true)) {
    327         qemu_clipboard_update(info);
    328     } else {
    329         trace_dbus_clipboard_grab_failed();
    330     }
    331 
    332     qemu_dbus_display1_clipboard_complete_grab(dpy->clipboard, invocation);
    333     return DBUS_METHOD_INVOCATION_HANDLED;
    334 }
    335 
    336 static gboolean
    337 dbus_clipboard_release(
    338     DBusDisplay *dpy,
    339     GDBusMethodInvocation *invocation,
    340     gint arg_selection)
    341 {
    342     if (!dbus_clipboard_check_caller(dpy, invocation)) {
    343         return DBUS_METHOD_INVOCATION_HANDLED;
    344     }
    345 
    346     qemu_clipboard_peer_release(&dpy->clipboard_peer, arg_selection);
    347 
    348     qemu_dbus_display1_clipboard_complete_release(dpy->clipboard, invocation);
    349     return DBUS_METHOD_INVOCATION_HANDLED;
    350 }
    351 
    352 static gboolean
    353 dbus_clipboard_request_timeout(gpointer user_data)
    354 {
    355     dbus_clipboard_request_cancelled(user_data);
    356     return G_SOURCE_REMOVE;
    357 }
    358 
    359 static gboolean
    360 dbus_clipboard_request(
    361     DBusDisplay *dpy,
    362     GDBusMethodInvocation *invocation,
    363     gint arg_selection,
    364     const gchar *const *arg_mimes)
    365 {
    366     QemuClipboardSelection s = arg_selection;
    367     QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
    368     QemuClipboardInfo *info = NULL;
    369 
    370     if (!dbus_clipboard_check_caller(dpy, invocation)) {
    371         return DBUS_METHOD_INVOCATION_HANDLED;
    372     }
    373 
    374     if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
    375         g_dbus_method_invocation_return_error(
    376             invocation,
    377             DBUS_DISPLAY_ERROR,
    378             DBUS_DISPLAY_ERROR_FAILED,
    379             "Invalid clipboard selection: %d", arg_selection);
    380         return DBUS_METHOD_INVOCATION_HANDLED;
    381     }
    382 
    383     if (dpy->clipboard_request[s].invocation) {
    384         g_dbus_method_invocation_return_error(
    385             invocation,
    386             DBUS_DISPLAY_ERROR,
    387             DBUS_DISPLAY_ERROR_FAILED,
    388             "Pending request");
    389         return DBUS_METHOD_INVOCATION_HANDLED;
    390     }
    391 
    392     info = qemu_clipboard_info(s);
    393     if (!info || !info->owner || info->owner == &dpy->clipboard_peer) {
    394         g_dbus_method_invocation_return_error(
    395             invocation,
    396             DBUS_DISPLAY_ERROR,
    397             DBUS_DISPLAY_ERROR_FAILED,
    398             "Empty clipboard");
    399         return DBUS_METHOD_INVOCATION_HANDLED;
    400     }
    401 
    402     if (!g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8) ||
    403         !info->types[type].available) {
    404         g_dbus_method_invocation_return_error(
    405             invocation,
    406             DBUS_DISPLAY_ERROR,
    407             DBUS_DISPLAY_ERROR_FAILED,
    408             "Unhandled MIME types requested");
    409         return DBUS_METHOD_INVOCATION_HANDLED;
    410     }
    411 
    412     if (info->types[type].data) {
    413         dbus_clipboard_complete_request(dpy, invocation, info, type);
    414     } else {
    415         qemu_clipboard_request(info, type);
    416 
    417         dpy->clipboard_request[s].invocation = g_object_ref(invocation);
    418         dpy->clipboard_request[s].type = type;
    419         dpy->clipboard_request[s].timeout_id =
    420             g_timeout_add_seconds(5, dbus_clipboard_request_timeout,
    421                                   &dpy->clipboard_request[s]);
    422     }
    423 
    424     return DBUS_METHOD_INVOCATION_HANDLED;
    425 }
    426 
    427 void
    428 dbus_clipboard_init(DBusDisplay *dpy)
    429 {
    430     g_autoptr(GDBusObjectSkeleton) clipboard = NULL;
    431 
    432     assert(!dpy->clipboard);
    433 
    434     clipboard = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Clipboard");
    435     dpy->clipboard = qemu_dbus_display1_clipboard_skeleton_new();
    436     g_object_connect(dpy->clipboard,
    437                      "swapped-signal::handle-register",
    438                      dbus_clipboard_register, dpy,
    439                      "swapped-signal::handle-unregister",
    440                      dbus_clipboard_unregister, dpy,
    441                      "swapped-signal::handle-grab",
    442                      dbus_clipboard_grab, dpy,
    443                      "swapped-signal::handle-release",
    444                      dbus_clipboard_release, dpy,
    445                      "swapped-signal::handle-request",
    446                      dbus_clipboard_request, dpy,
    447                      NULL);
    448 
    449     g_dbus_object_skeleton_add_interface(
    450         G_DBUS_OBJECT_SKELETON(clipboard),
    451         G_DBUS_INTERFACE_SKELETON(dpy->clipboard));
    452     g_dbus_object_manager_server_export(dpy->server, clipboard);
    453     dpy->clipboard_peer.name = "dbus";
    454     dpy->clipboard_peer.notifier.notify = dbus_clipboard_notify;
    455     dpy->clipboard_peer.request = dbus_clipboard_qemu_request;
    456     qemu_clipboard_peer_register(&dpy->clipboard_peer);
    457 }