diff --git a/display/x11.go b/display/x11.go index 05aadb4..0f6f227 100644 --- a/display/x11.go +++ b/display/x11.go @@ -14,11 +14,11 @@ func (xds *XDS) Runtime() string { } func (xds *XDS) CopyText(text string) { - handlers.X11SetClipboardText(text) + shell.X11CopyText(text) } func (xds *XDS) CopyImage(filePath string) { - handlers.X11SetClipboardImage(filePath) + shell.X11CopyImage(filePath) } func (xds *XDS) ReadClipboard() string { diff --git a/handlers/x11.go b/handlers/x11.go index 90fd95b..afb05d2 100644 --- a/handlers/x11.go +++ b/handlers/x11.go @@ -141,65 +141,73 @@ char* getClipboardTextX11() { unsigned char* getClipboardImageX11(int *out_len) { init_x11(); if (!dpy) return NULL; - *out_len = 0; Atom sel = XA_CLIPBOARD; + Atom TARGETS = XInternAtom(dpy, "TARGETS", False); // preferred MIME targets (BMP removed) Atom PNG = XInternAtom(dpy, "image/png", False); Atom JPEG = XInternAtom(dpy, "image/jpeg", False); - Atom targets[] = { PNG, JPEG }; - const int ntargets = sizeof(targets) / sizeof(targets[0]); - - for (int i = 0; i < ntargets; i++) { - Atom target = targets[i]; - - // Ask clipboard owner to convert to requested type - XConvertSelection(dpy, sel, target, target, win, CurrentTime); - XFlush(dpy); + // First ask what formats are available + XConvertSelection(dpy, sel, TARGETS, TARGETS, win, CurrentTime); + XFlush(dpy); - // Wait for the SelectionNotify event - XEvent ev; - XNextEvent(dpy, &ev); + XEvent ev; + XNextEvent(dpy, &ev); - if (ev.type != SelectionNotify) - continue; + if (ev.type != SelectionNotify || ev.xselection.property == None) + return NULL; - if (ev.xselection.property == None) - continue; + Atom type; + int format; + unsigned long len, bytes_left; + unsigned char *data = NULL; - Atom type; - int format; - unsigned long len, bytes_left; - unsigned char *data = NULL; + XGetWindowProperty(dpy, win, TARGETS, 0, ~0, False, + AnyPropertyType, &type, &format, + &len, &bytes_left, &data); - if (XGetWindowProperty(dpy, win, target, 0, ~0, False, - AnyPropertyType, &type, &format, - &len, &bytes_left, &data) != Success) { - continue; - } + if (!data) return NULL; - if (!data || len == 0) { - if (data) XFree(data); - continue; - } + // Check if PNG or JPEG is in the supported targets + Atom *atoms = (Atom*)data; + Atom target = None; + for (unsigned long i = 0; i < len; i++) { + if (atoms[i] == PNG) { target = PNG; break; } + if (atoms[i] == JPEG) { target = JPEG; break; } + } + XFree(data); - // XGetWindowProperty returns len in terms of format units, not bytes - // format is in bits (8, 16, or 32), so calculate actual byte length - int actual_len = len * (format / 8); + if (target == None) return NULL; // no image format available - // Copy result to malloc'd buffer (Go will free this) - unsigned char *copy = malloc(actual_len); - memcpy(copy, data, actual_len); - XFree(data); + // Now request the actual image data + XConvertSelection(dpy, sel, target, target, win, CurrentTime); + XFlush(dpy); - *out_len = actual_len; - return copy; + XNextEvent(dpy, &ev); + if (ev.type != SelectionNotify || ev.xselection.property == None) + return NULL; + + data = NULL; + if (XGetWindowProperty(dpy, win, target, 0, ~0, False, + AnyPropertyType, &type, &format, + &len, &bytes_left, &data) != Success) + return NULL; + + if (!data || len == 0) { + if (data) XFree(data); + return NULL; } - return NULL; // neither PNG nor JPEG available + int actual_len = len * (format / 8); + unsigned char *copy = malloc(actual_len); + memcpy(copy, data, actual_len); + XFree(data); + + *out_len = actual_len; + return copy; } // Clipboard data holder @@ -333,6 +341,7 @@ int setClipboardImageX11(unsigned char *data, int len, const char *mime_type) { } */ import "C" + import ( "fmt" "os" @@ -372,6 +381,7 @@ func RunX11Listener() { if imgContents != nil { utils.HandleError(SaveImage(imgContents)) + continue } // Check if the clipboard content should be excluded based on source application diff --git a/shell/constants.go b/shell/constants.go index 5b66d08..16659f3 100644 --- a/shell/constants.go +++ b/shell/constants.go @@ -5,6 +5,7 @@ const ( listenShellCmd = "--listen-shell" pgrepCmd = "pgrep 'clipse'" psCmd = "ps -o command" + x11CopyHandler = "xclip" wlCopyHandler = "wl-copy" wlPasteHandler = "wl-paste" wlPasteWatcher = "--watch" diff --git a/shell/x11.go b/shell/x11.go index 25540a8..079b0ae 100644 --- a/shell/x11.go +++ b/shell/x11.go @@ -3,6 +3,7 @@ package shell import ( "os/exec" "strings" + "syscall" "github.com/savedra1/clipse/utils" ) @@ -24,6 +25,22 @@ func X11ActiveWindowTitle() string { return "" } +func X11CopyText(text string) { + cmd := exec.Command(x11CopyHandler, "-selection", "clipboard") + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + Pgid: 0, + } + + cmd.Stdin = strings.NewReader(text) + utils.HandleError(cmd.Start()) +} + +func X11CopyImage(filePath string) { + cmd := exec.Command(x11CopyHandler, "-selection", "clipboard", "-t", "image/png", "-i", filePath) + runDetachedCmd(cmd) +} + // tryXprop tries getting the window title for X11 systems using xprop - property displayer for X // xprop is widely available on X11 desktop environments // Example output: _NET_ACTIVE_WINDOW(WINDOW): window id # 0x1a00005 (then) WM_NAME(STRING) = "Alacritty"