Introduction
Introduction Statistics Contact Development Disclaimer Help
tReplace broken clipboard handling with ctrlsel - ledit - Text editor (WIP)
git clone git://lumidify.org/ledit.git (fast, but not encrypted)
git clone https://lumidify.org/git/ledit.git (encrypted, but very slow)
Log
Files
Refs
README
LICENSE
---
commit 2685622dcc0ab5293da9b82e9d651d3b9696e1ec
parent 60f311ec4a85a62f84f76d5a1a05432f08417287
Author: lumidify <[email protected]>
Date: Sun, 20 Aug 2023 22:15:59 +0200
Replace broken clipboard handling with ctrlsel
Diffstat:
M LICENSE | 3 ++-
A LICENSE.ctrlsel | 23 +++++++++++++++++++++++
M Makefile | 13 +++++++++----
M clipboard.c | 265 ++++++++---------------------…
A ctrlsel.c | 1621 +++++++++++++++++++++++++++++…
A ctrlsel.h | 107 +++++++++++++++++++++++++++++…
6 files changed, 1832 insertions(+), 200 deletions(-)
---
diff --git a/LICENSE b/LICENSE
t@@ -2,10 +2,11 @@ Note 1: Some stuff is stolen from st (https://st.suckless.or…
Note 2: Some stuff is stolen from OpenBSD (https://openbsd.org)
Note 3: pango-compat.{c,h} contains a bit of code copied from
Pango in order to be compatible with older versions.
+Note 4: See LICENSE.ctrlsel for ctrlsel.{c,h}
ISC License
-Copyright (c) 2021, 2022 lumidify <[email protected]>
+Copyright (c) 2021-2023 lumidify <[email protected]>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
diff --git a/LICENSE.ctrlsel b/LICENSE.ctrlsel
t@@ -0,0 +1,23 @@
+License for ctrlsel.{c,h}:
+
+MIT/X Consortium License
+
+© 2022-2023 Lucas de Sena <lucas at seninha dot org>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
t@@ -32,7 +32,8 @@ OBJ = \
draw_util.o \
window.o \
clipboard.o \
- pango-compat.o
+ pango-compat.o \
+ ctrlsel.o
SRC = ${OBJ:.o=.c}
t@@ -57,7 +58,8 @@ HDR = \
macros.h \
pango-compat.h \
clipboard.h \
- uglycrap.h
+ uglycrap.h \
+ ctrlsel.h
CONFIGHDR = \
config.h \
t@@ -69,8 +71,11 @@ EXTRA_LDFLAGS_DEBUG0 = ${LDFLAGS}
EXTRA_CFLAGS_DEBUG1 = -DLEDIT_DEBUG -g
EXTRA_FLAGS_SANITIZE1 = -fsanitize=address
-CFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_CFLAGS_DEBUG${DEBUG}…
-LDFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_LDFLAGS_DEBUG${DEBU…
+# Xcursor isn't actually needed right now since I'm not using the drag 'n drop…
+# of ctrlsel yet, but since it's moderately likely that I will use that in the…
+# decided to just leave it in.
+CFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_CFLAGS_DEBUG${DEBUG}…
+LDFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_LDFLAGS_DEBUG${DEBU…
all: ${BIN}
diff --git a/clipboard.c b/clipboard.c
t@@ -12,27 +12,25 @@
#include "clipboard.h"
#include "macros.h"
#include "config.h"
+#include "ctrlsel.h"
-/* clipboard handling largely stolen from st (https://st.suckless.org),
- with some *inspiration* taken from SDL (https://libsdl.org), mainly
- the idea to create a separate window just for clipboard handling */
+/* Some *inspiration* taken from SDL (https://libsdl.org), mainly
+ the idea to create a separate window just for clipboard handling. */
static Window get_clipboard_window(ledit_clipboard *clip);
static Bool check_window(Display *dpy, XEvent *event, XPointer arg);
static txtbuf *get_text(ledit_clipboard *clip, int primary);
-static int clipboard_propnotify(ledit_clipboard *clip, XEvent *e, txtbuf *buf);
-static int clipboard_selnotify(ledit_clipboard *clip, XEvent *e, txtbuf *buf);
-static void clipboard_selrequest(ledit_clipboard *clip, XEvent *e);
-
-#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
struct ledit_clipboard {
txtbuf *primary;
txtbuf *clipboard;
+ txtbuf *rbuf;
ledit_common *common;
Window window;
+ struct CtrlSelTarget starget;
+ struct CtrlSelTarget rtarget;
+ CtrlSelContext *scontext;
Atom xtarget;
- XSetWindowAttributes wattrs;
};
ledit_clipboard *
t@@ -40,15 +38,16 @@ clipboard_create(ledit_common *common) {
ledit_clipboard *clip = ledit_malloc(sizeof(ledit_clipboard));
clip->primary = txtbuf_new();
clip->clipboard = txtbuf_new();
+ clip->rbuf = txtbuf_new();
clip->common = common;
clip->window = None;
clip->xtarget = None;
#ifdef X_HAVE_UTF8_STRING
clip->xtarget = XInternAtom(common->dpy, "UTF8_STRING", False);
+ #else
+ clip->xtarget = XA_STRING;
#endif
- if (clip->xtarget == None)
- clip->xtarget = XA_STRING;
- clip->wattrs.event_mask = 0;
+ clip->scontext = NULL;
return clip;
}
t@@ -56,6 +55,9 @@ void
clipboard_destroy(ledit_clipboard *clip) {
txtbuf_destroy(clip->primary);
txtbuf_destroy(clip->clipboard);
+ txtbuf_destroy(clip->rbuf);
+ if (clip->scontext)
+ ctrlsel_disown(clip->scontext);
if (clip->window != None)
XDestroyWindow(clip->common->dpy, clip->window);
free(clip);
t@@ -66,7 +68,7 @@ get_clipboard_window(ledit_clipboard *clip) {
if (clip->window == None) {
clip->window = XCreateWindow(
clip->common->dpy, DefaultRootWindow(clip->common->dpy),
- -10, -10, 1, 1, 0, CopyFromParent, InputOnly, CopyFromPare…
+ -10, -10, 1, 1, 0, CopyFromParent, InputOnly, CopyFromPare…
);
XFlush(clip->common->dpy);
}
t@@ -75,9 +77,8 @@ get_clipboard_window(ledit_clipboard *clip) {
void
clipboard_set_primary_text(ledit_clipboard *clip, char *text) {
- Window window = get_clipboard_window(clip);
txtbuf_set_text(clip->primary, text);
- XSetSelectionOwner(clip->common->dpy, XA_PRIMARY, window, CurrentTime);
+ clipboard_set_primary_selection_owner(clip);
}
txtbuf *
t@@ -88,16 +89,21 @@ clipboard_get_primary_buffer(ledit_clipboard *clip) {
void
clipboard_set_primary_selection_owner(ledit_clipboard *clip) {
Window window = get_clipboard_window(clip);
- XSetSelectionOwner(clip->common->dpy, XA_PRIMARY, window, CurrentTime);
+ if (clip->scontext)
+ ctrlsel_disown(clip->scontext);
+ clip->scontext = NULL;
+ /* FIXME: is it fine to cast to unsigned char everywhere? */
+ ctrlsel_filltarget(clip->xtarget, clip->xtarget, 8, (unsigned char *)c…
+ /* FIXME: use proper time */
+ clip->scontext = ctrlsel_setowner(clip->common->dpy, window, XA_PRIMAR…
+ if (!clip->scontext)
+ fprintf(stderr, "WARNING: Could not own primary selection.\n");
}
void
clipboard_set_clipboard_text(ledit_clipboard *clip, char *text) {
- Atom clip_atom;
- Window window = get_clipboard_window(clip);
- clip_atom = XInternAtom(clip->common->dpy, "CLIPBOARD", False);
txtbuf_set_text(clip->clipboard, text);
- XSetSelectionOwner(clip->common->dpy, clip_atom, window, CurrentTime);
+ clipboard_set_clipboard_selection_owner(clip);
}
txtbuf *
t@@ -110,25 +116,30 @@ clipboard_set_clipboard_selection_owner(ledit_clipboard …
Atom clip_atom;
Window window = get_clipboard_window(clip);
clip_atom = XInternAtom(clip->common->dpy, "CLIPBOARD", False);
- XSetSelectionOwner(clip->common->dpy, clip_atom, window, CurrentTime);
+ if (clip->scontext)
+ ctrlsel_disown(clip->scontext);
+ clip->scontext = NULL;
+ /* FIXME: see clipboard_set_primary_selection_owner */
+ ctrlsel_filltarget(clip->xtarget, clip->xtarget, 8, (unsigned char *)c…
+ /* FIXME: use proper time */
+ clip->scontext = ctrlsel_setowner(clip->common->dpy, window, clip_atom…
+ if (!clip->scontext)
+ fprintf(stderr, "WARNING: Could not own clipboard selection.\n…
}
void
clipboard_primary_to_clipboard(ledit_clipboard *clip) {
- Atom clip_atom;
if (clip->primary->len > 0) {
- Window window = get_clipboard_window(clip);
txtbuf_copy(clip->clipboard, clip->primary);
- clip_atom = XInternAtom(clip->common->dpy, "CLIPBOARD", False);
- XSetSelectionOwner(clip->common->dpy, clip_atom, window, Curre…
+ clipboard_set_clipboard_selection_owner(clip);
}
}
int
clipboard_filter_event(ledit_clipboard *clip, XEvent *e) {
if (clip->window != None && e->xany.window == clip->window) {
- if (e->type == SelectionRequest)
- clipboard_selrequest(clip, e);
+ if (clip->scontext)
+ ctrlsel_send(clip->scontext, e);
/* other events are discarded since there
was no request to get the clipboard text */
return 1;
t@@ -145,9 +156,18 @@ check_window(Display *dpy, XEvent *event, XPointer arg) {
/* WARNING: The returned txtbuf needs to be copied before further processing! …
static txtbuf *
get_text(ledit_clipboard *clip, int primary) {
- txtbuf *buf = primary ? clip->primary : clip->clipboard;
- txtbuf_clear(buf);
+ CtrlSelContext *context;
Window window = get_clipboard_window(clip);
+ ctrlsel_filltarget(clip->xtarget, clip->xtarget, 0, NULL, 0, &clip->rt…
+ Atom clip_atom = primary ? XA_PRIMARY : XInternAtom(clip->common->dpy,…
+ /* FIXME: use proper time here */
+ context = ctrlsel_request(clip->common->dpy, window, clip_atom, Curren…
+ /* FIXME: show error in window? */
+ if (!context) {
+ fprintf(stderr, "WARNING: Unable to request selection.\n");
+ return NULL;
+ }
+
struct timespec now, elapsed, last, start, sleep_time;
sleep_time.tv_sec = 0;
clock_gettime(CLOCK_MONOTONIC, &start);
t@@ -156,24 +176,22 @@ get_text(ledit_clipboard *clip, int primary) {
while (1) {
/* FIXME: I have no idea how inefficient this is */
if (XCheckIfEvent(clip->common->dpy, &event, &check_window, (X…
- switch (event.type) {
- case SelectionNotify:
- if (!clipboard_selnotify(clip, &event,…
- return buf;
- break;
- case PropertyNotify:
- if (!clipboard_propnotify(clip, &event…
- return buf;
- break;
- case SelectionRequest:
- clipboard_selrequest(clip, &event);
- break;
- default:
- break;
+ switch (ctrlsel_receive(context, &event)) {
+ case CTRLSEL_RECEIVED:
+ goto done;
+ case CTRLSEL_ERROR:
+ fprintf(stderr, "WARNING: Could not get select…
+ ctrlsel_cancel(context);
+ return NULL;
+ default:
+ continue;
}
}
clock_gettime(CLOCK_MONOTONIC, &now);
ledit_timespecsub(&now, &start, &elapsed);
+ /* Timeout if it takes too long. When that happens, become the…
+ avoid further timeouts in the future (I think I copied this…
+ /* FIXME: configure timeout */
if (elapsed.tv_sec > 0) {
if (primary)
clipboard_set_primary_text(clip, "");
t@@ -189,6 +207,15 @@ get_text(ledit_clipboard *clip, int primary) {
last = now;
}
return NULL;
+done:
+ /* FIXME: this is a bit ugly because it fiddles around with txtbuf int…
+ free(clip->rbuf->text);
+ clip->rbuf->cap = clip->rbuf->len = clip->rtarget.bufsize;
+ /* FIXME: again weird conversion between char and unsigned char */
+ clip->rbuf->text = (char *)clip->rtarget.buffer;
+ clip->rtarget.buffer = NULL; /* important so ctrlsel_cancel doesn't fr…
+ ctrlsel_cancel(context);
+ return clip->rbuf;
}
txtbuf *
t@@ -202,7 +229,6 @@ clipboard_get_clipboard_text(ledit_clipboard *clip) {
} else if (owner == window) {
return clip->clipboard;
} else {
- XConvertSelection(clip->common->dpy, clip_atom, clip->xtarget,…
return get_text(clip, 0);
}
}
t@@ -216,157 +242,6 @@ clipboard_get_primary_text(ledit_clipboard *clip) {
} else if (owner == window) {
return clip->primary;
} else {
- XConvertSelection(clip->common->dpy, XA_PRIMARY, clip->xtarget…
return get_text(clip, 1);
}
}
-
-/* 0 means the transfer is done, 1 means there might be more */
-static int
-clipboard_propnotify(ledit_clipboard *clip, XEvent *e, txtbuf *buf) {
- XPropertyEvent *xpev;
- Atom clipboard = XInternAtom(clip->common->dpy, "CLIPBOARD", False);
- xpev = &e->xproperty;
- if (xpev->state == PropertyNewValue && (xpev->atom == XA_PRIMARY || xp…
- return clipboard_selnotify(clip, e, buf);
- }
- return 1;
-}
-
-/* FIXME: test this properly because I don't really understand all of it */
-/* 0 means the transfer is done, 1 means there might be more */
-static int
-clipboard_selnotify(ledit_clipboard *clip, XEvent *e, txtbuf *buf) {
- unsigned long nitems, ofs, rem;
- int format;
- unsigned char *data;
- Atom type, incratom, property = None;
-
- incratom = XInternAtom(clip->common->dpy, "INCR", 0);
-
- ofs = 0;
- if (e->type == SelectionNotify) {
- property = e->xselection.property;
- } else if (e->type == PropertyNotify) {
- property = e->xproperty.atom;
- }
-
- if (property == None)
- return 1;
-
- Window window = get_clipboard_window(clip);
- do {
- /* FIXME: proper error logging */
- /* FIXME: show error message in window */
- if (XGetWindowProperty(
- clip->common->dpy, window, property, ofs, BUFSIZ/4, False,
- AnyPropertyType, &type, &format, &nitems, &rem, &data)) {
- fprintf(stderr, "Clipboard allocation failed\n");
- return 0;
- }
-
- if (e->type == PropertyNotify && nitems == 0 && rem == 0) {
- /*
- * If there is some PropertyNotify with no data, then
- * this is the signal of the selection owner that all
- * data has been transferred. We won't need to receive
- * PropertyNotify events anymore.
- */
- MODBIT(clip->wattrs.event_mask, 0, PropertyChangeMask);
- XChangeWindowAttributes(clip->common->dpy, window, CWE…
- return 0;
- }
-
- if (type == incratom) {
- /*
- * Activate the PropertyNotify events so we receive
- * when the selection owner sends us the next
- * chunk of data.
- */
- MODBIT(clip->wattrs.event_mask, 1, PropertyChangeMask);
- XChangeWindowAttributes(clip->common->dpy, window, CWE…
-
- /*
- * Deleting the property is the transfer start signal.
- */
- XDeleteProperty(clip->common->dpy, window, (int)proper…
- continue;
- }
-
- /* FIXME: XGetWindowProperty takes data as unsigned char, so i…
- txtbuf_appendn(buf, (char *)data, (size_t)(nitems * format / 8…
- XFree(data);
- if (!(clip->wattrs.event_mask & PropertyChangeMask))
- return 0;
- /* number of 32-bit chunks returned */
- ofs += nitems * format / 32;
- } while (rem > 0);
-
- /*
- * Deleting the property again tells the selection owner to send the
- * next data chunk in the property.
- */
- XDeleteProperty(clip->common->dpy, window, (int)property);
- return 1;
-}
-
-static void
-clipboard_selrequest(ledit_clipboard *clip, XEvent *e)
-{
- XSelectionRequestEvent *xsre;
- XSelectionEvent xev;
- Atom xa_targets, string, clip_atom;
- char *seltext;
-
- xsre = (XSelectionRequestEvent *) e;
- xev.type = SelectionNotify;
- xev.requestor = xsre->requestor;
- xev.selection = xsre->selection;
- xev.target = xsre->target;
- xev.time = xsre->time;
- if (xsre->property == None)
- xsre->property = xsre->target;
-
- /* reject */
- xev.property = None;
-
- xa_targets = XInternAtom(clip->common->dpy, "TARGETS", 0);
- if (xsre->target == xa_targets) {
- /* respond with the supported type */
- string = clip->xtarget;
- XChangeProperty(
- xsre->display, xsre->requestor, xsre->property,
- XA_ATOM, 32, PropModeReplace, (unsigned char *) &string, 1
- );
- xev.property = xsre->property;
- } else if (xsre->target == clip->xtarget || xsre->target == XA_STRING)…
- /*
- * xith XA_STRING non ascii characters may be incorrect in the
- * requestor. It is not our problem, use utf8.
- */
- clip_atom = XInternAtom(clip->common->dpy, "CLIPBOARD", 0);
- if (xsre->selection == XA_PRIMARY) {
- seltext = clip->primary->text;
- } else if (xsre->selection == clip_atom) {
- seltext = clip->clipboard->text;
- } else {
- fprintf(
- stderr,
- "Unhandled clipboard selection 0x%lx\n", xsre->sel…
- );
- return;
- }
- /* FIXME: Should this handle sending data in multiple chunks? …
- if (seltext != NULL) {
- XChangeProperty(
- xsre->display, xsre->requestor, xsre->property, xs…
- 8, PropModeReplace, (unsigned char *)seltext, strl…
- );
- xev.property = xsre->property;
- }
- }
-
- /* all done, send a notification to the listener */
- if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *)&xev))
- fprintf(stderr, "Error sending SelectionNotify event\n");
-}
diff --git a/ctrlsel.c b/ctrlsel.c
t@@ -0,0 +1,1621 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/keysym.h>
+#include <X11/cursorfont.h>
+#include <X11/Xcursor/Xcursor.h>
+
+#include "ctrlsel.h"
+
+#define _TIMESTAMP_PROP "_TIMESTAMP_PROP"
+#define TIMESTAMP "TIMESTAMP"
+#define ATOM_PAIR "ATOM_PAIR"
+#define MULTIPLE "MULTIPLE"
+#define MANAGER "MANAGER"
+#define TARGETS "TARGETS"
+#define INCR "INCR"
+#define SELDEFSIZE 0x4000
+#define FLAG(f, b) (((f) & (b)) == (b))
+#define MOTION_TIME 32
+#define DND_DISTANCE 8 /* distance from pointer to dnd miniwi…
+#define XDND_VERSION 5 /* XDND protocol version */
+#define NCLIENTMSG_DATA 5 /* number of members on a the .data.l[…
+
+enum {
+ CONTENT_INCR,
+ CONTENT_ZERO,
+ CONTENT_ERROR,
+ CONTENT_SUCCESS,
+};
+
+enum {
+ PAIR_TARGET,
+ PAIR_PROPERTY,
+ PAIR_LAST
+};
+
+enum {
+ /* xdnd window properties */
+ XDND_AWARE,
+
+ /* xdnd selections */
+ XDND_SELECTION,
+
+ /* xdnd client messages */
+ XDND_ENTER,
+ XDND_POSITION,
+ XDND_STATUS,
+ XDND_LEAVE,
+ XDND_DROP,
+ XDND_FINISHED,
+
+ /* xdnd actions */
+ XDND_ACTION_COPY,
+ XDND_ACTION_MOVE,
+ XDND_ACTION_LINK,
+ XDND_ACTION_ASK,
+ XDND_ACTION_PRIVATE,
+
+ XDND_ATOM_LAST,
+};
+
+enum {
+ CURSOR_TARGET,
+ CURSOR_PIRATE,
+ CURSOR_DRAG,
+ CURSOR_COPY,
+ CURSOR_MOVE,
+ CURSOR_LINK,
+ CURSOR_NODROP,
+ CURSOR_LAST,
+};
+
+struct Transfer {
+ /*
+ * When a client request the clipboard but its content is too
+ * large, we perform incremental transfer. We keep track of
+ * each incremental transfer in a list of transfers.
+ */
+ struct Transfer *prev, *next;
+ struct CtrlSelTarget *target;
+ Window requestor;
+ Atom property;
+ unsigned long size; /* how much have we transferred */
+};
+
+struct PredArg {
+ CtrlSelContext *context;
+ Window window;
+ Atom message_type;
+};
+
+struct CtrlSelContext {
+ Display *display;
+ Window window;
+ Atom selection;
+ Time time;
+ unsigned long ntargets;
+ struct CtrlSelTarget *targets;
+
+ /*
+ * Items below are used internally to keep track of any
+ * incremental transference in progress.
+ */
+ unsigned long selmaxsize;
+ unsigned long ndone;
+ void *transfers;
+
+ /*
+ * Items below are used internally for drag-and-dropping.
+ */
+ Window dndwindow;
+ unsigned int dndactions, dndresult;
+};
+
+static char *atomnames[XDND_ATOM_LAST] = {
+ [XDND_AWARE] = "XdndAware",
+ [XDND_SELECTION] = "XdndSelection",
+ [XDND_ENTER] = "XdndEnter",
+ [XDND_POSITION] = "XdndPosition",
+ [XDND_STATUS] = "XdndStatus",
+ [XDND_LEAVE] = "XdndLeave",
+ [XDND_DROP] = "XdndDrop",
+ [XDND_FINISHED] = "XdndFinished",
+ [XDND_ACTION_COPY] = "XdndActionCopy",
+ [XDND_ACTION_MOVE] = "XdndActionMove",
+ [XDND_ACTION_LINK] = "XdndActionLink",
+ [XDND_ACTION_ASK] = "XdndActionAsk",
+ [XDND_ACTION_PRIVATE] = "XdndActionPrivate",
+};
+
+static int
+between(int x, int y, int x0, int y0, int w0, int h0)
+{
+ return x >= x0 && x < x0 + w0 && y >= y0 && y < y0 + h0;
+}
+
+static void
+clientmsg(Display *dpy, Window win, Atom atom, long d[5])
+{
+ XEvent ev;
+
+ ev.xclient.type = ClientMessage;
+ ev.xclient.display = dpy;
+ ev.xclient.serial = 0;
+ ev.xclient.send_event = True;
+ ev.xclient.message_type = atom;
+ ev.xclient.window = win;
+ ev.xclient.format = 32;
+ ev.xclient.data.l[0] = d[0];
+ ev.xclient.data.l[1] = d[1];
+ ev.xclient.data.l[2] = d[2];
+ ev.xclient.data.l[3] = d[3];
+ ev.xclient.data.l[4] = d[4];
+ (void)XSendEvent(dpy, win, False, 0x0, &ev);
+}
+
+static unsigned long
+getselmaxsize(Display *display)
+{
+ unsigned long n;
+
+ if ((n = XExtendedMaxRequestSize(display)) > 0)
+ return n;
+ if ((n = XMaxRequestSize(display)) > 0)
+ return n;
+ return SELDEFSIZE;
+}
+
+static int
+getservertime(Display *display, Time *time)
+{
+ XEvent xev;
+ Window window;
+ Atom timeprop;
+
+ /*
+ * According to ICCCM, a client wishing to acquire ownership of
+ * a selection should set the specfied time to some time between
+ * the current last-change time of the selection concerned and
+ * the current server time.
+ *
+ * Those clients should not set the time value to `CurrentTime`,
+ * because if they do so, they have no way of finding when they
+ * gained ownership of the selection.
+ *
+ * In the case that an event triggers the acquisition of the
+ * selection, this time value can be obtained from the event
+ * itself.
+ *
+ * In the case that the client must unconditionally acquire the
+ * ownership of a selection (which is our case), a zero-length
+ * append to a property is a way to obtain a timestamp for this
+ * purpose. The timestamp is in the corresponding
+ * `PropertyNotify` event.
+ */
+
+ if (time != CurrentTime)
+ return 1;
+ timeprop = XInternAtom(display, _TIMESTAMP_PROP, False);
+ if (timeprop == None)
+ goto error;
+ window = XCreateWindow(
+ display,
+ DefaultRootWindow(display),
+ 0, 0, 1, 1, 0,
+ CopyFromParent, CopyFromParent, CopyFromParent,
+ CWEventMask,
+ &(XSetWindowAttributes){
+ .event_mask = PropertyChangeMask,
+ }
+ );
+ if (window == None)
+ goto error;
+ XChangeProperty(
+ display, window,
+ timeprop, timeprop,
+ 8L, PropModeAppend, NULL, 0
+ );
+ while (!XWindowEvent(display, window, PropertyChangeMask, &xev)) {
+ if (xev.type == PropertyNotify &&
+ xev.xproperty.window == window &&
+ xev.xproperty.atom == timeprop) {
+ *time = xev.xproperty.time;
+ break;
+ }
+ }
+ (void)XDestroyWindow(display, window);
+ return 1;
+error:
+ return 0;
+}
+
+static int
+nbytes(int format)
+{
+ switch (format) {
+ default: return sizeof(char);
+ case 16: return sizeof(short);
+ case 32: return sizeof(long);
+ }
+}
+
+static int
+getcontent(struct CtrlSelTarget *target, Display *display, Window window, Atom…
+{
+ unsigned char *p, *q;
+ unsigned long len, addsize, size;
+ unsigned long dl; /* dummy variable */
+ int status;
+ Atom incr;
+
+ incr = XInternAtom(display, INCR, False),
+ status = XGetWindowProperty(
+ display,
+ window,
+ property,
+ 0L, 0x1FFFFFFF,
+ True,
+ AnyPropertyType,
+ &target->type,
+ &target->format,
+ &len, &dl, &p
+ );
+ if (target->format != 32 && target->format != 16)
+ target->format = 8;
+ if (target->type == incr) {
+ XFree(p);
+ return CONTENT_INCR;
+ }
+ if (len == 0) {
+ XFree(p);
+ return CONTENT_ZERO;
+ }
+ if (status != Success) {
+ XFree(p);
+ return CONTENT_ERROR;
+ }
+ if (p == NULL) {
+ XFree(p);
+ return CONTENT_ERROR;
+ }
+ addsize = len * nbytes(target->format);
+ size = addsize;
+ if (target->buffer != NULL) {
+ /* append buffer */
+ size += target->bufsize;
+ if ((q = realloc(target->buffer, size + 1)) == NULL) {
+ XFree(p);
+ return CONTENT_ERROR;
+ }
+ memcpy(q + target->bufsize, p, addsize);
+ target->buffer = q;
+ target->bufsize = size;
+ target->nitems += len;
+ } else {
+ /* new buffer */
+ if ((q = malloc(size + 1)) == NULL) {
+ XFree(p);
+ return CONTENT_ERROR;
+ }
+ memcpy(q, p, addsize);
+ target->buffer = q;
+ target->bufsize = size;
+ target->nitems = len;
+ }
+ target->buffer[size] = '\0';
+ XFree(p);
+ return CONTENT_SUCCESS;
+}
+
+static void
+deltransfer(CtrlSelContext *context, struct Transfer *transfer)
+{
+ if (transfer->prev != NULL) {
+ transfer->prev->next = transfer->next;
+ } else {
+ context->transfers = transfer->next;
+ }
+ if (transfer->next != NULL) {
+ transfer->next->prev = transfer->prev;
+ }
+}
+
+static void
+freetransferences(CtrlSelContext *context)
+{
+ struct Transfer *transfer;
+
+ while (context->transfers != NULL) {
+ transfer = (struct Transfer *)context->transfers;
+ context->transfers = ((struct Transfer *)context->transfers)->…
+ XDeleteProperty(
+ context->display,
+ transfer->requestor,
+ transfer->property
+ );
+ free(transfer);
+ }
+ context->transfers = NULL;
+}
+
+static void
+freebuffers(CtrlSelContext *context)
+{
+ unsigned long i;
+
+ for (i = 0; i < context->ntargets; i++) {
+ free(context->targets[i].buffer);
+ context->targets[i].buffer = NULL;
+ context->targets[i].nitems = 0;
+ context->targets[i].bufsize = 0;
+ }
+}
+
+static unsigned long
+getatomsprop(Display *display, Window window, Atom property, Atom type, Atom *…
+{
+ unsigned char *p;
+ unsigned long len;
+ unsigned long dl; /* dummy variable */
+ int format;
+ Atom gottype;
+ unsigned long size;
+ int success;
+
+ success = XGetWindowProperty(
+ display,
+ window,
+ property,
+ 0L, 0x1FFFFFFF,
+ False,
+ type, &gottype,
+ &format, &len,
+ &dl, &p
+ );
+ if (success != Success || len == 0 || p == NULL || format != 32)
+ goto error;
+ if (type != AnyPropertyType && type != gottype)
+ goto error;
+ size = len * sizeof(**atoms);
+ if ((*atoms = malloc(size)) == NULL)
+ goto error;
+ memcpy(*atoms, p, size);
+ XFree(p);
+ return len;
+error:
+ XFree(p);
+ *atoms = NULL;
+ return 0;
+}
+
+static int
+newtransfer(CtrlSelContext *context, struct CtrlSelTarget *target, Window requ…
+{
+ struct Transfer *transfer;
+
+ transfer = malloc(sizeof(*transfer));
+ if (transfer == NULL)
+ return 0;
+ *transfer = (struct Transfer){
+ .prev = NULL,
+ .next = (struct Transfer *)context->transfers,
+ .requestor = requestor,
+ .property = property,
+ .target = target,
+ .size = 0,
+ };
+ if (context->transfers != NULL)
+ ((struct Transfer *)context->transfers)->prev = transfer;
+ context->transfers = transfer;
+ return 1;
+}
+
+static Bool
+convert(CtrlSelContext *context, Window requestor, Atom target, Atom property)
+{
+ Atom multiple, timestamp, targets, incr;
+ Atom *supported;
+ unsigned long i;
+ int nsupported;
+
+ incr = XInternAtom(context->display, INCR, False);
+ targets = XInternAtom(context->display, TARGETS, False);
+ multiple = XInternAtom(context->display, MULTIPLE, False);
+ timestamp = XInternAtom(context->display, TIMESTAMP, False);
+ if (target == multiple) {
+ /* A MULTIPLE should be handled when processing a
+ * SelectionRequest event. We do not support nested
+ * MULTIPLE targets.
+ */
+ return False;
+ }
+ if (target == timestamp) {
+ /*
+ * According to ICCCM, to avoid some race conditions, it
+ * is important that requestors be able to discover the
+ * timestamp the owner used to acquire ownership.
+ * Requestors do that by requesting selection owners to
+ * convert the `TIMESTAMP` target. Selection owners
+ * must return the timestamp as an `XA_INTEGER`.
+ */
+ XChangeProperty(
+ context->display,
+ requestor,
+ property,
+ XA_INTEGER, 32,
+ PropModeReplace,
+ (unsigned char *)&context->time,
+ 1
+ );
+ return True;
+ }
+ if (target == targets) {
+ /*
+ * According to ICCCM, when requested for the `TARGETS`
+ * target, the selection owner should return a list of
+ * atoms representing the targets for which an attempt
+ * to convert the selection will (hopefully) succeed.
+ */
+ nsupported = context->ntargets + 2; /* +2 for MULTIPLE + T…
+ if ((supported = calloc(nsupported, sizeof(*supported))) == NU…
+ return False;
+ for (i = 0; i < context->ntargets; i++) {
+ supported[i] = context->targets[i].target;
+ }
+ supported[i++] = multiple;
+ supported[i++] = timestamp;
+ XChangeProperty(
+ context->display,
+ requestor,
+ property,
+ XA_ATOM, 32,
+ PropModeReplace,
+ (unsigned char *)supported,
+ nsupported
+ );
+ free(supported);
+ return True;
+ }
+ for (i = 0; i < context->ntargets; i++) {
+ if (target == context->targets[i].target)
+ goto found;
+ }
+ return False;
+found:
+ if (context->targets[i].bufsize > context->selmaxsize) {
+ XSelectInput(
+ context->display,
+ requestor,
+ StructureNotifyMask | PropertyChangeMask
+ );
+ XChangeProperty(
+ context->display,
+ requestor,
+ property,
+ incr,
+ 32L,
+ PropModeReplace,
+ (unsigned char *)context->targets[i].buffer,
+ 1
+ );
+ newtransfer(context, &context->targets[i], requestor, property…
+ } else {
+ XChangeProperty(
+ context->display,
+ requestor,
+ property,
+ target,
+ context->targets[i].format,
+ PropModeReplace,
+ context->targets[i].buffer,
+ context->targets[i].nitems
+ );
+ }
+ return True;
+}
+
+static int
+request(CtrlSelContext *context)
+{
+ Atom multiple, atom_pair;
+ Atom *pairs;
+ unsigned long i, size;
+
+ for (i = 0; i < context->ntargets; i++) {
+ context->targets[i].nitems = 0;
+ context->targets[i].bufsize = 0;
+ context->targets[i].buffer = NULL;
+ }
+ if (context->ntargets == 1) {
+ (void)XConvertSelection(
+ context->display,
+ context->selection,
+ context->targets[0].target,
+ context->targets[0].target,
+ context->window,
+ context->time
+ );
+ } else if (context->ntargets > 1) {
+ multiple = XInternAtom(context->display, MULTIPLE, False);
+ atom_pair = XInternAtom(context->display, ATOM_PAIR, False);
+ size = 2 * context->ntargets;
+ pairs = calloc(size, sizeof(*pairs));
+ if (pairs == NULL)
+ return 0;
+ for (i = 0; i < context->ntargets; i++) {
+ pairs[i * 2 + 0] = context->targets[i].target;
+ pairs[i * 2 + 1] = context->targets[i].target;
+ }
+ (void)XChangeProperty(
+ context->display,
+ context->window,
+ multiple,
+ atom_pair,
+ 32,
+ PropModeReplace,
+ (unsigned char *)pairs,
+ size
+ );
+ (void)XConvertSelection(
+ context->display,
+ context->selection,
+ multiple,
+ multiple,
+ context->window,
+ context->time
+ );
+ free(pairs);
+ }
+ return 1;
+}
+
+void
+ctrlsel_filltarget(
+ Atom target,
+ Atom type,
+ int format,
+ unsigned char *buffer,
+ unsigned long size,
+ struct CtrlSelTarget *fill
+) {
+ if (fill == NULL)
+ return;
+ if (format != 32 && format != 16)
+ format = 8;
+ *fill = (struct CtrlSelTarget){
+ .target = target,
+ .type = type,
+ .action = None,
+ .format = format,
+ .nitems = size / nbytes(format),
+ .buffer = buffer,
+ .bufsize = size,
+ };
+}
+
+CtrlSelContext *
+ctrlsel_request(
+ Display *display,
+ Window window,
+ Atom selection,
+ Time time,
+ struct CtrlSelTarget targets[],
+ unsigned long ntargets
+) {
+ CtrlSelContext *context;
+
+ if (!getservertime(display, &time))
+ return NULL;
+ if ((context = malloc(sizeof(*context))) == NULL)
+ return NULL;
+ *context = (CtrlSelContext){
+ .display = display,
+ .window = window,
+ .selection = selection,
+ .time = time,
+ .targets = targets,
+ .ntargets = ntargets,
+ .selmaxsize = getselmaxsize(display),
+ .ndone = 0,
+ .transfers = NULL,
+ .dndwindow = None,
+ .dndactions = 0x00,
+ .dndresult = 0x00,
+ };
+ if (ntargets == 0)
+ return context;
+ if (request(context))
+ return context;
+ free(context);
+ return NULL;
+}
+
+CtrlSelContext *
+ctrlsel_setowner(
+ Display *display,
+ Window window,
+ Atom selection,
+ Time time,
+ int ismanager,
+ struct CtrlSelTarget targets[],
+ unsigned long ntargets
+) {
+ CtrlSelContext *context;
+ Window root;
+
+ root = DefaultRootWindow(display);
+ if (!getservertime(display, &time))
+ return NULL;
+ if ((context = malloc(sizeof(*context))) == NULL)
+ return NULL;
+ *context = (CtrlSelContext){
+ .display = display,
+ .window = window,
+ .selection = selection,
+ .time = time,
+ .targets = targets,
+ .ntargets = ntargets,
+ .selmaxsize = getselmaxsize(display),
+ .ndone = 0,
+ .transfers = NULL,
+ .dndwindow = None,
+ .dndactions = 0x00,
+ .dndresult = 0x00,
+ };
+ (void)XSetSelectionOwner(display, selection, window, time);
+ if (XGetSelectionOwner(display, selection) != window) {
+ free(context);
+ return NULL;
+ }
+ if (!ismanager)
+ return context;
+
+ /*
+ * According to ICCCM, a manager client (that is, a client
+ * responsible for managing shared resources) should take
+ * ownership of an appropriate selection.
+ *
+ * Immediately after a manager successfully acquires ownership
+ * of a manager selection, it should announce its arrival by
+ * sending a `ClientMessage` event. (That is necessary for
+ * clients to be able to know when a specific manager has
+ * started: any client that wish to do so should select for
+ * `StructureNotify` on the root window and should watch for
+ * the appropriate `MANAGER` `ClientMessage`).
+ */
+ (void)XSendEvent(
+ display,
+ root,
+ False,
+ StructureNotifyMask,
+ (XEvent *)&(XClientMessageEvent){
+ .type = ClientMessage,
+ .window = root,
+ .message_type = XInternAtom(display, MANAGER, False),
+ .format = 32,
+ .data.l[0] = time, /* timestamp */
+ .data.l[1] = selection, /* manager selection a…
+ .data.l[2] = window, /* window owning the s…
+ .data.l[3] = 0, /* manager-specific da…
+ .data.l[4] = 0, /* manager-specific da…
+ }
+ );
+ return context;
+}
+
+static int
+receiveinit(CtrlSelContext *context, XEvent *xev)
+{
+ struct CtrlSelTarget *targetp;
+ XSelectionEvent *xselev;
+ Atom multiple, atom_pair;
+ Atom *pairs;
+ Atom pair[PAIR_LAST];
+ unsigned long j, natoms;
+ unsigned long i;
+ int status, success;
+
+ multiple = XInternAtom(context->display, MULTIPLE, False);
+ atom_pair = XInternAtom(context->display, ATOM_PAIR, False);
+ xselev = &xev->xselection;
+ if (xselev->selection != context->selection)
+ return CTRLSEL_NONE;
+ if (xselev->requestor != context->window)
+ return CTRLSEL_NONE;
+ if (xselev->property == None)
+ return CTRLSEL_ERROR;
+ if (xselev->target == multiple) {
+ natoms = getatomsprop(
+ xselev->display,
+ xselev->requestor,
+ xselev->property,
+ atom_pair,
+ &pairs
+ );
+ if (natoms == 0 || pairs == NULL) {
+ free(pairs);
+ return CTRLSEL_ERROR;
+ }
+ } else {
+ pair[PAIR_TARGET] = xselev->target;
+ pair[PAIR_PROPERTY] = xselev->property;
+ pairs = pair;
+ natoms = 2;
+ }
+ success = 1;
+ for (j = 0; j < natoms; j += 2) {
+ targetp = NULL;
+ for (i = 0; i < context->ntargets; i++) {
+ if (pairs[j + PAIR_TARGET] == context->targets[i].targ…
+ targetp = &context->targets[i];
+ break;
+ }
+ }
+ if (pairs[j + PAIR_PROPERTY] == None)
+ pairs[j + PAIR_PROPERTY] = pairs[j + PAIR_TARGET];
+ if (targetp == NULL) {
+ success = 0;
+ continue;
+ }
+ status = getcontent(
+ targetp,
+ xselev->display,
+ xselev->requestor,
+ pairs[j + PAIR_PROPERTY]
+ );
+ switch (status) {
+ case CONTENT_ERROR:
+ success = 0;
+ break;
+ case CONTENT_SUCCESS:
+ /* fallthrough */
+ case CONTENT_ZERO:
+ context->ndone++;
+ break;
+ case CONTENT_INCR:
+ if (!newtransfer(context, targetp, xselev->requestor, …
+ success = 0;
+ break;
+ }
+ }
+ if (xselev->target == multiple)
+ free(pairs);
+ return success ? CTRLSEL_INTERNAL : CTRLSEL_ERROR;
+}
+
+static int
+receiveincr(CtrlSelContext *context, XEvent *xev)
+{
+ struct Transfer *transfer;
+ XPropertyEvent *xpropev;
+ int status;
+
+ xpropev = &xev->xproperty;
+ if (xpropev->state != PropertyNewValue)
+ return CTRLSEL_NONE;
+ if (xpropev->window != context->window)
+ return CTRLSEL_NONE;
+ for (transfer = (struct Transfer *)context->transfers; transfer != NUL…
+ if (transfer->property == xpropev->atom)
+ goto found;
+ return CTRLSEL_NONE;
+found:
+ status = getcontent(
+ transfer->target,
+ xpropev->display,
+ xpropev->window,
+ xpropev->atom
+ );
+ switch (status) {
+ case CONTENT_ERROR:
+ case CONTENT_INCR:
+ return CTRLSEL_ERROR;
+ case CONTENT_SUCCESS:
+ return CTRLSEL_INTERNAL;
+ case CONTENT_ZERO:
+ context->ndone++;
+ deltransfer(context, transfer);
+ break;
+ }
+ return CTRLSEL_INTERNAL;
+}
+
+int
+ctrlsel_receive(CtrlSelContext *context, XEvent *xev)
+{
+ int status;
+
+ if (xev->type == SelectionNotify)
+ status = receiveinit(context, xev);
+ else if (xev->type == PropertyNotify)
+ status = receiveincr(context, xev);
+ else
+ return CTRLSEL_NONE;
+ if (status == CTRLSEL_INTERNAL) {
+ if (context->ndone >= context->ntargets) {
+ status = CTRLSEL_RECEIVED;
+ goto done;
+ }
+ } else if (status == CTRLSEL_ERROR) {
+ freebuffers(context);
+ freetransferences(context);
+ }
+done:
+ if (status == CTRLSEL_RECEIVED)
+ freetransferences(context);
+ return status;
+}
+
+static int
+sendinit(CtrlSelContext *context, XEvent *xev)
+{
+ XSelectionRequestEvent *xreqev;
+ XSelectionEvent xselev;
+ unsigned long natoms, i;
+ Atom *pairs;
+ Atom pair[PAIR_LAST];
+ Atom multiple, atom_pair;
+ Bool success;
+
+ xreqev = &xev->xselectionrequest;
+ if (xreqev->selection != context->selection)
+ return CTRLSEL_NONE;
+ multiple = XInternAtom(context->display, MULTIPLE, False);
+ atom_pair = XInternAtom(context->display, ATOM_PAIR, False);
+ xselev = (XSelectionEvent){
+ .type = SelectionNotify,
+ .display = xreqev->display,
+ .requestor = xreqev->requestor,
+ .selection = xreqev->selection,
+ .time = xreqev->time,
+ .target = xreqev->target,
+ .property = None,
+ };
+ if (xreqev->time != CurrentTime && xreqev->time < context->time) {
+ /*
+ * According to ICCCM, the selection owner
+ * should compare the timestamp with the period
+ * it has owned the selection and, if the time
+ * is outside, refuse the `SelectionRequest` by
+ * sending the requestor window a
+ * `SelectionNotify` event with the property set
+ * to `None` (by means of a `SendEvent` request
+ * with an empty event mask).
+ */
+ goto done;
+ }
+ if (xreqev->target == multiple) {
+ if (xreqev->property == None)
+ goto done;
+ natoms = getatomsprop(
+ xreqev->display,
+ xreqev->requestor,
+ xreqev->property,
+ atom_pair,
+ &pairs
+ );
+ } else {
+ pair[PAIR_TARGET] = xreqev->target;
+ pair[PAIR_PROPERTY] = xreqev->property;
+ pairs = pair;
+ natoms = 2;
+ }
+ success = True;
+ for (i = 0; i < natoms; i += 2) {
+ if (!convert(context, xreqev->requestor,
+ pairs[i + PAIR_TARGET],
+ pairs[i + PAIR_PROPERTY])) {
+ success = False;
+ pairs[i + PAIR_PROPERTY] = None;
+ }
+ }
+ if (xreqev->target == multiple) {
+ XChangeProperty(
+ xreqev->display,
+ xreqev->requestor,
+ xreqev->property,
+ atom_pair,
+ 32, PropModeReplace,
+ (unsigned char *)pairs,
+ natoms
+ );
+ free(pairs);
+ }
+ if (success) {
+ if (xreqev->property == None) {
+ xselev.property = xreqev->target;
+ } else {
+ xselev.property = xreqev->property;
+ }
+ }
+done:
+ XSendEvent(
+ xreqev->display,
+ xreqev->requestor,
+ False,
+ NoEventMask,
+ (XEvent *)&xselev
+ );
+ return CTRLSEL_INTERNAL;
+}
+
+static int
+sendlost(CtrlSelContext *context, XEvent *xev)
+{
+ XSelectionClearEvent *xclearev;
+
+ xclearev = &xev->xselectionclear;
+ if (xclearev->selection == context->selection &&
+ xclearev->window == context->window) {
+ return CTRLSEL_LOST;
+ }
+ return CTRLSEL_NONE;
+}
+
+static int
+senddestroy(CtrlSelContext *context, XEvent *xev)
+{
+ struct Transfer *transfer;
+ XDestroyWindowEvent *xdestroyev;
+
+ xdestroyev = &xev->xdestroywindow;
+ for (transfer = context->transfers; transfer != NULL; transfer = trans…
+ if (transfer->requestor == xdestroyev->window)
+ deltransfer(context, transfer);
+ return CTRLSEL_NONE;
+}
+
+static int
+sendincr(CtrlSelContext *context, XEvent *xev)
+{
+ struct Transfer *transfer;
+ XPropertyEvent *xpropev;
+ unsigned long size;
+
+ xpropev = &xev->xproperty;
+ if (xpropev->state != PropertyDelete)
+ return CTRLSEL_NONE;
+ for (transfer = context->transfers; transfer != NULL; transfer = trans…
+ if (transfer->property == xpropev->atom &&
+ transfer->requestor == xpropev->window)
+ goto found;
+ return CTRLSEL_NONE;
+found:
+ if (transfer->size >= transfer->target->bufsize)
+ transfer->size = transfer->target->bufsize;
+ size = transfer->target->bufsize - transfer->size;
+ if (size > context->selmaxsize)
+ size = context->selmaxsize;
+ XChangeProperty(
+ xpropev->display,
+ xpropev->window,
+ xpropev->atom,
+ transfer->target->target,
+ transfer->target->format,
+ PropModeReplace,
+ transfer->target->buffer + transfer->size,
+ size / nbytes(transfer->target->format)
+ );
+ if (transfer->size >= transfer->target->bufsize) {
+ deltransfer(context, transfer);
+ } else {
+ transfer->size += size;
+ }
+ return CTRLSEL_INTERNAL;
+}
+
+int
+ctrlsel_send(CtrlSelContext *context, XEvent *xev)
+{
+ int status;
+
+ if (xev->type == SelectionRequest)
+ status = sendinit(context, xev);
+ else if (xev->type == SelectionClear)
+ status = sendlost(context, xev);
+ else if (xev->type == DestroyNotify)
+ status = senddestroy(context, xev);
+ else if (xev->type == PropertyNotify)
+ status = sendincr(context, xev);
+ else
+ return CTRLSEL_NONE;
+ if (status == CTRLSEL_LOST || status == CTRLSEL_ERROR) {
+ status = CTRLSEL_LOST;
+ freetransferences(context);
+ }
+ return status;
+}
+
+void
+ctrlsel_cancel(CtrlSelContext *context)
+{
+ if (context == NULL)
+ return;
+ freebuffers(context);
+ freetransferences(context);
+ free(context);
+}
+
+void
+ctrlsel_disown(CtrlSelContext *context)
+{
+ if (context == NULL)
+ return;
+ freetransferences(context);
+ free(context);
+}
+
+static Bool
+dndpred(Display *display, XEvent *event, XPointer p)
+{
+ struct PredArg *arg;
+ struct Transfer *transfer;
+
+ arg = (struct PredArg *)p;
+ switch (event->type) {
+ case KeyPress:
+ case KeyRelease:
+ if (event->xkey.display == display &&
+ event->xkey.window == arg->window)
+ return True;
+ break;
+ case ButtonPress:
+ case ButtonRelease:
+ if (event->xbutton.display == display &&
+ event->xbutton.window == arg->window)
+ return True;
+ break;
+ case MotionNotify:
+ if (event->xmotion.display == display &&
+ event->xmotion.window == arg->window)
+ return True;
+ break;
+ case DestroyNotify:
+ if (event->xdestroywindow.display == display &&
+ event->xdestroywindow.window == arg->window)
+ return True;
+ break;
+ case UnmapNotify:
+ if (event->xunmap.display == display &&
+ event->xunmap.window == arg->window)
+ return True;
+ break;
+ case SelectionClear:
+ if (event->xselectionclear.display == display &&
+ event->xselectionclear.window == arg->window)
+ return True;
+ break;
+ case SelectionRequest:
+ if (event->xselectionrequest.display == display &&
+ event->xselectionrequest.owner == arg->window)
+ return True;
+ break;
+ case ClientMessage:
+ if (event->xclient.display == display &&
+ event->xclient.window == arg->window &&
+ event->xclient.message_type == arg->message_type)
+ return True;
+ break;
+ case PropertyNotify:
+ if (event->xproperty.display != display ||
+ event->xproperty.state != PropertyDelete)
+ return False;
+ for (transfer = arg->context->transfers;
+ transfer != NULL;
+ transfer = transfer->next) {
+ if (transfer->property == event->xproperty.atom &&
+ transfer->requestor == event->xproperty.window) {
+ return True;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return False;
+}
+
+#define SOME(a, b, c) ((a) != None ? (a) : ((b) != None ? (b) : (c)))
+
+static Cursor
+getcursor(Cursor cursors[CURSOR_LAST], int type)
+{
+ switch (type) {
+ case CURSOR_TARGET:
+ case CURSOR_DRAG:
+ return SOME(cursors[CURSOR_DRAG], cursors[CURSOR_TARGET], None…
+ case CURSOR_PIRATE:
+ case CURSOR_NODROP:
+ return SOME(cursors[CURSOR_NODROP], cursors[CURSOR_PIRATE], No…
+ case CURSOR_COPY:
+ return SOME(cursors[CURSOR_COPY], cursors[CURSOR_DRAG], cursor…
+ case CURSOR_MOVE:
+ return SOME(cursors[CURSOR_MOVE], cursors[CURSOR_DRAG], cursor…
+ case CURSOR_LINK:
+ return SOME(cursors[CURSOR_LINK], cursors[CURSOR_DRAG], cursor…
+ };
+ return None;
+}
+
+static void
+initcursors(Display *display, Cursor cursors[CURSOR_LAST])
+{
+ cursors[CURSOR_TARGET] = XCreateFontCursor(display, XC_target);
+ cursors[CURSOR_PIRATE] = XCreateFontCursor(display, XC_pirate);
+ cursors[CURSOR_DRAG] = XcursorLibraryLoadCursor(display, "dnd-none");
+ cursors[CURSOR_COPY] = XcursorLibraryLoadCursor(display, "dnd-copy");
+ cursors[CURSOR_MOVE] = XcursorLibraryLoadCursor(display, "dnd-move");
+ cursors[CURSOR_LINK] = XcursorLibraryLoadCursor(display, "dnd-link");
+ cursors[CURSOR_NODROP] = XcursorLibraryLoadCursor(display, "forbidden"…
+}
+
+static void
+freecursors(Display *display, Cursor cursors[CURSOR_LAST])
+{
+ int i;
+
+ for (i = 0; i < CURSOR_LAST; i++) {
+ if (cursors[i] != None) {
+ XFreeCursor(display, cursors[i]);
+ }
+ }
+}
+
+static int
+querypointer(Display *display, Window window, int *retx, int *rety, Window *re…
+{
+ Window root, child;
+ unsigned int mask;
+ int rootx, rooty;
+ int x, y;
+ int retval;
+
+ retval = XQueryPointer(
+ display,
+ window,
+ &root, &child,
+ &rootx, &rooty,
+ &x, &y,
+ &mask
+ );
+ if (retwin != NULL)
+ *retwin = child;
+ if (retx != NULL)
+ *retx = x;
+ if (rety != NULL)
+ *rety = y;
+ return retval;
+}
+
+static Window
+getdndwindowbelow(Display *display, Window root, Atom aware, Atom *version)
+{
+ Atom *p;
+ Window window;
+
+ /*
+ * Query pointer location and return the window below it,
+ * and the version of the XDND protocol it uses.
+ */
+ *version = None;
+ window = root;
+ p = NULL;
+ while (querypointer(display, window, NULL, NULL, &window)) {
+ if (window == None)
+ break;
+ p = NULL;
+ if (getatomsprop(display, window, aware, AnyPropertyType, &p) …
+ *version = *p;
+ XFree(p);
+ return window;
+ }
+ }
+ XFree(p);
+ return None;
+}
+
+CtrlSelContext *
+ctrlsel_dndwatch(
+ Display *display,
+ Window window,
+ unsigned int actions,
+ struct CtrlSelTarget targets[],
+ unsigned long ntargets
+) {
+ CtrlSelContext *context;
+ Atom version = XDND_VERSION; /* yes, version is an Atom */
+ Atom xdndaware, xdndselection;
+
+ xdndaware = XInternAtom(display, atomnames[XDND_AWARE], False);
+ if (xdndaware == None)
+ return NULL;
+ xdndselection = XInternAtom(display, atomnames[XDND_SELECTION], False);
+ if (xdndselection == None)
+ return NULL;
+ if ((context = malloc(sizeof(*context))) == NULL)
+ return NULL;
+ *context = (CtrlSelContext){
+ .display = display,
+ .window = window,
+ .selection = xdndselection,
+ .time = CurrentTime,
+ .targets = targets,
+ .ntargets = ntargets,
+ .selmaxsize = getselmaxsize(display),
+ .ndone = 0,
+ .transfers = NULL,
+ .dndwindow = None,
+ .dndactions = actions,
+ .dndresult = 0x00,
+ };
+ (void)XChangeProperty(
+ display,
+ window,
+ xdndaware,
+ XA_ATOM, 32,
+ PropModeReplace,
+ (unsigned char *)&version,
+ 1
+ );
+ return context;
+}
+
+static void
+finishdrop(CtrlSelContext *context)
+{
+ long d[NCLIENTMSG_DATA];
+ unsigned long i;
+ Atom finished;
+
+ if (context->dndwindow == None)
+ return;
+ finished = XInternAtom(context->display, atomnames[XDND_FINISHED], Fal…
+ if (finished == None)
+ return;
+ for (i = 0; i < context->ntargets; i++)
+ context->targets[i].action = context->dndresult;
+ d[0] = context->window;
+ d[1] = d[2] = d[3] = d[4] = 0;
+ clientmsg(context->display, context->dndwindow, finished, d);
+ context->dndwindow = None;
+}
+
+int
+ctrlsel_dndreceive(CtrlSelContext *context, XEvent *event)
+{
+ Atom atoms[XDND_ATOM_LAST];
+ Atom action;
+ long d[NCLIENTMSG_DATA];
+
+ if (!XInternAtoms(context->display, atomnames, XDND_ATOM_LAST, False, …
+ return CTRLSEL_NONE;
+ switch (ctrlsel_receive(context, event)) {
+ case CTRLSEL_RECEIVED:
+ finishdrop(context);
+ return CTRLSEL_RECEIVED;
+ case CTRLSEL_INTERNAL:
+ case CTRLSEL_ERROR:
+ return CTRLSEL_INTERNAL;
+ default:
+ break;
+ }
+ if (event->type != ClientMessage)
+ return CTRLSEL_NONE;
+ if (event->xclient.message_type == atoms[XDND_ENTER]) {
+ context->dndwindow = (Window)event->xclient.data.l[0];
+ context->dndresult = 0x00;
+ } else if (event->xclient.message_type == atoms[XDND_LEAVE]) {
+ if ((Window)event->xclient.data.l[0] == None ||
+ (Window)event->xclient.data.l[0] != context->dndwindow)
+ return CTRLSEL_NONE;
+ context->dndwindow = None;
+ } else if (event->xclient.message_type == atoms[XDND_DROP]) {
+ if ((Window)event->xclient.data.l[0] == None ||
+ (Window)event->xclient.data.l[0] != context->dndwindow)
+ return CTRLSEL_NONE;
+ context->time = (Time)event->xclient.data.l[2];
+ (void)request(context);
+ } else if (event->xclient.message_type == atoms[XDND_POSITION]) {
+ if ((Window)event->xclient.data.l[0] == None ||
+ (Window)event->xclient.data.l[0] != context->dndwindow)
+ return CTRLSEL_NONE;
+ if (((Atom)event->xclient.data.l[4] == atoms[XDND_ACTION_COPY]…
+ context->dndactions & CTRLSEL_COPY) ||
+ ((Atom)event->xclient.data.l[4] == atoms[XDND_ACTION_MOVE]…
+ context->dndactions & CTRLSEL_MOVE) ||
+ ((Atom)event->xclient.data.l[4] == atoms[XDND_ACTION_LINK]…
+ context->dndactions & CTRLSEL_LINK) ||
+ ((Atom)event->xclient.data.l[4] == atoms[XDND_ACTION_ASK] …
+ context->dndactions & CTRLSEL_ASK) ||
+ ((Atom)event->xclient.data.l[4] == atoms[XDND_ACTION_PRIVA…
+ context->dndactions & CTRLSEL_PRIVATE)) {
+ action = (Atom)event->xclient.data.l[4];
+ } else {
+ action = atoms[XDND_ACTION_COPY];
+ }
+ d[0] = context->window;
+ d[1] = 0x1;
+ d[2] = 0; /* our rectangle is the entire screen …
+ d[3] = 0xFFFFFFFF; /* so we do not get lots of messages */
+ d[4] = action;
+ if (action == atoms[XDND_ACTION_PRIVATE])
+ context->dndresult = CTRLSEL_PRIVATE;
+ else if (action == atoms[XDND_ACTION_ASK])
+ context->dndresult = CTRLSEL_ASK;
+ else if (action == atoms[XDND_ACTION_LINK])
+ context->dndresult = CTRLSEL_LINK;
+ else if (action == atoms[XDND_ACTION_MOVE])
+ context->dndresult = CTRLSEL_MOVE;
+ else
+ context->dndresult = CTRLSEL_COPY;
+ clientmsg(
+ context->display,
+ (Window)event->xclient.data.l[0],
+ atoms[XDND_STATUS],
+ d
+ );
+ } else {
+ return CTRLSEL_NONE;
+ }
+ return CTRLSEL_INTERNAL;
+}
+
+void
+ctrlsel_dndclose(CtrlSelContext *context)
+{
+ if (context == NULL)
+ return;
+ finishdrop(context);
+ freebuffers(context);
+ freetransferences(context);
+ free(context);
+}
+
+void
+ctrlsel_dnddisown(CtrlSelContext *context)
+{
+ ctrlsel_disown(context);
+}
+
+int
+ctrlsel_dndsend(CtrlSelContext *context, XEvent *event)
+{
+ Atom finished;
+
+ finished = XInternAtom(context->display, atomnames[XDND_FINISHED], Fal…
+ if (event->type == ClientMessage &&
+ event->xclient.message_type == finished &&
+ (Window)event->xclient.data.l[0] == context->dndwindow) {
+ ctrlsel_dnddisown(context);
+ return CTRLSEL_SENT;
+ }
+ return ctrlsel_send(context, event);
+}
+
+int
+ctrlsel_dndown(
+ Display *display,
+ Window window,
+ Window miniature,
+ Time time,
+ struct CtrlSelTarget targets[],
+ unsigned long ntargets,
+ CtrlSelContext **context_ret
+) {
+ CtrlSelContext *context;
+ struct PredArg arg;
+ XWindowAttributes wattr;
+ XEvent event;
+ Atom atoms[XDND_ATOM_LAST];
+ Cursor cursors[CURSOR_LAST] = { None, None };
+ Cursor cursor;
+ Window lastwin, winbelow;
+ Atom lastaction, action, version;
+ long d[NCLIENTMSG_DATA];
+ int sendposition, retval, status, inside;
+ int x, y, w, h;
+
+ *context_ret = NULL;
+ if (display == NULL || window == None)
+ return CTRLSEL_ERROR;
+ if (!XGetWindowAttributes(display, window, &wattr))
+ return CTRLSEL_ERROR;
+ if ((wattr.your_event_mask & StructureNotifyMask) == 0x00)
+ return CTRLSEL_ERROR;
+ if (wattr.map_state != IsViewable)
+ return CTRLSEL_ERROR;
+ if (!XInternAtoms(display, atomnames, XDND_ATOM_LAST, False, atoms))
+ return CTRLSEL_ERROR;
+ context = ctrlsel_setowner(
+ display,
+ window,
+ atoms[XDND_SELECTION],
+ time,
+ 0,
+ targets,
+ ntargets
+ );
+ if (context == NULL)
+ return CTRLSEL_ERROR;
+ d[0] = window;
+ sendposition = 1;
+ x = y = w = h = 0;
+ retval = CTRLSEL_ERROR;
+ lastaction = action = None;
+ lastwin = None;
+ arg = (struct PredArg){
+ .context = context,
+ .window = window,
+ .message_type = atoms[XDND_STATUS],
+ };
+ initcursors(display, cursors);
+ status = XGrabPointer(
+ display,
+ window,
+ True,
+ ButtonPressMask | ButtonMotionMask |
+ ButtonReleaseMask | PointerMotionMask,
+ GrabModeAsync,
+ GrabModeAsync,
+ None,
+ None,
+ time
+ );
+ if (status != GrabSuccess)
+ goto done;
+ status = XGrabKeyboard(
+ display,
+ window,
+ True,
+ GrabModeAsync,
+ GrabModeAsync,
+ time
+ );
+ if (status != GrabSuccess)
+ goto done;
+ if (miniature != None)
+ XMapRaised(display, miniature);
+ cursor = getcursor(cursors, CURSOR_DRAG);
+ for (;;) {
+ (void)XIfEvent(display, &event, &dndpred, (XPointer)&arg);
+ switch (ctrlsel_send(context, &event)) {
+ case CTRLSEL_LOST:
+ retval = CTRLSEL_NONE;
+ goto done;
+ case CTRLSEL_INTERNAL:
+ continue;
+ default:
+ break;
+ }
+ switch (event.type) {
+ case KeyPress:
+ case KeyRelease:
+ if (event.xkey.keycode != 0 &&
+ event.xkey.keycode == XKeysymToKeycode(display, XK…
+ retval = CTRLSEL_NONE;
+ goto done;
+ }
+ break;
+ case ButtonPress:
+ case ButtonRelease:
+ if (lastwin == None) {
+ retval = CTRLSEL_NONE;
+ } else if (lastwin == window) {
+ retval = CTRLSEL_DROPSELF;
+ } else {
+ retval = CTRLSEL_DROPOTHER;
+ d[1] = d[3] = d[4] = 0;
+ d[2] = event.xbutton.time;
+ clientmsg(display, lastwin, atoms[XDND_DROP], …
+ context->dndwindow = lastwin;
+ }
+ goto done;
+ case MotionNotify:
+ if (event.xmotion.time - time < MOTION_TIME)
+ break;
+ if (miniature != None) {
+ XMoveWindow(
+ display,
+ miniature,
+ event.xmotion.x_root + DND_DISTANCE,
+ event.xmotion.y_root + DND_DISTANCE
+ );
+ }
+ inside = between(event.xmotion.x, event.xmotion.y, x, …
+ if ((lastaction != action || sendposition || !inside)
+ && lastwin != None) {
+ if (lastaction != None)
+ d[4] = lastaction;
+ else if (FLAG(event.xmotion.state, ControlMask…
+ d[4] = atoms[XDND_ACTION_LINK];
+ else if (FLAG(event.xmotion.state, ShiftMask))
+ d[4] = atoms[XDND_ACTION_MOVE];
+ else if (FLAG(event.xmotion.state, ControlMask…
+ d[4] = atoms[XDND_ACTION_COPY];
+ else
+ d[4] = atoms[XDND_ACTION_ASK];
+ d[1] = 0;
+ d[2] = event.xmotion.x_root << 16;
+ d[2] |= event.xmotion.y_root & 0xFFFF;
+ d[3] = event.xmotion.time;
+ clientmsg(display, lastwin, atoms[XDND_POSITIO…
+ sendposition = 1;
+ }
+ time = event.xmotion.time;
+ lastaction = action;
+ winbelow = getdndwindowbelow(display, wattr.root, atom…
+ if (winbelow == lastwin)
+ break;
+ sendposition = 1;
+ x = y = w = h = 0;
+ if (version > XDND_VERSION)
+ version = XDND_VERSION;
+ if (lastwin != None && lastwin != window) {
+ d[1] = d[2] = d[3] = d[4] = 0;
+ clientmsg(display, lastwin, atoms[XDND_LEAVE],…
+ }
+ if (winbelow != None && winbelow != window) {
+ d[1] = version;
+ d[1] <<= 24;
+ d[2] = ntargets > 0 ? targets[0].target : None;
+ d[3] = ntargets > 1 ? targets[1].target : None;
+ d[4] = ntargets > 2 ? targets[2].target : None;
+ clientmsg(display, winbelow, atoms[XDND_ENTER]…
+ }
+ if (winbelow == None)
+ cursor = getcursor(cursors, CURSOR_NODROP);
+ else if (FLAG(event.xmotion.state, ControlMask|ShiftMa…
+ cursor = getcursor(cursors, CURSOR_LINK);
+ else if (FLAG(event.xmotion.state, ShiftMask))
+ cursor = getcursor(cursors, CURSOR_MOVE);
+ else if (FLAG(event.xmotion.state, ControlMask))
+ cursor = getcursor(cursors, CURSOR_COPY);
+ else
+ cursor = getcursor(cursors, CURSOR_DRAG);
+ XDefineCursor(display, window, cursor);
+ lastwin = winbelow;
+ lastaction = action = None;
+ break;
+ case ClientMessage:
+ if ((Window)event.xclient.data.l[0] != lastwin)
+ break;
+ sendposition = (event.xclient.data.l[1] & 0x02);
+ if (event.xclient.data.l[1] & 0x01)
+ XDefineCursor(display, window, cursor);
+ else
+ XDefineCursor(display, window, getcursor(curso…
+ x = event.xclient.data.l[2] >> 16;
+ y = event.xclient.data.l[2] & 0xFFF;
+ w = event.xclient.data.l[3] >> 16;
+ h = event.xclient.data.l[3] & 0xFFF;
+ if ((Atom)event.xclient.data.l[4] != None)
+ action = (Atom)event.xclient.data.l[4];
+ else
+ action = atoms[XDND_ACTION_COPY];
+ break;
+ case DestroyNotify:
+ case UnmapNotify:
+ XPutBackEvent(display, &event);
+ retval = CTRLSEL_ERROR;
+ goto done;
+ default:
+ break;
+ }
+ }
+done:
+ XUndefineCursor(display, window);
+ if (miniature != None)
+ XUnmapWindow(display, miniature);
+ XUngrabPointer(display, CurrentTime);
+ XUngrabKeyboard(display, CurrentTime);
+ freecursors(display, cursors);
+ if (retval != CTRLSEL_DROPOTHER) {
+ ctrlsel_dnddisown(context);
+ context = NULL;
+ }
+ *context_ret = context;
+ return retval;
+}
diff --git a/ctrlsel.h b/ctrlsel.h
t@@ -0,0 +1,107 @@
+/*
+ * ctrlsel: X11 selection ownership and request helper functions
+ *
+ * Refer to the accompanying manual for a description of the interface.
+ */
+#ifndef _CTRLSEL_H_
+#define _CTRLSEL_H_
+
+enum {
+ CTRLSEL_NONE,
+ CTRLSEL_INTERNAL,
+ CTRLSEL_RECEIVED,
+ CTRLSEL_SENT,
+ CTRLSEL_DROPSELF,
+ CTRLSEL_DROPOTHER,
+ CTRLSEL_ERROR,
+ CTRLSEL_LOST
+};
+
+enum {
+ CTRLSEL_COPY = 0x01,
+ CTRLSEL_MOVE = 0x02,
+ CTRLSEL_LINK = 0x04,
+ CTRLSEL_ASK = 0x08,
+ CTRLSEL_PRIVATE = 0x10,
+};
+
+typedef struct CtrlSelContext CtrlSelContext;
+
+struct CtrlSelTarget {
+ Atom target;
+ Atom type;
+ int format;
+ unsigned int action;
+ unsigned long nitems;
+ unsigned long bufsize;
+ unsigned char *buffer;
+};
+
+void
+ctrlsel_filltarget(
+ Atom target,
+ Atom type,
+ int format,
+ unsigned char *buffer,
+ unsigned long size,
+ struct CtrlSelTarget *fill
+);
+
+CtrlSelContext *
+ctrlsel_request(
+ Display *display,
+ Window window,
+ Atom selection,
+ Time time,
+ struct CtrlSelTarget targets[],
+ unsigned long ntargets
+);
+
+CtrlSelContext *
+ctrlsel_setowner(
+ Display *display,
+ Window window,
+ Atom selection,
+ Time time,
+ int ismanager,
+ struct CtrlSelTarget targets[],
+ unsigned long ntargets
+);
+
+int ctrlsel_receive(struct CtrlSelContext *context, XEvent *event);
+
+int ctrlsel_send(struct CtrlSelContext *context, XEvent *event);
+
+void ctrlsel_cancel(struct CtrlSelContext *context);
+
+void ctrlsel_disown(struct CtrlSelContext *context);
+
+CtrlSelContext *
+ctrlsel_dndwatch(
+ Display *display,
+ Window window,
+ unsigned int actions,
+ struct CtrlSelTarget targets[],
+ unsigned long ntargets
+);
+
+int ctrlsel_dndreceive(struct CtrlSelContext *context, XEvent *event);
+
+void ctrlsel_dndclose(struct CtrlSelContext *context);
+
+int
+ctrlsel_dndown(
+ Display *display,
+ Window window,
+ Window miniature,
+ Time time,
+ struct CtrlSelTarget targets[],
+ unsigned long ntargets,
+ CtrlSelContext **context
+);
+
+int ctrlsel_dndsend(struct CtrlSelContext *context, XEvent *event);
+
+void ctrlsel_dnddisown(struct CtrlSelContext *context);
+
+#endif /* _CTRLSEL_H_ */
You are viewing proxied material from lumidify.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.