Hi

On Mon, Jul 28, 2025 at 5:06 PM startergo <starte...@protonmail.com> wrote:
>
> Subject: Re: [PATCH RFC v1 1/1] ui/sdl2: clipboard sharing implementation for 
> SDL
> In-Reply-To: 
> <cakmqykm+4kbqynhkqwxqphyp1zn5crtxc4r7yj8wbx5m+tc...@mail.gmail.com>
> References: <20231108105411.1759509-1-ad...@xutaxkamay.com> 
> <cakmqykm+4kbqynhkqwxqphyp1zn5crtxc4r7yj8wbx5m+tc...@mail.gmail.com>
>
> Hi Marc-André,
>
> Following up on your thoughtful review of the SDL clipboard RFC from November 
> 2023,
> I've developed a comprehensive implementation that directly addresses the 
> concerns
> you raised about main loop reentrancy and clipboard management issues.
>
> ## Key Improvements Addressing Your Feedback:
>
> **1. Main Loop Reentrancy Solution**
> You correctly identified the problematic `main_loop_wait(false)` pattern from 
> the
> original RFC. My implementation eliminates this entirely by:
> - Using immediate data processing without busy-wait loops
> - Implementing proper asynchronous clipboard handling
> - Following the same safety patterns used in QEMU issue #1150 resolution
>
> **2. Clipboard Manager Conflict Prevention**
> Your concern about fighting with clipboard managers is addressed through:
> - Self-update loop prevention in `sdl2_clipboard_update()`
> - Clean ownership tracking via `info->owner == &scon->cbpeer` checks
> - No automatic clipboard stealing or aggressive management behavior
>
> **3. Null Termination Handling**
> Regarding your question about proper string handling: my implementation 
> ensures:
> - SDL-compatible null-terminated UTF-8 strings using `g_strndup()`
> - Proper length calculation excluding null terminator for QEMU clipboard
> - Safe handling of embedded nulls in clipboard data
>
> **4. Configuration Options**
> Following your suggestion about the optional nature (like gtk_clipboard), the
> implementation includes:
> - `CONFIG_SDL_CLIPBOARD` build option for conditional compilation
> - Clean fallback when clipboard support is disabled
> - No forced dependencies or runtime requirements
>
> ## Technical Implementation Details:
>
> The implementation uses event-driven clipboard monitoring via 
> `SDL_CLIPBOARDUPDATE`
> rather than polling, and integrates cleanly with QEMU's unified clipboard 
> subsystem
> through the `QemuClipboardPeer` interface.
>
> Key safety features:
> - No main loop reentrancy
> - Proper memory management with SDL-specific allocation/deallocation
> - Self-update prevention to avoid clipboard ownership conflicts
> - UTF-8 string validation and proper null termination
>
> ## Testing and Validation:
>
> Extensive testing on macOS with Linux  guest demonstrates:
> - Reliable bidirectional clipboard operation
> - No performance impact or stability regressions
> - Clean coexistence with system clipboard managers
> - Proper handling of various text encodings and formats
>
> This implementation addresses the SDL2 backend's clipboard gap while 
> incorporating
> lessons learned from both the GTK clipboard implementation and the community
> feedback from the original RFC.
>
> The patch brings SDL2 to feature parity with other QEMU display backends 
> regarding
> clipboard functionality, using a safety-first approach that avoids the 
> pitfalls
> identified in your review.
>
> Would appreciate your thoughts on this refined approach. The implementation is
> ready for community review and addresses the architectural concerns raised in
> the original thread.
>
> Best regards,
> startergo
>
> ---
>
> [Complete patch follows below]
>

Please send a properly formatted git patch:
https://www.qemu.org/docs/master/devel/submitting-a-patch.html#do-not-send-as-an-attachment

> From: startergo <starte...@protonmail.com>
> Date: Mon, 28 Jul 2025 12:00:00 +0000
> Subject: [PATCH] ui/sdl2: Add bidirectional clipboard support
>
> This patch implements bidirectional clipboard functionality for the SDL2
> display backend, addressing the lack of clipboard integration when using
> SDL2 as the display interface.
>
> The implementation addresses concerns raised in previous SDL clipboard
> discussions, particularly around main loop reentrancy and clipboard
> manager conflicts identified in the November 2023 RFC review.Key features:
> - Bidirectional text clipboard synchronization between host and guest
> - Safe implementation avoiding main loop reentrancy issues
> - Proper memory management with SDL-specific allocation/deallocation
> - Integration with QEMU's unified clipboard subsystem
> - Configurable via CONFIG_SDL_CLIPBOARD build option

The patch is missing meson updates for a new "sdl_clipboard" option.

It would not be required if you can avoid the main loop reentrancy.
You removed it, but I am afraid you didn't address the issue from
Kamay's original patch.

>
> The implementation follows established QEMU patterns and addresses
> reentrancy concerns similar to those resolved in QEMU issue #1150.
>
> Implementation details:
> - Uses SDL_CLIPBOARDUPDATE events to detect host clipboard changes
> - Implements QemuClipboardPeer interface for guest-to-host direction
> - Avoids busy-wait loops by processing clipboard data immediately
> - Proper UTF-8 handling following SDL2 string conventions
> - Memory management uses SDL_free() for SDL-allocated strings
> - Self-update prevention to avoid clipboard manager conflicts
>
> The patch has been tested extensively on macOS with various guest
> operating systems including Linux and Windows. Clipboard functionality
> works reliably in both directions without performance impact or
> stability issues.
>
> This addresses a significant usability gap in the SDL2 backend, bringing
> it to feature parity with other QEMU display backends regarding clipboard
> functionality.
>
> Signed-off-by: startergo <starte...@protonmail.com>
> ---
>  ui/meson.build        |   1 +
>  include/ui/sdl2.h     |  11 +++
>  ui/clipboard.c        | 104 ++++++++++++++++++++++++++++++++++++++++++

name it sdl2-clipboard.c

>  ui/sdl2.c            |   8 ++++
>  4 files changed, 124 insertions(+)
>  create mode 100644 ui/clipboard.c
>
> diff --git a/ui/meson.build b/ui/meson.build
> index 92e7e61219..c5e7880ca5 100644
> --- a/ui/meson.build
> +++ b/ui/meson.build
> @@ -59,6 +59,7 @@ if sdl.found()
>    softmmu_ss.add(when: 'CONFIG_SDL', if_true: files(
>      'sdl2-2d.c',
>      'sdl2-gl.c',
> +    'clipboard.c',

make it conditional on have_sdl_clipboard option


>      'sdl2-input.c',
>      'sdl2.c'
>    ))
> diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h
> index 1624ad6938..28a17e7b53 100644
> --- a/include/ui/sdl2.h
> +++ b/include/ui/sdl2.h
> @@ -7,6 +7,10 @@
>  # include <SDL.h>
>  # include <SDL_syswm.h>
>
> +#ifdef CONFIG_SDL_CLIPBOARD
> +#include "ui/clipboard.h"
> +#endif
> +
>  struct sdl2_console {
>      DisplayChangeListener dcl;
>      DisplaySurface *surface;
> @@ -22,6 +26,10 @@ struct sdl2_console {
>      int idle_counter;
>      int ignore_hotkeys;
>      SDL_GLContext winctx;
> +
> +#ifdef CONFIG_SDL_CLIPBOARD
> +    QemuClipboardPeer cbpeer;
> +#endif
>  };
>
>  void sdl2_window_create(struct sdl2_console *scon);
> @@ -39,4 +47,7 @@ void sdl2_reset_keys(struct sdl2_console *scon);
>  void sdl2_process_key(struct sdl2_console *scon,
>                        SDL_KeyboardEvent *ev);
>
> +void sdl2_clipboard_init(struct sdl2_console *scon);
> +void sdl2_clipboard_handle_request(struct sdl2_console *scon);
> +
>  #endif /* SDL2_H */
> diff --git a/ui/clipboard.c b/ui/clipboard.c
> new file mode 100644
> index 0000000000..98fa9f1c2a
> --- /dev/null
> +++ b/ui/clipboard.c
> @@ -0,0 +1,104 @@
> +/*
> + * QEMU SDL2 clipboard implementation
> + *
> + * Copyright (c) 2025 startergo
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a 
> copy
> + * of this software and associated documentation files (the "Software"), to 
> deal
> + * in the Software without restriction, including without limitation the 
> rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
> FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> + * THE SOFTWARE.
> + */

QEMU code has SPDX identifiers and is GPL2+:

/*
 * Copyright ...
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */


> +
> +#include "qemu/osdep.h"
> +#include "ui/sdl2.h"
> +
> +#ifdef CONFIG_SDL_CLIPBOARD
> +
> +static void sdl2_clipboard_update(struct sdl2_console *scon,
> +                                  QemuClipboardInfo *info)
> +{
> +    QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
> +    g_autoptr(QemuClipboardData) data = NULL;
> +
> +    /* Prevent self-update loops */
> +    if (info->owner == &scon->cbpeer) {
> +        return;
> +    }
> +
> +    /* Only handle text clipboard for now */
> +    if (!qemu_clipboard_info_has_type(info, type)) {
> +        return;
> +    }
> +
> +    /* Get clipboard data from QEMU */
> +    data = qemu_clipboard_request(info, type);
> +    if (!data || !data->data || data->size == 0) {
> +        return;
> +    }

Here, Kamay's patch was waiting for the clipboard to be filled. You
can't assume the data is readily available after calling
qemu_clipboard_request(). vdagent code will send a request, and data
can come later asynchronously via VD_AGENT_CLIPBOARD message. The code
must deal with QEMU_CLIPBOARD_UPDATE_INFO notifiers / callbacks and
handle request state tracking to properly deal with this.


> +
> +    /*
> +     * Ensure null termination for SDL clipboard.
> +     * QEMU clipboard data may not be null-terminated.
> +     */
> +    g_autofree char *text = g_strndup((const char *)data->data, data->size);

casting required here?

> +    if (text) {

text can't be NULL if data->data is non-NULL. If we want to handle the
case anyway, we could have a trace or a warning

> +        SDL_SetClipboardText(text);
> +    }
> +}
> +
> +static void sdl2_clipboard_notify(Notifier *notifier,
> +                                  void *data)
> +{
> +    QemuClipboardNotify *notify = data;
> +    struct sdl2_console *scon =
> +        container_of(notifier, struct sdl2_console, cbpeer.notifier);
> +
> +    switch (notify->type) {
> +    case QEMU_CLIPBOARD_UPDATE_INFO:
> +        sdl2_clipboard_update(scon, notify->info);
> +        break;
> +    case QEMU_CLIPBOARD_RESET_SERIAL:
> +        /* Nothing to do for reset */
> +        break;
> +    }
> +}
> +
> +static void sdl2_clipboard_request(QemuClipboardInfo *info,
> +                                   QemuClipboardType type)
> +{
> +    struct sdl2_console *scon =
> +        container_of(info->owner, struct sdl2_console, cbpeer);
> +    char *sdl_text = NULL;
> +
> +    switch (type) {
> +    case QEMU_CLIPBOARD_TYPE_TEXT:
> +        if (!SDL_HasClipboardText()) {
> +            return;
> +        }
> +
> +        sdl_text = SDL_GetClipboardText();
> +        if (sdl_text && strlen(sdl_text) > 0) {

Interesting that SDL decided that empty string is for error reporting.

Could you simplify the check with sdl_text[0] != '\0' instead? Also
add a warning with SDL_GetError() if it's empty.


> +            /*
> +             * SDL guarantees null-terminated UTF-8 strings.
> +             * Pass length without null terminator as QEMU clipboard
> +             * will handle null termination consistently.
> +             */
> +            qemu_clipboard_set_data(&scon->cbpeer, info, type,
> +                                    strlen(sdl_text), sdl_text, true);
> +        }
> +
> +        /* Always free SDL-allocated memory */
> +        if (sdl_text) {

drop the condition, GetClipboardText() should not return NULL, and
SDL_free(NULL) is fine anyway.

> +            SDL_free(sdl_text);
> +        }
> +        break;
> +    default:
> +        break;
> +    }
> +}
> +
> +void sdl2_clipboard_handle_request(struct sdl2_console *scon)
> +{
> +    g_autoptr(QemuClipboardInfo) info =
> +        qemu_clipboard_info_new(&scon->cbpeer,
> +                                QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
> +
> +    if (info) {

qemu_clipboard_info_new never returns NULL

> +        sdl2_clipboard_request(info, QEMU_CLIPBOARD_TYPE_TEXT);
> +    }
> +}
> +
> +void sdl2_clipboard_init(struct sdl2_console *scon)
> +{
> +    scon->cbpeer.name = "sdl2";
> +    scon->cbpeer.notifier.notify = sdl2_clipboard_notify;
> +    scon->cbpeer.request = sdl2_clipboard_request;
> +
> +    /* Register the clipboard peer with QEMU */
> +    qemu_clipboard_peer_register(&scon->cbpeer);
> +}
> +
> +#endif /* CONFIG_SDL_CLIPBOARD */
> diff --git a/ui/sdl2.c b/ui/sdl2.c
> index 1a83c3b1bf..5678930d3c 100644
> --- a/ui/sdl2.c
> +++ b/ui/sdl2.c
> @@ -691,6 +691,11 @@ void sdl2_poll_events(struct sdl2_console *scon)
>          case SDL_WINDOWEVENT:
>              handle_windowevent(ev);
>              break;
> +#ifdef CONFIG_SDL_CLIPBOARD
> +        case SDL_CLIPBOARDUPDATE:
> +            sdl2_clipboard_handle_request(scon);
> +            break;
> +#endif
>          default:
>              break;
>          }
> @@ -914,6 +919,9 @@ static void sdl2_display_init(DisplayState *ds, 
> DisplayOptions *o)
>              qemu_console_set_window_id(con, info.info.x11.window);
>  #endif
>          }
> +#ifdef CONFIG_SDL_CLIPBOARD
> +        sdl2_clipboard_init(&sdl2_console[i]);
> +#endif
>      }
>
>  #ifdef CONFIG_SDL_IMAGE
>


-- 
Marc-André Lureau

Reply via email to