clipboard icon indicating copy to clipboard operation
clipboard copied to clipboard

Limit number of read requests by other applications

Open qjerome opened this issue 3 years ago • 8 comments

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,

qjerome avatar Mar 04 '22 08:03 qjerome

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 avatar Mar 04 '22 09:03 changkun

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
}

changkun avatar Mar 04 '22 09:03 changkun

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 avatar Mar 04 '22 09:03 qjerome

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

  • When pasting to my terminal app, IĀ can paste an infinite number of times -> it seams readReaderLimit never gets called
  • When pasting to a browser app (tested chrome and firefox), IĀ can paste only one time (second time terminates the program)
  • When pasting to thunderbird, IĀ can paste two times (third time terminates the program)

qjerome avatar Mar 04 '22 10:03 qjerome

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..

changkun avatar Mar 04 '22 10:03 changkun

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:

qjerome avatar Mar 04 '22 10:03 qjerome

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.

changkun avatar Mar 04 '22 11:03 changkun

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

qjerome avatar Mar 04 '22 12:03 qjerome