/* -- selection.c -- */ #include "x11vnc.h" #include "cleanup.h" #include "connections.h" #include "unixpw.h" /* * Selection/Cutbuffer/Clipboard handlers. */ int own_primary = 0; /* whether we currently own PRIMARY or not */ int set_primary = 1; int own_clipboard = 0; /* whether we currently own CLIPBOARD or not */ int set_clipboard = 1; int set_cutbuffer = 0; /* to avoid bouncing the CutText right back */ int sel_waittime = 15; /* some seconds to skip before first send */ Window selwin; /* special window for our selection */ Atom clipboard_atom = None; /* * This is where we keep our selection: the string sent TO us from VNC * clients, and the string sent BY us to requesting X11 clients. */ char *xcut_str_primary = NULL; char *xcut_str_clipboard = NULL; void selection_request(XEvent *ev, char *type); int check_sel_direction(char *dir, char *label, char *sel, int len); void cutbuffer_send(void); void selection_send(XEvent *ev); /* * Our callbacks instruct us to check for changes in the cutbuffer * and PRIMARY and CLIPBOARD selection on the local X11 display. * * TODO: check if malloc does not cause performance issues (esp. WRT * SelectionNotify handling). */ static char cutbuffer_str[PROP_MAX+1]; static char primary_str[PROP_MAX+1]; static char clipboard_str[PROP_MAX+1]; /* * An X11 (not VNC) client on the local display has requested the selection * from us (because we are the current owner). * * n.b.: our caller already has the X_LOCK. */ void selection_request(XEvent *ev, char *type) { XSelectionEvent notify_event; XSelectionRequestEvent *req_event; XErrorHandler old_handler; char *str; unsigned int length; unsigned char *data; #ifndef XA_LENGTH unsigned long XA_LENGTH = XInternAtom(dpy, "LENGTH", True); #endif req_event = &(ev->xselectionrequest); notify_event.type = SelectionNotify; notify_event.display = req_event->display; notify_event.requestor = req_event->requestor; notify_event.selection = req_event->selection; notify_event.target = req_event->target; notify_event.time = req_event->time; if (req_event->property == None) { notify_event.property = req_event->target; } else { notify_event.property = req_event->property; } if (!strcmp(type, "PRIMARY")) { str = xcut_str_primary; } else if (!strcmp(type, "CLIPBOARD")) { str = xcut_str_clipboard; } else { return; } if (str) { length = strlen(str); } else { length = 0; } if (debug_sel) { rfbLog("%s\trequest event: owner=0x%x requestor=0x%x sel=%03d targ=%d prop=%d\n", type, req_event->owner, req_event->requestor, req_event->selection, req_event->target, req_event->property); } /* the window may have gone away, so trap errors */ trapped_xerror = 0; old_handler = XSetErrorHandler(trap_xerror); if (ev->xselectionrequest.target == XA_LENGTH) { /* length request */ XChangeProperty(ev->xselectionrequest.display, ev->xselectionrequest.requestor, ev->xselectionrequest.property, ev->xselectionrequest.target, 32, PropModeReplace, (unsigned char *) &length, sizeof(unsigned int)); } else { /* data request */ data = (unsigned char *)str; XChangeProperty(ev->xselectionrequest.display, ev->xselectionrequest.requestor, ev->xselectionrequest.property, ev->xselectionrequest.target, 8, PropModeReplace, data, length); } if (! trapped_xerror) { XSendEvent(req_event->display, req_event->requestor, False, 0, (XEvent *)¬ify_event); } if (trapped_xerror) { rfbLog("selection_request: ignored XError while sending " "%s selection to 0x%x.\n", type, req_event->requestor); } XSetErrorHandler(old_handler); trapped_xerror = 0; XFlush(dpy); } int check_sel_direction(char *dir, char *label, char *sel, int len) { int db = 0, ok = 1; if (debug_sel) { db = 1; } if (sel_direction) { if (strstr(sel_direction, "debug")) { db = 1; } if (strcmp(sel_direction, "debug")) { if (strstr(sel_direction, dir) == NULL) { ok = 0; } } } if (db) { char str[40]; int n = 40; strncpy(str, sel, n); str[n-1] = '\0'; if (len < n) { str[len] = '\0'; } rfbLog("%s: '%s'\n", label, str); if (ok) { rfbLog("%s: %s-ing it.\n", label, dir); } else { rfbLog("%s: NOT %s-ing it.\n", label, dir); } } return ok; } /* * CUT_BUFFER0 property on the local display has changed, we read and * store it and send it out to any connected VNC clients. * * n.b.: our caller already has the X_LOCK. */ void cutbuffer_send(void) { Atom type; int format, slen, dlen, len; unsigned long nitems = 0, bytes_after = 0; unsigned char* data = NULL; cutbuffer_str[0] = '\0'; slen = 0; /* read the property value into cutbuffer_str: */ do { if (XGetWindowProperty(dpy, DefaultRootWindow(dpy), XA_CUT_BUFFER0, nitems/4, PROP_MAX/16, False, AnyPropertyType, &type, &format, &nitems, &bytes_after, &data) == Success) { dlen = nitems * (format/8); if (slen + dlen > PROP_MAX) { /* too big */ rfbLog("warning: truncating large CUT_BUFFER0" " selection > %d bytes.\n", PROP_MAX); XFree(data); break; } memcpy(cutbuffer_str+slen, data, dlen); slen += dlen; cutbuffer_str[slen] = '\0'; XFree(data); } } while (bytes_after > 0); cutbuffer_str[PROP_MAX] = '\0'; if (debug_sel) { rfbLog("cutbuffer_send: '%s'\n", cutbuffer_str); } if (! all_clients_initialized()) { rfbLog("cutbuffer_send: no send: uninitialized clients\n"); return; /* some clients initializing, cannot send */ } if (unixpw_in_progress) { return; } /* now send it to any connected VNC clients (rfbServerCutText) */ if (!screen) { return; } len = strlen(cutbuffer_str); if (check_sel_direction("send", "cutbuffer_send", cutbuffer_str, len)) { rfbSendServerCutText(screen, cutbuffer_str, len); } } /* * "callback" for our SelectionNotify polling. We try to determine if * the PRIMARY selection has changed (checking length and first CHKSZ bytes) * and if it has we store it and send it off to any connected VNC clients. * * n.b.: our caller already has the X_LOCK. * * TODO: if we were willing to use libXt, we could perhaps get selection * timestamps to speed up the checking... XtGetSelectionValue(). * * Also: XFIXES has XFixesSelectSelectionInput(). */ #define CHKSZ 32 void selection_send(XEvent *ev) { Atom type; int format, slen, dlen, oldlen, newlen, toobig = 0, len; static int err = 0, sent_one = 0; char before[CHKSZ], after[CHKSZ]; unsigned long nitems = 0, bytes_after = 0; unsigned char* data = NULL; char *selection_str; /* * remember info about our last value of PRIMARY (or CUT_BUFFER0) * so we can check for any changes below. */ if (ev->xselection.selection == XA_PRIMARY) { if (! watch_primary) { return; } selection_str = primary_str; if (debug_sel) { rfbLog("selection_send: event PRIMARY prop: %d requestor: 0x%x atom: %d\n", ev->xselection.property, ev->xselection.requestor, ev->xselection.selection); } } else if (clipboard_atom && ev->xselection.selection == clipboard_atom) { if (! watch_clipboard) { return; } selection_str = clipboard_str; if (debug_sel) { rfbLog("selection_send: event CLIPBOARD prop: %d requestor: 0x%x atom: %d\n", ev->xselection.property, ev->xselection.requestor, ev->xselection.selection); } } else { return; } oldlen = strlen(selection_str); strncpy(before, selection_str, CHKSZ); selection_str[0] = '\0'; slen = 0; /* read in the current value of PRIMARY or CLIPBOARD: */ do { if (XGetWindowProperty(dpy, ev->xselection.requestor, ev->xselection.property, nitems/4, PROP_MAX/16, True, AnyPropertyType, &type, &format, &nitems, &bytes_after, &data) == Success) { dlen = nitems * (format/8); if (slen + dlen > PROP_MAX) { /* too big */ toobig = 1; XFree(data); if (err) { /* cut down on messages */ break; } else { err = 5; } rfbLog("warning: truncating large PRIMARY" "/CLIPBOARD selection > %d bytes.\n", PROP_MAX); break; } if (debug_sel) fprintf(stderr, "selection_send: data: '%s' dlen: %d nitems: %lu ba: %lu\n", data, dlen, nitems, bytes_after); memcpy(selection_str+slen, data, dlen); slen += dlen; selection_str[slen] = '\0'; XFree(data); } } while (bytes_after > 0); if (! toobig) { err = 0; } else if (err) { err--; } if (! sent_one) { /* try to force a send first time in */ oldlen = -1; sent_one = 1; } if (debug_sel) { rfbLog("selection_send: %s '%s'\n", ev->xselection.selection == XA_PRIMARY ? "PRIMARY " : "CLIPBOARD", selection_str); } /* look for changes in the new value */ newlen = strlen(selection_str); strncpy(after, selection_str, CHKSZ); if (oldlen == newlen && strncmp(before, after, CHKSZ) == 0) { /* evidently no change */ if (debug_sel) { rfbLog("selection_send: no change.\n"); } return; } if (newlen == 0) { /* do not bother sending a null string out */ return; } if (! all_clients_initialized()) { rfbLog("selection_send: no send: uninitialized clients\n"); return; /* some clients initializing, cannot send */ } if (unixpw_in_progress) { return; } /* now send it to any connected VNC clients (rfbServerCutText) */ if (!screen) { return; } len = newlen; if (check_sel_direction("send", "selection_send", selection_str, len)) { rfbSendServerCutText(screen, selection_str, len); } }