windowmaker-wl/WINGs/selection_x11.c

424 lines
11 KiB
C

#include <stdlib.h>
#include <X11/Xatom.h>
#include "WINGsP.h"
#include "WINGs/WINGsX11.h"
#define MAX_PROPERTY_SIZE 8*1024
const char *WMSelectionOwnerDidChangeNotification = "WMSelectionOwnerDidChange";
typedef struct SelectionHandler {
WMView *view;
WMNativeAtom selection;
Time timestamp;
WMSelectionProcs procs;
void *data;
struct {
unsigned delete_pending:1;
unsigned done_pending:1;
} flags;
} SelectionHandler;
typedef struct SelectionCallback {
WMView *view;
WMNativeAtom selection;
WMNativeAtom target;
Time timestamp;
WMSelectionCallback *callback;
void *data;
struct {
unsigned delete_pending:1;
unsigned done_pending:1;
} flags;
} SelectionCallback;
static WMArray *selCallbacks = NULL;
static WMArray *selHandlers = NULL;
static Bool gotXError = False;
void WMDeleteSelectionHandler(WMView * view, WMNativeAtom selection, Time timestamp)
{
SelectionHandler *handler;
WMArrayIterator iter;
if (!selHandlers)
return;
/*//printf("deleting selection handler for %d", win); */
WM_ITERATE_ARRAY(selHandlers, handler, iter) {
if (handler->view == view && (handler->selection == selection || selection == WM_NATIVE_NONE)
&& (handler->timestamp == timestamp || timestamp == WM_CURRENT_TIME)) {
if (handler->flags.done_pending) {
handler->flags.delete_pending = 1;
/*//puts(": postponed because still pending"); */
return;
}
/*//printf(": found & removed"); */
WMRemoveFromArray(selHandlers, handler);
break;
}
}
/*//printf("\n"); */
const W_ViewBackend *vb = view->screen->viewBackend;
vb->selection_release(view->screen->viewBackendCtx, view, selection);
}
static void WMDeleteSelectionCallback(WMView * view, WMNativeAtom selection, Time timestamp)
{
SelectionCallback *handler;
WMArrayIterator iter;
if (!selCallbacks)
return;
WM_ITERATE_ARRAY(selCallbacks, handler, iter) {
if (handler->view == view && (handler->selection == selection || selection == WM_NATIVE_NONE)
&& (handler->timestamp == timestamp || timestamp == WM_CURRENT_TIME)) {
if (handler->flags.done_pending) {
handler->flags.delete_pending = 1;
return;
}
WMRemoveFromArray(selCallbacks, handler);
break;
}
}
}
static int handleXError(Display * dpy, XErrorEvent * ev)
{
/* Parameter not used, but tell the compiler that it is ok */
(void) dpy;
(void) ev;
gotXError = True;
return 1;
}
static Bool writeSelection(Display * dpy, WNativeWindow requestor, WMNativeAtom property, WMNativeAtom type, WMData * data)
{
static void *oldHandler;
int format, bpi;
format = WMGetDataFormat(data);
if (format == 0)
format = 8;
bpi = format / 8;
/* printf("write to %x: %s\n", requestor, XGetAtomName(dpy, property)); */
oldHandler = XSetErrorHandler(handleXError);
gotXError = False;
XChangeProperty(dpy, requestor, property, type, format, PropModeReplace,
WMDataBytes(data), WMGetDataLength(data) / bpi);
XFlush(dpy);
XSetErrorHandler(oldHandler);
return !gotXError;
}
static void notifySelection(XEvent * event, WMNativeAtom prop)
{
XEvent ev;
/* printf("event to %x\n", event->xselectionrequest.requestor); */
ev.xselection.type = SelectionNotify;
ev.xselection.serial = 0;
ev.xselection.send_event = True;
ev.xselection.display = event->xselectionrequest.display;
ev.xselection.requestor = event->xselectionrequest.requestor;
ev.xselection.target = event->xselectionrequest.target;
ev.xselection.selection = event->xselectionrequest.selection;
ev.xselection.property = prop;
ev.xselection.time = event->xselectionrequest.time;
XSendEvent(event->xany.display, event->xselectionrequest.requestor, False, 0, &ev);
XFlush(event->xany.display);
}
static void handleRequestEvent(XEvent * event)
{
SelectionHandler *handler;
WMArrayIterator iter;
WMArray *copy;
Bool handledRequest;
WM_ITERATE_ARRAY(selHandlers, handler, iter) {
switch (event->type) {
case SelectionClear:
if (W_VIEW_DRAWABLE(handler->view)
!= event->xselectionclear.window) {
break;
}
handler->flags.done_pending = 1;
if (handler->procs.selectionLost)
handler->procs.selectionLost(handler->view, handler->selection, handler->data);
handler->flags.done_pending = 0;
handler->flags.delete_pending = 1;
break;
case SelectionRequest:
if (W_VIEW_DRAWABLE(handler->view) != event->xselectionrequest.owner) {
break;
}
if (handler->procs.convertSelection != NULL
&& handler->selection == event->xselectionrequest.selection) {
WMNativeAtom atom;
WMData *data;
WMNativeAtom prop;
/* they're requesting for something old.. maybe another handler
* can handle it */
if (event->xselectionrequest.time < handler->timestamp
&& event->xselectionrequest.time != WM_CURRENT_TIME) {
break;
}
handledRequest = False;
handler->flags.done_pending = 1;
data = handler->procs.convertSelection(handler->view,
handler->selection,
event->xselectionrequest.target,
handler->data, &atom);
prop = event->xselectionrequest.property;
/* obsolete clients that don't set the property field */
if (prop == WM_NATIVE_NONE)
prop = event->xselectionrequest.target;
if (data) {
if (writeSelection(event->xselectionrequest.display,
event->xselectionrequest.requestor, prop, atom, data)) {
handledRequest = True;
}
WMReleaseData(data);
}
notifySelection(event, (handledRequest == True ? prop : WM_NATIVE_NONE));
if (handler->procs.selectionDone != NULL) {
handler->procs.selectionDone(handler->view,
handler->selection,
event->xselectionrequest.target,
handler->data);
}
handler->flags.done_pending = 0;
}
break;
}
}
/* delete handlers */
copy = WMDuplicateArray(selHandlers);
WM_ITERATE_ARRAY(copy, handler, iter) {
if (handler && handler->flags.delete_pending) {
WMDeleteSelectionHandler(handler->view, handler->selection, handler->timestamp);
}
}
WMFreeArray(copy);
}
static WMData *getSelectionData(Display * dpy, WNativeWindow win, WMNativeAtom where)
{
WMData *wdata;
unsigned char *data;
WMNativeAtom rtype;
int bits, bpi;
unsigned long len, bytes;
if (XGetWindowProperty(dpy, win, where, 0, MAX_PROPERTY_SIZE,
False, AnyPropertyType, &rtype, &bits, &len, &bytes, &data) != Success) {
return NULL;
}
bpi = bits / 8;
wdata = WMCreateDataWithBytesNoCopy(data, len * bpi, (void *) XFree);
WMSetDataFormat(wdata, bits);
return wdata;
}
static void handleNotifyEvent(XEvent * event)
{
SelectionCallback *handler;
WMArrayIterator iter;
WMArray *copy;
WMData *data;
WM_ITERATE_ARRAY(selCallbacks, handler, iter) {
if (W_VIEW_DRAWABLE(handler->view) != event->xselection.requestor
|| handler->selection != event->xselection.selection) {
continue;
}
handler->flags.done_pending = 1;
if (event->xselection.property == WM_NATIVE_NONE) {
data = NULL;
} else {
data = getSelectionData(event->xselection.display,
event->xselection.requestor, event->xselection.property);
}
(*handler->callback) (handler->view, handler->selection,
handler->target, handler->timestamp, handler->data, data);
if (data != NULL) {
WMReleaseData(data);
}
handler->flags.done_pending = 0;
handler->flags.delete_pending = 1;
}
/* delete callbacks */
copy = WMDuplicateArray(selCallbacks);
WM_ITERATE_ARRAY(copy, handler, iter) {
if (handler && handler->flags.delete_pending) {
WMDeleteSelectionCallback(handler->view, handler->selection, handler->timestamp);
}
}
WMFreeArray(copy);
}
void W_HandleSelectionEvent(XEvent *event)
{
/*//printf("%d received selection ", event->xany.window); */
/*//switch(event->type) {
case SelectionNotify:
puts("notify"); break;
case SelectionRequest:
puts("request"); break;
case SelectionClear:
puts("clear"); break;
default:
puts("unknown"); break;
} */
if (event->type == SelectionNotify) {
handleNotifyEvent(event);
} else {
handleRequestEvent(event);
}
}
Bool WMCreateSelectionHandler(WMView * view, WMNativeAtom selection, Time timestamp, WMSelectionProcs * procs, void *cdata)
{
SelectionHandler *handler;
const W_ViewBackend *vb = view->screen->viewBackend;
if (vb->selection_acquire && !vb->selection_acquire(view->screen->viewBackendCtx, view, selection, timestamp)) {
return False;
}
WMPostNotificationName(WMSelectionOwnerDidChangeNotification, (void *)selection, (void *)view);
/* On Wayland: delete any existing handler so fresh data is
* pushed on re-selection (e.g. double-click after paste). */
if (vb->selection_fetch)
WMDeleteSelectionHandler(view, selection, WM_CURRENT_TIME);
/*//printf("created selection handler for %d\n", W_VIEW_DRAWABLE(view)); */
handler = wmalloc(sizeof(SelectionHandler));
handler->view = view;
handler->selection = selection;
handler->timestamp = timestamp;
handler->procs = *procs;
handler->data = cdata;
memset(&handler->flags, 0, sizeof(handler->flags));
if (selHandlers == NULL) {
selHandlers = WMCreateArrayWithDestructor(4, wfree);
}
WMAddToArray(selHandlers, handler);
/* On Wayland (no X11 SelectionRequest events), immediately push
* the selection data via the clipboard store path so external
* clients can paste it. */
if (vb->selection_fetch && handler->procs.convertSelection) {
WMNativeAtom textAtom = XA_STRING;
WMNativeAtom type;
WMData *d = handler->procs.convertSelection(handler->view,
selection, textAtom, handler->data, &type);
if (d) {
if (view->screen->viewBackend->clipboard_store) {
view->screen->viewBackend->clipboard_store(
view->screen->viewBackendCtx,
WMDataBytes(d),
WMGetDataLength(d));
}
WMReleaseData(d);
}
}
return True;
}
Bool
WMRequestSelection(WMView * view, WMNativeAtom selection, WMNativeAtom target, Time timestamp,
WMSelectionCallback * callback, void *cdata)
{
SelectionCallback *handler;
const W_ViewBackend *vb = view->screen->viewBackend;
/* Synchronous path (Wayland): fetch data directly and call callback */
if (vb->selection_fetch) {
int len = 0;
char *text = vb->selection_fetch(view->screen->viewBackendCtx, selection, &len);
if (!text)
return False;
WMData *data = WMCreateDataWithBytesNoCopy(text, len, wfree);
(*callback)(view, selection, target, timestamp, cdata, data);
WMReleaseData(data);
return True;
}
/* Async path (X11): initiate conversion, callback fires on SelectionNotify */
if (!vb->selection_request ||
!vb->selection_request(view->screen->viewBackendCtx, view, selection, target, timestamp))
return False;
handler = wmalloc(sizeof(SelectionCallback));
handler->view = view;
handler->selection = selection;
handler->target = target;
handler->timestamp = timestamp;
handler->callback = callback;
handler->data = cdata;
if (selCallbacks == NULL) {
selCallbacks = WMCreateArrayWithDestructor(4, wfree);
}
WMAddToArray(selCallbacks, handler);
return True;
}