34#if defined(__linux__) && !defined(__ANDROID__)
39#include <sys/select.h>
44static char* clip_dupn(
const char* s,
size_t n) {
45 char* out = malloc(n + 1);
54static Display* s_dpy = NULL;
56static Window s_win = 0;
57static pthread_t s_thread;
58static int s_running = 0;
59static int s_ready = 0;
60static pthread_mutex_t s_lock = PTHREAD_MUTEX_INITIALIZER;
62static char* s_text[2] = {NULL, NULL};
65static Atom A_CLIPBOARD, A_TARGETS, A_UTF8, A_TEXT, A_TEXTPLAIN, A_TEXTPLAIN_UTF8, A_INCR;
68static Atom clip_sel_atom(
int which) {
73static int clip_sel_index(Atom a) {
82static int clip_is_text_target(Atom t) {
83 return t == A_UTF8 || t == XA_STRING || t == A_TEXT || t == A_TEXTPLAIN || t == A_TEXTPLAIN_UTF8;
87static void clip_answer_request(
const XSelectionRequestEvent* req) {
89 int idx = clip_sel_index(req->selection);
90 resp.type = SelectionNotify;
91 resp.display = req->display;
92 resp.requestor = req->requestor;
93 resp.selection = req->selection;
94 resp.target = req->target;
95 resp.time = req->time;
99 if (req->target == A_TARGETS) {
101 targets[0] = A_TARGETS;
103 targets[2] = XA_STRING;
105 targets[4] = A_TEXTPLAIN;
106 targets[5] = A_TEXTPLAIN_UTF8;
107 XChangeProperty(s_dpy, req->requestor, req->property, XA_ATOM, 32, PropModeReplace,
108 (
const unsigned char*)targets, (
int)(
sizeof(targets) /
sizeof(targets[0])));
109 resp.property = req->property;
110 }
else if (clip_is_text_target(req->target)) {
112 pthread_mutex_lock(&s_lock);
115 XChangeProperty(s_dpy, req->requestor, req->property, req->target, 8, PropModeReplace,
116 (
const unsigned char*)txt, (
int)strlen(txt));
117 resp.property = req->property;
119 pthread_mutex_unlock(&s_lock);
122 XSendEvent(s_dpy, req->requestor, False, 0, (XEvent*)&resp);
127static void* clip_thread_main(
void* arg) {
129 int fd = ConnectionNumber(s_dpy);
130 while (__atomic_load_n(&s_running, __ATOMIC_ACQUIRE)) {
137 select(fd + 1, &fds, NULL, NULL, &tv);
138 while (XPending(s_dpy)) {
140 XNextEvent(s_dpy, &ev);
141 if (ev.type == SelectionRequest) {
142 clip_answer_request(&ev.xselectionrequest);
143 }
else if (ev.type == SelectionClear) {
144 int idx = clip_sel_index(ev.xselectionclear.selection);
146 pthread_mutex_lock(&s_lock);
149 pthread_mutex_unlock(&s_lock);
160 s_dpy = XOpenDisplay(NULL);
162 n_log(
LOG_INFO,
"n_clipboard: no X11 display, clipboard backend disabled");
165 s_win = XCreateSimpleWindow(s_dpy, RootWindow(s_dpy, DefaultScreen(s_dpy)), 0, 0, 1, 1, 0, 0, 0);
166 A_CLIPBOARD = XInternAtom(s_dpy,
"CLIPBOARD", False);
167 A_TARGETS = XInternAtom(s_dpy,
"TARGETS", False);
168 A_UTF8 = XInternAtom(s_dpy,
"UTF8_STRING", False);
169 A_TEXT = XInternAtom(s_dpy,
"TEXT", False);
170 A_TEXTPLAIN = XInternAtom(s_dpy,
"text/plain", False);
171 A_TEXTPLAIN_UTF8 = XInternAtom(s_dpy,
"text/plain;charset=utf-8", False);
172 A_INCR = XInternAtom(s_dpy,
"INCR", False);
174 __atomic_store_n(&s_running, 1, __ATOMIC_RELEASE);
175 if (pthread_create(&s_thread, NULL, clip_thread_main, NULL) != 0) {
176 __atomic_store_n(&s_running, 0, __ATOMIC_RELEASE);
177 XDestroyWindow(s_dpy, s_win);
178 XCloseDisplay(s_dpy);
180 n_log(
LOG_ERR,
"n_clipboard: could not start the serving thread");
199 pthread_mutex_lock(&s_lock);
201 s_text[which] = (text && text[0]) ? clip_dupn(text, strlen(text)) : NULL;
202 pthread_mutex_unlock(&s_lock);
204 XSetSelectionOwner(s_dpy, clip_sel_atom(which), s_win, CurrentTime);
206 XSetSelectionOwner(s_dpy, clip_sel_atom(which), None, CurrentTime);
222 pthread_mutex_lock(&s_lock);
224 result = clip_dupn(s_text[which], strlen(s_text[which]));
225 pthread_mutex_unlock(&s_lock);
228 pthread_mutex_unlock(&s_lock);
231 d = XOpenDisplay(NULL);
234 w = XCreateSimpleWindow(d, RootWindow(d, DefaultScreen(d)), 0, 0, 1, 1, 0, 0, 0);
236 prop = XInternAtom(d,
"NIL_CLIPBOARD_TARGET", False);
237 if (XGetSelectionOwner(d, sel) == None) {
238 XDestroyWindow(d, w);
242 XConvertSelection(d, sel, XInternAtom(d,
"UTF8_STRING", False), prop, w, CurrentTime);
244 fd = ConnectionNumber(d);
245 for (tries = 0; tries < 20 && !result; tries++) {
252 select(fd + 1, &fds, NULL, NULL, &tv);
253 while (XPending(d)) {
256 if (ev.type == SelectionNotify) {
257 if (ev.xselection.property != None) {
260 unsigned long nitems = 0, after = 0;
261 unsigned char* data = NULL;
262 if (XGetWindowProperty(d, w, prop, 0, (~0L), False, AnyPropertyType, &rtype, &rfmt,
263 &nitems, &after, &data) == Success &&
265 result = clip_dupn((
const char*)data, nitems);
269 XDeleteProperty(d, w, prop);
276 XDestroyWindow(d, w);
285 __atomic_store_n(&s_running, 0, __ATOMIC_RELEASE);
286 pthread_join(s_thread, NULL);
288 XDestroyWindow(s_dpy, s_win);
289 XCloseDisplay(s_dpy);
292 for (i = 0; i < 2; i++) {
299#elif defined(__windows__)
301#define WIN32_LEAN_AND_MEAN
313static int s_ready = 0;
314static pthread_mutex_t s_lock = PTHREAD_MUTEX_INITIALIZER;
317static int clip_open(
void) {
319 for (tries = 0; tries < 10; tries++) {
320 if (OpenClipboard(NULL))
329 n_log(
LOG_INFO,
"n_clipboard: Win32 clipboard backend ready");
349 pthread_mutex_lock(&s_lock);
351 pthread_mutex_unlock(&s_lock);
354 if (!EmptyClipboard()) {
356 pthread_mutex_unlock(&s_lock);
360 if (!text || !text[0]) {
362 pthread_mutex_unlock(&s_lock);
365 wlen = MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0);
368 pthread_mutex_unlock(&s_lock);
371 hmem = GlobalAlloc(GMEM_MOVEABLE, (
size_t)wlen *
sizeof(
wchar_t));
373 wchar_t* wbuf = (
wchar_t*)GlobalLock(hmem);
375 MultiByteToWideChar(CP_UTF8, 0, text, -1, wbuf, wlen);
377 if (SetClipboardData(CF_UNICODETEXT, hmem)) {
387 pthread_mutex_unlock(&s_lock);
389 n_log(
LOG_ERR,
"n_clipboard: failed to set the Win32 clipboard");
402 if (!IsClipboardFormatAvailable(CF_UNICODETEXT))
404 pthread_mutex_lock(&s_lock);
406 pthread_mutex_unlock(&s_lock);
409 h = GetClipboardData(CF_UNICODETEXT);
411 const wchar_t* wptr = (
const wchar_t*)GlobalLock(h);
413 int len = WideCharToMultiByte(CP_UTF8, 0, wptr, -1, NULL, 0, NULL, NULL);
415 out = malloc((
size_t)len);
417 WideCharToMultiByte(CP_UTF8, 0, wptr, -1, out, len, NULL, NULL);
423 pthread_mutex_unlock(&s_lock);
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_ERR
error conditions
#define LOG_INFO
informational
int n_clipboard_set(int which, const char *text)
Own which selection and serve text to any app that requests it.
int n_clipboard_available(void)
Whether a real clipboard backend (X11 or Win32) is active on this platform.
int n_clipboard_init(void)
Start the clipboard backend (idempotent; also started lazily on first use).
char * n_clipboard_get(int which)
Fetch the current text of which selection from its owner.
void n_clipboard_destroy(void)
Stop the backend thread and release the display connection.
System clipboard: full X11 selection support on Linux, Win32 clipboard on Windows/MinGW.
#define N_CLIPBOARD_PRIMARY
the X11 PRIMARY selection (mouse select-to-copy / middle-click paste); a no-op on Windows,...
#define N_CLIPBOARD_CLIPBOARD
the Ctrl+C / Ctrl+V clipboard selection