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_ */ |