Skip to content

Commit

Permalink
linux: dynamically load libX11 (#20)
Browse files Browse the repository at this point in the history
Removing linking of libX11 and replacing it with dlopen to call the
functions dynamically.
For the future wayland support, the library will not require the libX11
library to be installed on the running system.
For compiling it we need it, because of the huge and complicated type
definitions, but the compiled binary will run also on a system without
libX11.

References: #6
Signed-off-by: Vladim铆r Magyar <vladimir@mgyar.me>
  • Loading branch information
microo8 committed Jan 24, 2022
1 parent 076d92f commit 1d2e38d
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 38 deletions.
125 changes: 91 additions & 34 deletions clipboard_linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,86 @@
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <dlfcn.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>

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

void *libX11;

Display* (*P_XOpenDisplay)(int);
void (*P_XCloseDisplay)(Display*);
Window (*P_XDefaultRootWindow)(Display*);
Window (*P_XCreateSimpleWindow)(Display*, Window, int, int, int, int, int, int, int);
Atom (*P_XInternAtom)(Display*, char*, int);
void (*P_XSetSelectionOwner)(Display*, Atom, Window, unsigned long);
Window (*P_XGetSelectionOwner)(Display*, Atom);
void (*P_XNextEvent)(Display*, XEvent*);
int (*P_XChangeProperty)(Display*, Window, Atom, Atom, int, int, unsigned char*, int);
void (*P_XSendEvent)(Display*, Window, int, long , XEvent*);
int (*P_XGetWindowProperty) (Display*, Window, Atom, long, long, Bool, Atom, Atom*, int*, unsigned long *, unsigned long *, unsigned char **);
void (*P_XFree) (void*);
void (*P_XDeleteProperty) (Display*, Window, Atom);
void (*P_XConvertSelection)(Display*, Atom, Atom, Atom, Window, Time);

int initX11() {
if (libX11) {
return 1;
}
libX11 = dlopen("libX11.so", RTLD_LAZY);
if (!libX11) {
return -1;
}
P_XOpenDisplay = (Display* (*)(int)) dlsym(libX11, "XOpenDisplay");
P_XCloseDisplay = (void (*)(Display*)) dlsym(libX11, "XCloseDisplay");
P_XDefaultRootWindow = (Window (*)(Display*)) dlsym(libX11, "XDefaultRootWindow");
P_XCreateSimpleWindow = (Window (*)(Display*, Window, int, int, int, int, int, int, int)) dlsym(libX11, "XCreateSimpleWindow");
P_XInternAtom = (Atom (*)(Display*, char*, int)) dlsym(libX11, "XInternAtom");
P_XSetSelectionOwner = (void (*)(Display*, Atom, Window, unsigned long)) dlsym(libX11, "XSetSelectionOwner");
P_XGetSelectionOwner = (Window (*)(Display*, Atom)) dlsym(libX11, "XGetSelectionOwner");
P_XNextEvent = (void (*)(Display*, XEvent*)) dlsym(libX11, "XNextEvent");
P_XChangeProperty = (int (*)(Display*, Window, Atom, Atom, int, int, unsigned char*, int)) dlsym(libX11, "XChangeProperty");
P_XSendEvent = (void (*)(Display*, Window, int, long , XEvent*)) dlsym(libX11, "XSendEvent");
P_XGetWindowProperty = (int (*)(Display*, Window, Atom, long, long, Bool, Atom, Atom*, int*, unsigned long *, unsigned long *, unsigned char **)) dlsym(libX11, "XGetWindowProperty");
P_XFree = (void (*)(void*)) dlsym(libX11, "XFree");
P_XDeleteProperty = (void (*)(Display*, Window, Atom)) dlsym(libX11, "XDeleteProperty");
P_XConvertSelection = (void (*)(Display*, Atom, Atom, Atom, Window, Time)) dlsym(libX11, "XConvertSelection");
return 1;
}

int clipboard_test() {
if (!initX11()) {
return -1;
}

Display* d = NULL;
for (int i = 0; i < 42; i++) {
d = XOpenDisplay(0);
d = (*P_XOpenDisplay)(0);
if (d == NULL) {
continue;
}
break;
}

if (d == NULL) {
return -1;
}
XCloseDisplay(d);
(*P_XCloseDisplay)(d);
return 0;
}

// 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) {
if (!initX11()) {
return -1;
}

Display* d = NULL;
for (int i = 0; i < 42; i++) {
d = XOpenDisplay(0);
d = (*P_XOpenDisplay)(0);
if (d == NULL) {
continue;
}
Expand All @@ -50,26 +100,25 @@ int clipboard_write(char *typ, unsigned char *buf, size_t n, uintptr_t handle) {
syncStatus(handle, -1);
return -1;
}

Window w = XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, 1, 1, 0, 0, 0);
Window w = (*P_XCreateSimpleWindow)(d, (*P_XDefaultRootWindow)(d), 0, 0, 1, 1, 0, 0, 0);

// Use False because these may not available for the first time.
Atom sel = XInternAtom(d, "CLIPBOARD", False);
Atom atomString = XInternAtom(d, "UTF8_STRING", False);
Atom atomImage = XInternAtom(d, "image/png", False);
Atom targetsAtom = XInternAtom(d, "TARGETS", False);
Atom sel = (*P_XInternAtom)(d, "CLIPBOARD", 0);
Atom atomString = (*P_XInternAtom)(d, "UTF8_STRING", 0);
Atom atomImage = (*P_XInternAtom)(d, "image/png", 0);
Atom targetsAtom = (*P_XInternAtom)(d, "TARGETS", 0);

// Use True to makesure the requested type is a valid type.
Atom target = XInternAtom(d, typ, True);
Atom target = (*P_XInternAtom)(d, typ, 1);
if (target == None) {
XCloseDisplay(d);
(*P_XCloseDisplay)(d);
syncStatus(handle, -2);
return -2;
}

XSetSelectionOwner(d, sel, w, CurrentTime);
if (XGetSelectionOwner(d, sel) != w) {
XCloseDisplay(d);
(*P_XSetSelectionOwner)(d, sel, w, CurrentTime);
if ((*P_XGetSelectionOwner)(d, sel) != w) {
(*P_XCloseDisplay)(d);
syncStatus(handle, -3);
return -3;
}
Expand All @@ -83,13 +132,13 @@ int clipboard_write(char *typ, unsigned char *buf, size_t n, uintptr_t handle) {
notified = 1;
}

XNextEvent(d, &event);
(*P_XNextEvent)(d, &event);
switch (event.type) {
case SelectionClear:
// For debugging:
// printf("x11write: lost ownership of clipboard selection.\n");
// fflush(stdout);
XCloseDisplay(d);
(*P_XCloseDisplay)(d);
return 0;
case SelectionNotify:
// For debugging:
Expand All @@ -114,24 +163,24 @@ int clipboard_write(char *typ, unsigned char *buf, size_t n, uintptr_t handle) {
ev.property = xsr->property;

if (ev.target == atomString && ev.target == target) {
R = XChangeProperty(ev.display, ev.requestor, ev.property,
R = (*P_XChangeProperty)(ev.display, ev.requestor, ev.property,
atomString, 8, PropModeReplace, buf, n);
} else if (ev.target == atomImage && ev.target == target) {
R = XChangeProperty(ev.display, ev.requestor, ev.property,
R = (*P_XChangeProperty)(ev.display, ev.requestor, ev.property,
atomImage, 8, PropModeReplace, buf, n);
} else if (ev.target == targetsAtom) {
// Reply atoms for supported targets, other clients should
// request the clipboard again and obtain the data if their
// implementation is correct.
Atom targets[] = { atomString, atomImage };
R = XChangeProperty(ev.display, ev.requestor, ev.property,
R = (*P_XChangeProperty)(ev.display, ev.requestor, ev.property,
XA_ATOM, 32, PropModeReplace,
(unsigned char *)&targets, sizeof(targets)/sizeof(Atom));
} else {
ev.property = None;
}

if ((R & 2) == 0) XSendEvent(d, ev.requestor, 0, 0, (XEvent *)&ev);
if ((R & 2) == 0) (*P_XSendEvent)(d, ev.requestor, 0, 0, (XEvent *)&ev);
break;
}
}
Expand All @@ -140,6 +189,10 @@ int clipboard_write(char *typ, unsigned char *buf, size_t n, uintptr_t handle) {
// read_data reads the property of a selection if the target atom matches
// the actual atom.
unsigned long read_data(XSelectionEvent *sev, Atom sel, Atom prop, Atom target, char **buf) {
if (!initX11()) {
return -1;
}

unsigned char *data;
Atom actual;
int format;
Expand All @@ -149,7 +202,7 @@ unsigned long read_data(XSelectionEvent *sev, Atom sel, Atom prop, Atom target,
return 0;
}

int ret = XGetWindowProperty(sev->display, sev->requestor, sev->property,
int ret = (*P_XGetWindowProperty)(sev->display, sev->requestor, sev->property,
0L, (~0L), 0, AnyPropertyType, &actual, &format, &size, &n, &data);
if (ret != Success) {
return 0;
Expand All @@ -159,8 +212,8 @@ unsigned long read_data(XSelectionEvent *sev, Atom sel, Atom prop, Atom target,
*buf = (char *)malloc(size * sizeof(char));
memcpy(*buf, data, size*sizeof(char));
}
XFree(data);
XDeleteProperty(sev->display, sev->requestor, sev->property);
(*P_XFree)(data);
(*P_XDeleteProperty)(sev->display, sev->requestor, sev->property);
return size * sizeof(char);
}

Expand All @@ -169,9 +222,13 @@ unsigned long read_data(XSelectionEvent *sev, Atom sel, Atom prop, Atom target,
//
// The caller of this function should responsible for the free of the buf.
unsigned long clipboard_read(char* typ, char **buf) {
if (!initX11()) {
return -1;
}

Display* d = NULL;
for (int i = 0; i < 42; i++) {
d = XOpenDisplay(0);
d = (*P_XOpenDisplay)(0);
if (d == NULL) {
continue;
}
Expand All @@ -181,27 +238,27 @@ unsigned long clipboard_read(char* typ, char **buf) {
return -1;
}

Window w = XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, 1, 1, 0, 0, 0);
Window w = (*P_XCreateSimpleWindow)(d, (*P_XDefaultRootWindow)(d), 0, 0, 1, 1, 0, 0, 0);

// Use False because these may not available for the first time.
Atom sel = XInternAtom(d, "CLIPBOARD", False);
Atom prop = XInternAtom(d, "GOLANG_DESIGN_DATA", False);
Atom sel = (*P_XInternAtom)(d, "CLIPBOARD", False);
Atom prop = (*P_XInternAtom)(d, "GOLANG_DESIGN_DATA", False);

// Use True to makesure the requested type is a valid type.
Atom target = XInternAtom(d, typ, True);
Atom target = (*P_XInternAtom)(d, typ, True);
if (target == None) {
XCloseDisplay(d);
(*P_XCloseDisplay)(d);
return -2;
}

XConvertSelection(d, sel, target, prop, w, CurrentTime);
(*P_XConvertSelection)(d, sel, target, prop, w, CurrentTime);
XEvent event;
for (;;) {
XNextEvent(d, &event);
(*P_XNextEvent)(d, &event);
if (event.type != SelectionNotify) continue;
break;
}
unsigned long n = read_data((XSelectionEvent *)&event.xselection, sel, prop, target, buf);
XCloseDisplay(d);
(*P_XCloseDisplay)(d);
return n;
}
}
5 changes: 1 addition & 4 deletions clipboard_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,11 @@
package clipboard

/*
#cgo LDFLAGS: -lX11
#cgo LDFLAGS: -ldl
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <stdatomic.h>
int clipboard_test();
int clipboard_write(
Expand Down

0 comments on commit 1d2e38d

Please sign in to comment.