Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions display/x11.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
90 changes: 50 additions & 40 deletions handlers/x11.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -333,6 +341,7 @@ int setClipboardImageX11(unsigned char *data, int len, const char *mime_type) {
}
*/
import "C"

import (
"fmt"
"os"
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions shell/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
17 changes: 17 additions & 0 deletions shell/x11.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package shell
import (
"os/exec"
"strings"
"syscall"

"github.com/savedra1/clipse/utils"
)
Expand All @@ -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"
Expand Down