golang-design / clipboard

đź“‹ cross-platform clipboard package that supports accessing text and image in Go (macOS/Linux/Windows/Android/iOS)
https://golang.design/x/clipboard
MIT License
632 stars 68 forks source link

Question: performance issues and doesn't work as expected on Linux wayland-session DE. #66

Open woshikedayaa opened 1 month ago

woshikedayaa commented 1 month ago

First of all

I want to express my gratitude for developing this package! It has been incredibly helpful in many aspects of my work, and I truly appreciate the time and effort you've invested in it. However, I have noticed some performance issues and unexpected behavior when using it in a Linux Wayland session desktop environment.

Environment

$ cat /etc/os-release
NAME="Arch Linux"
PRETTY_NAME="Arch Linux"
ID=arch
BUILD_ID=rolling
ANSI_COLOR="38;2;23;147;209"
HOME_URL="https://archlinux.org/"
DOCUMENTATION_URL="https://wiki.archlinux.org/"
SUPPORT_URL="https://bbs.archlinux.org/"
BUG_REPORT_URL="https://gitlab.archlinux.org/groups/archlinux/-/issues"
PRIVACY_POLICY_URL="https://terms.archlinux.org/docs/privacy-policy/"
LOGO=archlinux-logo
$ uname -a
Linux arch 6.6.56-1-lts #1 SMP PREEMPT_DYNAMIC Thu, 10 Oct 2024 12:04:53 +0000 x86_64 GNU/Linux

Performance issues

I noticed that each write operation starts a coroutine that then enters a C function

go func() { // serve as a daemon until the ownership is terminated.
        runtime.LockOSThread()
        defer runtime.UnlockOSThread()

        cs := C.CString(s)
        defer C.free(unsafe.Pointer(cs))

        h := cgo.NewHandle(start)
        var ok C.int
        if len(buf) == 0 {
            ok = C.clipboard_write(cs, nil, 0, C.uintptr_t(h))
        } else {
            ok = C.clipboard_write(cs, (*C.uchar)(unsafe.Pointer(&(buf[0]))), C.size_t(len(buf)), C.uintptr_t(h))
        }
        if ok != C.int(0) {
            fmt.Fprintf(os.Stderr, "write failed with status: %d\n", int(ok))
        }
        done <- struct{}{}
        close(done)
    }()

Within the C function clipboard_write, it appears to enter a loop that seems unable to exit (at least, I haven't found an exit condition so far).

// clipboard_write writes the given buf of size n as type typ.
// if start is provided, the value of start will be changed to 1 to indicate
// if the write is availiable for reading.
int clipboard_write(char *typ, unsigned char *buf, size_t n, uintptr_t handle) {
    // ......   
    XEvent event;
    XSelectionRequestEvent* xsr;
    int notified = 0;
    for (;;) { // loop forever
        if (notified == 0) {
            syncStatus(handle, 1); // notify Go side
            notified = 1;
        }
        (*P_XNextEvent)(d, &event);
        switch (event.type) {
        case SelectionClear:
            (*P_XCloseDisplay)(d);
            return 0;
        case SelectionNotify:
            break;
        case SelectionRequest:
            // ......
            if ((R & 2) == 0) (*P_XSendEvent)(d, ev.requestor, 0, 0, (XEvent *)&ev);
            break;
        }
    }
}

I am concerned that repeatedly calling write in this way could lead to performance issues over time. If there’s no performance impact, could you kindly explain why? I would love to understand the mechanics better and learn from your insights.

--

Doesn't Work as Expected on Linux Wayland-session DE

I'm encountering an issue with clipboard operations in a Wayland session on Linux. Here’s a summary of the problem:

// Initialization...
<-clipboard.Write(f, []byte("123456")) // This hangs indefinitely until I manually send a Control-C signal.
//go func() { <-changed }()
return nil

To troubleshoot, I modified the code as follows:

// ...
clipboard.Write(f, []byte("123456"))
return nil

However, this approach doesn’t work as expected—the clipboard doesn’t receive the data successfully. I suspect this is because the Goroutine handling the Write operation doesn't complete before the program exits.

To verify, I added a short delay after Write:

// ...
clipboard.Write(f, []byte("123456"))
time.Sleep(time.Second)
return nil

This works, but introducing an arbitrary sleep is not ideal.

Expected Behavior:
I expected that, after successfully writing to the clipboard, the channel would receive a signal or value indicating completion. This way, I could avoid any delay-based workarounds.

Would you be able to clarify if there’s a preferred way to ensure the Write operation completes before the program exits? Or is there an internal mechanism for confirming successful writes that I might have missed?