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
578 stars 64 forks source link

Limit number of read requests by other applications #22

Open qjerome opened 2 years ago

qjerome commented 2 years ago

Hey @changkun,

Thank you for your great work. I am wondering how hard it would be to implement the equivalent of the -l, -loops switch of xclip (X11 tool on Linux) in your package. The goal would be to allow only a limited number of Read operations on the clipboard before the content gets flushed. I am currently relying on piping to xclip to do that but I think it is not very clean and I'd like to opt for a full C/Go approach.

Cheers,

changkun commented 2 years ago

Hi, thanks for the request. After a quick inspection, I think the feature is pretty much linux-only because neither windows nor darwin can get the number of readers to a given write request. Therefore I am not sure if this make sense to add to the package.

However, if you'd like to have this feature to use, I still prepared a change list, with examples showing how to use them:

diff --git a/clipboard_linux.c b/clipboard_linux.c
index 336ab2b..b487fc6 100644
--- a/clipboard_linux.c
+++ b/clipboard_linux.c
@@ -17,6 +17,7 @@

 // syncStatus is a function from the Go side.
 extern void syncStatus(uintptr_t handle, int status);
+extern int reachReaderLimit();

 void *libX11;

@@ -176,6 +177,10 @@ int clipboard_write(char *typ, unsigned char *buf, size_t n, uintptr_t handle) {
                 R = (*P_XChangeProperty)(ev.display, ev.requestor, ev.property,
                     XA_ATOM, 32, PropModeReplace,
                     (unsigned char *)&targets, sizeof(targets)/sizeof(Atom));
+                if (reachReaderLimit() == 1) {
+                    (*P_XCloseDisplay)(d);
+                    return 0;
+                }
             } else {
                 ev.property = None;
             }
diff --git a/clipboard_linux.go b/clipboard_linux.go
index 2137235..44b21d2 100644
--- a/clipboard_linux.go
+++ b/clipboard_linux.go
@@ -32,6 +32,7 @@ import (
    "fmt"
    "os"
    "runtime"
+   "sync/atomic"
    "time"
    "unsafe"

@@ -170,3 +171,23 @@ func syncStatus(h uintptr, val int) {
    v <- val
    cgo.Handle(h).Delete()
 }
+
+var readLimit = int64(0)
+
+// SetReaderLimit limits the number of the readers after a Write.
+// Setting it to zero means no limit.
+func SetReaderLimit(n int) {
+   atomic.StoreInt64(&readLimit, int64(n))
+}
+
+var counter = 0
+
+//export reachReaderLimit
+func reachReaderLimit() C.int {
+   counter++
+   l := atomic.LoadInt64(&readLimit)
+   if counter%3 == 0 && l > 0 && counter/3 > int(l) {
+       return 1
+   }
+   return 0
+}

You could apply this change to a fork of this project then use the modified version.

changkun commented 2 years ago

Here is an example:

package main

import (
    "golang.design/x/clipboard"
)

func init() {
    if err := clipboard.Init(); err != nil {
        panic(err)
    }
}

func main() {
    content := "can only paste 5 times\n"

    clipboard.SetReaderLimit(5) // this

    ch := clipboard.Write(clipboard.FmtText, []byte(content))

    <-ch
}
qjerome commented 2 years ago

This is what I would qualify as a very quick answer :+1: I'll try this out ASAP. In my opinion, it would not be choking to have this method implemented in your package, only for Linux. It is quite frequent that some Go packages very OS specific do not expose the same APIs on the different OS. The simpler example that comes to my mind is the syscall package.

Thanks a lot

qjerome commented 2 years ago

I tried out your patch and it seems it does not work as intended (at least in my setup).

changkun commented 2 years ago

I see. It is probably due to application specific requests of events. The patch's approach rely on the observation that a paste can cause 3 events in the event loop (what I observed when I paste in VSCode, and why reachReaderLimit has a magic number 3).

The general idea would work but I think to really make this robust will need further investigation on how xclip resolves this..

qjerome commented 2 years ago

It is fun because I did actually read the code ten times to understand why you did this %3 and /3. Then I resigned and thought that it was probably a X11 related magic constant :rofl:

changkun commented 2 years ago

It is fun because I did actually read the code ten times to understand why you did this %3 and /3. Then I resigned and thought that it was probably a X11 related magic constant 🤣

Sigh. I don't understand why it such a behavior. But it sounds like that an application may sent SelectionRequest multiple times. This sounds like that a reader will try to request multiple times to actually get the content, which cause multiple reads at one paste.

qjerome commented 2 years ago

No pressure man, I'll keep an eye on your project and if you happen to find a solution for this I will use it in place of my dirty xclip hack ... Thank you for your time