#include #include #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; }