diff options
-rw-r--r-- | src/xselect.c | 1949 |
1 files changed, 1949 insertions, 0 deletions
diff --git a/src/xselect.c b/src/xselect.c new file mode 100644 index 00000000000..c5b56e5b548 --- /dev/null +++ b/src/xselect.c @@ -0,0 +1,1949 @@ +/* x_handle_selection_notify +x_reply_selection_request +XFree +x_selection_timeout initial value */ + +/* X Selection processing for emacs + Copyright (C) 1990-1993 Free Software Foundation. + +This file is part of GNU Emacs. + +GNU Emacs is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Rewritten by jwz */ + +#include "config.h" +#include "lisp.h" +#include "xterm.h" /* for all of the X includes */ +#include "dispextern.h" /* screen.h seems to want this */ +#include "screen.h" /* Need this to get the X window of selected_screen */ + +#define CUT_BUFFER_SUPPORT + +static Atom Xatom_CLIPBOARD, Xatom_TIMESTAMP, Xatom_TEXT, Xatom_DELETE, + Xatom_MULTIPLE, Xatom_INCR, Xatom_EMACS_TMP, Xatom_TARGETS, Xatom_NULL, + Xatom_ATOM_PAIR; + +Lisp_Object QPRIMARY, QSECONDARY, QSTRING, QINTEGER, QCLIPBOARD, QTIMESTAMP, + QTEXT, QDELETE, QMULTIPLE, QINCR, QEMACS_TMP, QTARGETS, QATOM, QNULL, + QATOM_PAIR; + +#ifdef CUT_BUFFER_SUPPORT +Lisp_Object QCUT_BUFFER0, QCUT_BUFFER1, QCUT_BUFFER2, QCUT_BUFFER3, + QCUT_BUFFER4, QCUT_BUFFER5, QCUT_BUFFER6, QCUT_BUFFER7; +#endif + +Lisp_Object Vx_lost_selection_hooks; +Lisp_Object Vx_sent_selection_hooks; + +/* If this is a smaller number than the max-request-size of the display, + emacs will use INCR selection transfer when the selection is larger + than this. The max-request-size is usually around 64k, so if you want + emacs to use incremental selection transfers when the selection is + smaller than that, set this. I added this mostly for debugging the + incremental transfer stuff, but it might improve server performance. + */ +#define MAX_SELECTION_QUANTUM 0xFFFFFF + +#define SELECTION_QUANTUM(dpy) ((XMaxRequestSize (dpy) << 2) - 100) + + +/* The time of the last-read mouse or keyboard event. + For selection purposes, we use this as a sleazy way of knowing what the + current time is in server-time. This assumes that the most recently read + mouse or keyboard event has something to do with the assertion of the + selection, which is probably true. + */ +extern Time mouse_timestamp; + + +/* This is an association list whose elements are of the form + ( selection-name selection-value selection-timestamp ) + selection-name is a lisp symbol, whose name is the name of an X Atom. + selection-value is the value that emacs owns for that selection. + It may be any kind of Lisp object. + selection-timestamp is the time at which emacs began owning this selection, + as a cons of two 16-bit numbers (making a 32 bit time.) + If there is an entry in this alist, then it can be assumed that emacs owns + that selection. + The only (eq) parts of this list that are visible from Lisp are the + selection-values. + */ +Lisp_Object Vselection_alist; + +/* This is an alist whose CARs are selection-types (whose names are the same + as the names of X Atoms) and whose CDRs are the names of Lisp functions to + call to convert the given Emacs selection value to a string representing + the given selection type. This is for Lisp-level extension of the emacs + selection handling. + */ +Lisp_Object Vselection_converter_alist; + +/* If the selection owner takes too long to reply to a selection request, + we give up on it. This is in seconds (0 = no timeout.) + */ +int x_selection_timeout; + + +/* Utility functions */ + +static void lisp_data_to_selection_data (); +static Lisp_Object selection_data_to_lisp_data (); +static Lisp_Object x_get_window_property_as_lisp_data (); + +static int expect_property_change (); +static void wait_for_property_change (); +static void unexpect_property_change (); +static int waiting_for_other_props_on_window (); + +/* This converts a Lisp symbol to a server Atom, avoiding a server + roundtrip whenever possible. */ + +static Atom +symbol_to_x_atom (display, sym) + Display *display; + Lisp_Object sym; +{ + Atom val; + if (NILP (sym)) return 0; + if (EQ (sym, QPRIMARY)) return XA_PRIMARY; + if (EQ (sym, QSECONDARY)) return XA_SECONDARY; + if (EQ (sym, QSTRING)) return XA_STRING; + if (EQ (sym, QINTEGER)) return XA_INTEGER; + if (EQ (sym, QATOM)) return XA_ATOM; + if (EQ (sym, QCLIPBOARD)) return Xatom_CLIPBOARD; + if (EQ (sym, QTIMESTAMP)) return Xatom_TIMESTAMP; + if (EQ (sym, QTEXT)) return Xatom_TEXT; + if (EQ (sym, QDELETE)) return Xatom_DELETE; + if (EQ (sym, QMULTIPLE)) return Xatom_MULTIPLE; + if (EQ (sym, QINCR)) return Xatom_INCR; + if (EQ (sym, QEMACS_TMP)) return Xatom_EMACS_TMP; + if (EQ (sym, QTARGETS)) return Xatom_TARGETS; + if (EQ (sym, QNULL)) return Xatom_NULL; +#ifdef CUT_BUFFER_SUPPORT + if (EQ (sym, QCUT_BUFFER0)) return XA_CUT_BUFFER0; + if (EQ (sym, QCUT_BUFFER1)) return XA_CUT_BUFFER1; + if (EQ (sym, QCUT_BUFFER2)) return XA_CUT_BUFFER2; + if (EQ (sym, QCUT_BUFFER3)) return XA_CUT_BUFFER3; + if (EQ (sym, QCUT_BUFFER4)) return XA_CUT_BUFFER4; + if (EQ (sym, QCUT_BUFFER5)) return XA_CUT_BUFFER5; + if (EQ (sym, QCUT_BUFFER6)) return XA_CUT_BUFFER6; + if (EQ (sym, QCUT_BUFFER7)) return XA_CUT_BUFFER7; +#endif + if (!SYMBOLP (sym)) abort (); + +#if 0 + fprintf (stderr, " XInternAtom %s\n", (char *) XSYMBOL (sym)->name->data); +#endif + BLOCK_INPUT; + val = XInternAtom (display, (char *) XSYMBOL (sym)->name->data, False); + UNBLOCK_INPUT; + return val; +} + + +/* This converts a server Atom to a Lisp symbol, avoiding server roundtrips + and calls to intern whenever possible. */ + +static Lisp_Object +x_atom_to_symbol (display, atom) + Display *display; + Atom atom; +{ + char *str; + Lisp_Object val; + if (! atom) return Qnil; + case XA_PRIMARY: + return QPRIMARY; + case XA_SECONDARY: + return QSECONDARY; + case XA_STRING: + return QSTRING; + case XA_INTEGER: + return QINTEGER; + case XA_ATOM: + return QATOM; + case Xatom_CLIPBOARD: + return QCLIPBOARD; + case Xatom_TIMESTAMP: + return QTIMESTAMP; + case Xatom_TEXT: + return QTEXT; + case Xatom_DELETE: + return QDELETE; + case Xatom_MULTIPLE: + return QMULTIPLE; + case Xatom_INCR: + return QINCR; + case Xatom_EMACS_TMP: + return QEMACS_TMP; + case Xatom_TARGETS: + return QTARGETS; + case Xatom_NULL: + return QNULL; +#ifdef CUT_BUFFER_SUPPORT + case XA_CUT_BUFFER0: + return QCUT_BUFFER0; + case XA_CUT_BUFFER1: + return QCUT_BUFFER1; + case XA_CUT_BUFFER2: + return QCUT_BUFFER2; + case XA_CUT_BUFFER3: + return QCUT_BUFFER3; + case XA_CUT_BUFFER4: + return QCUT_BUFFER4; + case XA_CUT_BUFFER5: + return QCUT_BUFFER5; + case XA_CUT_BUFFER6: + return QCUT_BUFFER6; + case XA_CUT_BUFFER7: + return QCUT_BUFFER7; +#endif + + BLOCK_INPUT; + str = XGetAtomName (display, atom); + UNBLOCK_INPUT; +#if 0 + fprintf (stderr, " XGetAtomName --> %s\n", str); +#endif + if (! str) return Qnil; + val = intern (str); + BLOCK_INPUT; + XFree (str); + UNBLOCK_INPUT; + return val; +} + + +static Lisp_Object +long_to_cons (i) + unsigned long i; +{ + unsigned int top = i >> 16; + unsigned int bot = i & 0xFFFF; + if (top == 0) return make_number (bot); + if (top == 0xFFFF) return Fcons (make_number (-1), make_number (bot)); + return Fcons (make_number (top), make_number (bot)); +} + +static unsigned long +cons_to_long (c) + Lisp_Object c; +{ + int top, bot; + if (FIXNUMP (c)) return XINT (c); + top = XCONS (c)->car; + bot = XCONS (c)->cdr; + if (CONSP (bot)) bot = XCONS (bot)->car; + return ((XINT (top) << 16) | XINT (bot)); +} + + + +/* Do protocol to assert ourself as a selection owner. + Update the Vselection_alist so that we can reply to later requests for + our selection. */ + +static void +x_own_selection (selection_name, selection_value) + Lisp_Object selection_name, selection_value; +{ + Display *display = x_current_display; +#ifdef X_TOOLKIT + Window selecting_window = XtWindow (selected_screen->display.x->edit_widget); +#else + Window selecting_window = FRAME_X_WINDOW (selected_frame); +#endif + Time time = mouse_timestamp; + Atom selection_atom; + + CHECK_SYMBOL (selection_name, 0); + selection_atom = symbol_to_x_atom (display, selection_name); + + BLOCK_INPUT; + XSetSelectionOwner (display, selection_atom, selecting_window, time); + UNBLOCK_INPUT; + + /* Now update the local cache */ + { + Lisp_Object selection_time; + Lisp_Object selection_data; + Lisp_Object prev_value; + + selection_time = long_to_cons ((unsigned long) time); + selection_data = Fcons (selection_name, + Fcons (selection_value, + Fcons (selection_time, Qnil))); + prev_value = assq_no_quit (selection_name, Vselection_alist); + + Vselection_alist = Fcons (selection_data, Vselection_alist); + + /* If we already owned the selection, remove the old selection data. + Perhaps we should destructively modify it instead. + Don't use Fdelq as that may QUIT. */ + if (!NILP (prev_value)) + { + Lisp_Object rest; /* we know it's not the CAR, so it's easy. */ + for (rest = Vselection_alist; !NILP (rest); rest = Fcdr (rest)) + if (EQ (prev_value, Fcar (XCONS (rest)->cdr))) + { + XCONS (rest)->cdr = Fcdr (XCONS (rest)->cdr); + break; + } + } + } +} + +/* Given a selection-name and desired type, look up our local copy of + the selection value and convert it to the type. + The value is nil or a string. + This function is used both for remote requests + and for local x-get-selection-internal. */ + + This calls random Lisp code, and may signal or gc. */ + +static Lisp_Object +x_get_local_selection (selection_symbol, target_type) + Lisp_Object selection_symbol, target_type; +{ + Lisp_Object local_value; + Lisp_Object handler_fn, value, type, check; + int count; + + local_value = assq_no_quit (selection_symbol, Vselection_alist); + + if (NILP (local_value)) return Qnil; + + /* TIMESTAMP and MULTIPLE are special cases 'cause that's easiest. */ + if (EQ (target_type, QTIMESTAMP)) + { + handler_fn = Qnil; + value = XCONS (XCONS (XCONS (local_value)->cdr)->cdr)->car; + } +#if 0 + else if (EQ (target_type, QDELETE)) + { + handler_fn = Qnil; + Fx_disown_selection_internal + (selection_symbol, + XCONS (XCONS (XCONS (local_value)->cdr)->cdr)->car); + value = QNULL; + } +#endif + +#if 0 /* #### MULTIPLE doesn't work yet */ + else if (CONSP (target_type) + && XCONS (target_type)->car == QMULTIPLE) + { + Lisp_Object pairs = XCONS (target_type)->cdr; + int size = XVECTOR (pairs)->size; + int i; + /* If the target is MULTIPLE, then target_type looks like + (MULTIPLE . [[SELECTION1 TARGET1] [SELECTION2 TARGET2] ... ]) + We modify the second element of each pair in the vector and + return it as [[SELECTION1 <value1>] [SELECTION2 <value2>] ... ] + */ + for (i = 0; i < size; i++) + { + Lisp_Object pair = XVECTOR (pairs)->contents [i]; + XVECTOR (pair)->contents [1] + = x_get_local_selection (XVECTOR (pair)->contents [0], + XVECTOR (pair)->contents [1]); + } + return pairs; + } +#endif + else + { + /* Don't allow a quit within the converter. + When the user types C-g, he would be surprised + if by luck it came during a converter. */ + count = specpdl_ptr - specpdl; + specbind (Qinhibit_quit, Qt); + + CHECK_SYMBOL (target_type, 0); + handler_fn = Fcdr (Fassq (target_type, Vselection_converter_alist)); + if (NILP (handler_fn)) return Qnil; + value = call3 (handler_fn, + selection_symbol, target_type, + XCONS (XCONS (local_value)->cdr)->car); + unbind_to (count, Qnil); + } + + /* Make sure this value is of a type that we could transmit + to another X client. */ + check = value; + if (CONSP (value) + && SYMBOLP (XCONS (value)->car)) + type = XCONS (value)->car, + check = XCONS (value)->cdr; + + if (STRINGP (check) + || VECTORP (check) + || SYMBOLP (check) + || FIXNUMP (check) + || NILP (value)) + return value; + else if (CONSP (check) + && FIXNUMP (XCONS (check)->car) + && (FIXNUMP (XCONS (check)->cdr) + || + (CONSP (XCONS (check)->cdr) + && FIXNUMP (XCONS (XCONS (check)->cdr)->car) + && NILP (XCONS (XCONS (check)->cdr)->cdr)))) + return value; + else + return + Fsignal (Qerror, + Fcons (build_string ("unrecognised selection-conversion type"), + Fcons (handler_fn, Fcons (value, Qnil)))); +} + +/* Subroutines of x_reply_selection_request. */ + +/* Send a SelectionNotify event to the requestor with property=None, + meaning we were unable to do what they wanted. */ + +static void +x_decline_selection_request (event) + struct input_event *event; +{ + XSelectionEvent reply; + reply.type = SelectionNotify; + reply.display = SELECTION_EVENT_DISPLAY (event); + reply.requestor = SELECTION_EVENT_REQUESTOR (event); + reply.selection = SELECTION_EVENT_SELECTION (event); + reply.time = SELECTION_EVENT_TIME (event); + reply.target = SELECTION_EVENT_TARGET (event); + reply.property = None; + + BLOCK_INPUT; + (void) XSendEvent (reply.display, reply.requestor, False, 0L, + (XEvent *) &reply); + UNBLOCK_INPUT; +} + +/* This is the selection request currently being processed. + It is set to zero when the request is fully processed. */ +static struct input_event *x_selection_current_request; + +/* Used as an unwind-protect clause so that, if a selection-converter signals + an error, we tell the requestor that we were unable to do what they wanted + before we throw to top-level or go into the debugger or whatever. */ + +static Lisp_Object +x_selection_request_lisp_error (ignore) + Lisp_Object ignore; +{ + if (x_selection_current_request != 0) + x_decline_selection_request (x_selection_current_request); + return Qnil; +} + +/* Send the reply to a selection request event EVENT. + TYPE is the type of selection data requested. + DATA and SIZE describe the data to send, already converted. + FORMAT is the unit-size (in bits) of the data to be transmitted. */ + +static void +x_reply_selection_request (event, format, data, size, type) + struct input_event *event; + int format, size; + unsigned char *data; + Atom type; +{ + XSelectionEvent reply; + Display *display = SELECTION_EVENT_DISPLAY (event); + Window window = SELECTION_EVENT_REQUESTOR (event); + int bytes_remaining; + int format_bytes = format/8; + int max_bytes = SELECTION_QUANTUM (display); + + if (max_bytes > MAX_SELECTION_QUANTUM) + max_bytes = MAX_SELECTION_QUANTUM; + + reply.type = SelectionNotify; + reply.display = display; + reply.requestor = window; + reply.selection = SELECTION_EVENT_SELECTION (event); + reply.time = SELECTION_EVENT_TIME (event); + reply.target = SELECTION_EVENT_TARGET (event); + reply.property = SELECTION_EVENT_PROPERTY (event); + if (reply.property == None) + reply.property = reply.target; + + /* #### XChangeProperty can generate BadAlloc, and we must handle it! */ + + BLOCK_INPUT; + /* Store the data on the requested property. + If the selection is large, only store the first N bytes of it. + */ + bytes_remaining = size * format_bytes; + if (bytes_remaining <= max_bytes) + { + /* Send all the data at once, with minimal handshaking. */ +#if 0 + fprintf (stderr,"\nStoring all %d\n", bytes_remaining); +#endif + XChangeProperty (display, window, reply.property, type, format, + PropModeReplace, data, size); + /* At this point, the selection was successfully stored; ack it. */ + (void) XSendEvent (display, window, False, 0L, (XEvent *) &reply); + } + else + { + /* Send an INCR selection. */ + int prop_id; + + if (x_window_to_screen (window)) /* #### debug */ + error ("attempt to transfer an INCR to ourself!"); +#if 0 + fprintf (stderr, "\nINCR %d\n", bytes_remaining); +#endif + prop_id = expect_property_change (display, window, reply.property, + PropertyDelete); + + XChangeProperty (display, window, reply.property, Xatom_INCR, + 32, PropModeReplace, (unsigned char *) + &bytes_remaining, 1); + XSelectInput (display, window, PropertyChangeMask); + /* Tell 'em the INCR data is there... */ + (void) XSendEvent (display, window, False, 0L, (XEvent *) &reply); + + /* First, wait for the requestor to ack by deleting the property. + This can run random lisp code (process handlers) or signal. */ + wait_for_property_change (prop_id); + + while (bytes_remaining) + { + int i = ((bytes_remaining < max_bytes) + ? bytes_remaining + : max_bytes); + prop_id = expect_property_change (display, window, reply.property, + PropertyDelete); +#if 0 + fprintf (stderr," INCR adding %d\n", i); +#endif + /* Append the next chunk of data to the property. */ + XChangeProperty (display, window, reply.property, type, format, + PropModeAppend, data, i / format_bytes); + bytes_remaining -= i; + data += i; + + /* Now wait for the requestor to ack this chunk by deleting the + property. This can run random lisp code or signal. + */ + wait_for_property_change (prop_id); + } + /* Now write a zero-length chunk to the property to tell the requestor + that we're done. */ +#if 0 + fprintf (stderr," INCR done\n"); +#endif + if (! waiting_for_other_props_on_window (display, window)) + XSelectInput (display, window, 0L); + + XChangeProperty (display, window, reply.property, type, format, + PropModeReplace, data, 0); + } + UNBLOCK_INPUT; +} + +/* Handle a SelectionRequest event EVENT. + This is called from keyboard.c when such an event is found in the queue. */ + +void +x_handle_selection_request (event) + struct input_event *event; +{ + struct gcpro gcpro1, gcpro2, gcpro3; + XSelectionEvent reply; + Lisp_Object local_selection_data = Qnil; + Lisp_Object selection_symbol; + Lisp_Object target_symbol = Qnil; + Lisp_Object converted_selection = Qnil; + Time local_selection_time; + Lisp_Object successful_p = Qnil; + int count; + + GCPRO3 (local_selection_data, converted_selection, target_symbol); + + reply.type = SelectionNotify; /* Construct the reply event */ + reply.display = SELECTION_EVENT_DISPLAY (event); + reply.requestor = SELECTION_EVENT_REQUESTOR (event); + reply.selection = SELECTION_EVENT_SELECTION (event); + reply.time = SELECTION_EVENT_TIME (event); + reply.target = SELECTION_EVENT_TARGET (event); + reply.property = SELECTION_EVENT_PROPERTY (event); + if (reply.property == None) + reply.property = reply.target; + + selection_symbol = x_atom_to_symbol (reply.display, + SELECTION_EVENT_SELECTION (event)); + + local_selection_data = assq_no_quit (selection_symbol, Vselection_alist); + +#if 0 +# define CDR(x) (XCONS (x)->cdr) +# define CAR(x) (XCONS (x)->car) + /* This list isn't user-visible, so it can't "go bad." */ + if (!CONSP (local_selection_data)) abort (); + if (!CONSP (CDR (local_selection_data))) abort (); + if (!CONSP (CDR (CDR (local_selection_data)))) abort (); + if (!NILP (CDR (CDR (CDR (local_selection_data))))) abort (); + if (!CONSP (CAR (CDR (CDR (local_selection_data))))) abort (); + if (!FIXNUMP (CAR (CAR (CDR (CDR (local_selection_data)))))) abort (); + if (!FIXNUMP (CDR (CAR (CDR (CDR (local_selection_data)))))) abort (); +# undef CAR +# undef CDR +#endif + + if (NILP (local_selection_data)) + { + /* Someone asked for the selection, but we don't have it any more. + */ + x_decline_selection_request (event); + goto DONE; + } + + local_selection_time = (Time) + cons_to_long (XCONS (XCONS (XCONS (local_selection_data)->cdr)->cdr)->car); + + if (SELECTION_EVENT_TIME (event) != CurrentTime + && local_selection_time > event->time) + { + /* Someone asked for the selection, and we have one, but not the one + they're looking for. + */ + x_decline_selection_request (event); + goto DONE; + } + + count = specpdl_ptr - specpdl; + x_selection_current_request = event; + record_unwind_protect (x_selection_request_lisp_error, Qnil); + + target_symbol = x_atom_to_symbol (reply.display, + SELECTION_EVENT_TARGET (event)); + +#if 0 /* #### MULTIPLE doesn't work yet */ + if (EQ (target_symbol, QMULTIPLE)) + target_symbol = fetch_multiple_target (event); +#endif + + /* Convert lisp objects back into binary data */ + + converted_selection + = x_get_local_selection (selection_symbol, target_symbol); + + if (! NILP (converted_selection)) + { + unsigned char *data; + unsigned int size; + int format; + Atom type; + lisp_data_to_selection_data (reply.display, converted_selection, + &data, &type, &size, &format); + + x_reply_selection_request (event, format, data, size, type); + successful_p = Qt; + + /* Indicate we have successfully processed this event. */ + x_selection_current_event = 0; + + xfree (data); + } + unbind_to (count, Qnil); + + DONE: + + UNGCPRO; + + /* Let random lisp code notice that the selection has been asked for. */ + { + Lisp_Object rest = Vx_sent_selection_hooks; + if (!EQ (rest, Qunbound)) + for (; CONSP (rest); rest = Fcdr (rest)) + call3 (Fcar (rest), selection_symbol, target_symbol, successful_p); + } +} + +/* Handle a SelectionClear event EVENT, which indicates that some other + client cleared out our previously asserted selection. + This is called from keyboard.c when such an event is found in the queue. */ + +void +x_handle_selection_clear (event) + struct input_event *event; +{ + Display *display = SELECTION_EVENT_DISPLAY (event); + Atom selection = SELECTION_EVENT_SELECTION (event); + Time changed_owner_time = SELECTION_EVENT_TIME (event); + + Lisp_Object selection_symbol, local_selection_data; + Time local_selection_time; + + selection_symbol = x_atom_to_symbol (display, selection); + + local_selection_data = assq_no_quit (selection_symbol, Vselection_alist); + + /* Well, we already believe that we don't own it, so that's just fine. */ + if (NILP (local_selection_data)) return; + + local_selection_time = (Time) + cons_to_long (XCONS (XCONS (XCONS (local_selection_data)->cdr)->cdr)->car); + + /* This SelectionClear is for a selection that we no longer own, so we can + disregard it. (That is, we have reasserted the selection since this + request was generated.) */ + + if (changed_owner_time != CurrentTime + && local_selection_time > changed_owner_time) + return; + + /* Otherwise, we're really honest and truly being told to drop it. + Don't use Fdelq as that may QUIT;. */ + + if (EQ (local_selection_data, Fcar (Vselection_alist))) + Vselection_alist = Fcdr (Vselection_alist); + else + { + Lisp_Object rest; + for (rest = Vselection_alist; !NILP (rest); rest = Fcdr (rest)) + if (EQ (local_selection_data, Fcar (XCONS (rest)->cdr))) + { + XCONS (rest)->cdr = Fcdr (XCONS (rest)->cdr); + break; + } + } + + /* Let random lisp code notice that the selection has been stolen. */ + + { + Lisp_Object rest = Vx_lost_selection_hooks; + if (!EQ (rest, Qunbound)) + for (; CONSP (rest); rest = Fcdr (rest)) + call1 (Fcar (rest), selection_symbol); + } +} + + +/* This stuff is so that INCR selections are reentrant (that is, so we can + be servicing multiple INCR selection requests simultaneously.) I haven't + actually tested that yet. */ + +static int prop_location_tick; + +static Lisp_Object property_change_reply; +static int property_change_reply_tick; + +/* Keep a list of the property changes that are awaited. */ + +struct prop_location +{ + int tick; + Display *display; + Window window; + Atom property; + int desired_state; + struct prop_location *next; +}; + +static struct prop_location *property_change_wait_list; + +static int +property_deleted_p (tick) + void *tick; +{ + struct prop_location *rest = property_change_wait_list; + while (rest) + if (rest->tick == (int) tick) + return 0; + else + rest = rest->next; + return 1; +} + +/* Nonzero if any properties for DISPLAY and WINDOW + are on the list of what we are waiting for. */ + +static int +waiting_for_other_props_on_window (display, window) + Display *display; + Window window; +{ + struct prop_location *rest = property_change_wait_list; + while (rest) + if (rest->display == display && rest->window == window) + return 1; + else + rest = rest->next; + return 0; +} + +/* Add an entry to the list of property changes we are waiting for. + DISPLAY, WINDOW, PROPERTY, STATE describe what we will wait for. + The return value is a number that uniquely identifies + this awaited property change. */ + +static int +expect_property_change (display, window, property, state) + Display *display; + Window window; + Lisp_Object property; + int state; +{ + struct prop_location *pl + = (struct prop_location *) xmalloc (sizeof (struct prop_location)); + pl->tick = ++prop_location_tick; + pl->display = display; + pl->window = window; + pl->property = property; + pl->desired_state = state; + pl->next = property_change_wait_list; + property_change_wait_list = pl; + return pl->tick; +} + +/* Delete an entry from the list of property changes we are waiting for. + TICK is the number that uniquely identifies the entry. */ + +static void +unexpect_property_change (tick) + int tick; +{ + struct prop_location *prev = 0, *rest = property_change_wait_list; + while (rest) + { + if (rest->tick == tick) + { + if (prev) + prev->next = rest->next; + else + property_change_wait_list = rest->next; + xfree (rest); + return; + } + prev = rest; + rest = rest->next; + } +} + +/* Actually wait for a property change. + TICK should be the value that expect_property_change returned. */ + +static void +wait_for_property_change (tick) +{ + XCONS (property_change_reply)->car = Qnil; + property_change_reply_tick = tick; + wait_reading_process_input (0, 0, property_change_reply, 0); +} + +/* Called from XTread_socket in response to a PropertyNotify event. */ + +void +x_handle_property_notify (event) + XPropertyEvent *event; +{ + struct prop_location *prev = 0, *rest = property_change_wait_list; + while (rest) + { + if (rest->property == event->atom + && rest->window == event->window + && rest->display == event->display + && rest->desired_state == event->state) + { +#if 0 + fprintf (stderr, "Saw expected prop-%s on %s\n", + (event->state == PropertyDelete ? "delete" : "change"), + (char *) XSYMBOL (x_atom_to_symbol (event->display, + event->atom)) + ->name->data); +#endif + + /* If this is the one wait_for_property_change is waiting for, + tell it to wake up. */ + if (rest->tick == property_change_reply_tick) + XCONS (property_change_reply)->car = Qt; + + if (prev) + prev->next = rest->next; + else + property_change_wait_list = rest->next; + xfree (rest); + return; + } + prev = rest; + rest = rest->next; + } +#if 0 + fprintf (stderr, "Saw UNexpected prop-%s on %s\n", + (event->state == PropertyDelete ? "delete" : "change"), + (char *) XSYMBOL (x_atom_to_symbol (event->display, event->atom)) + ->name->data); +#endif +} + + + +#if 0 /* #### MULTIPLE doesn't work yet */ + +static Lisp_Object +fetch_multiple_target (event) + XSelectionRequestEvent *event; +{ + Display *display = event->display; + Window window = event->requestor; + Atom target = event->target; + Atom selection_atom = event->selection; + int result; + + return + Fcons (QMULTIPLE, + x_get_window_property_as_lisp_data (display, window, target, + QMULTIPLE, selection_atom)); +} + +static Lisp_Object +copy_multiple_data (obj) + Lisp_Object obj; +{ + Lisp_Object vec; + int i; + int size; + if (CONSP (obj)) + return Fcons (XCONS (obj)->car, copy_multiple_data (XCONS (obj)->cdr)); + + CHECK_VECTOR (obj, 0); + vec = Fmake_vector (size = XVECTOR (obj)->size, Qnil); + for (i = 0; i < size; i++) + { + Lisp_Object vec2 = XVECTOR (obj)->contents [i]; + CHECK_VECTOR (vec2, 0); + if (XVECTOR (vec2)->size != 2) + /* ??? Confusing error message */ + Fsignal (Qerror, Fcons (build_string ("vectors must be of length 2"), + Fcons (vec2, Qnil))); + XVECTOR (vec)->contents [i] = Fmake_vector (2, Qnil); + XVECTOR (XVECTOR (vec)->contents [i])->contents [0] + = XVECTOR (vec2)->contents [0]; + XVECTOR (XVECTOR (vec)->contents [i])->contents [1] + = XVECTOR (vec2)->contents [1]; + } + return vec; +} + +#endif + + +/* Variables for communication with x_handle_selection_notify. */ +static Atom reading_which_selection; +static Lisp_Object reading_selection_reply; +static Window reading_selection_window; + +/* Do protocol to read selection-data from the server. + Converts this to Lisp data and returns it. */ + +static Lisp_Object +x_get_foreign_selection (selection_symbol, target_type) + Lisp_Object selection_symbol, target_type; +{ + Display *display = x_current_display; +#ifdef X_TOOLKIT + Window selecting_window = XtWindow (selected_screen->display.x->edit_widget); +#else + Window selecting_window = FRAME_X_WINDOW (selected_frame); +#endif + Time requestor_time = mouse_timestamp; + Atom target_property = Xatom_EMACS_TMP; + Atom selection_atom = symbol_to_x_atom (display, selection_symbol); + Atom type_atom; + + if (CONSP (target_type)) + type_atom = symbol_to_x_atom (display, XCONS (target_type)->car); + else + type_atom = symbol_to_x_atom (display, target_type); + + BLOCK_INPUT; + XConvertSelection (display, selection_atom, type_atom, target_property, + requestor_window, requestor_time); + + /* Prepare to block until the reply has been read. */ + reading_selection_window = requestor_window; + reading_which_selection = selection_atom; + XCONS (reading_selection_reply)->car = Qnil; + UNBLOCK_INPUT; + + /* This allows quits. */ + wait_reading_process_input (x_selection_timeout, 0, + reading_selection_reply, 0); + + if (NILP (XCONS (reading_selection_reply)->car)) + error ("timed out waiting for reply from selection owner"); + + /* Otherwise, the selection is waiting for us on the requested property. */ + return + x_get_window_property_as_lisp_data (display, requestor_window, + target_property, target_type, + selection_atom); +} + +/* Subroutines of x_get_window_property_as_lisp_data */ + +static void +x_get_window_property (display, window, property, data_ret, bytes_ret, + actual_type_ret, actual_format_ret, actual_size_ret, + delete_p) + Display *display; + Window window; + Atom property; + unsigned char **data_ret; + int *bytes_ret; + Atom *actual_type_ret; + int *actual_format_ret; + unsigned long *actual_size_ret; + int delete_p; +{ + int total_size; + unsigned long bytes_remaining; + int offset = 0; + unsigned char *tmp_data = 0; + int result; + int buffer_size = SELECTION_QUANTUM (display); + if (buffer_size > MAX_SELECTION_QUANTUM) buffer_size = MAX_SELECTION_QUANTUM; + + BLOCK_INPUT; + /* First probe the thing to find out how big it is. */ + result = XGetWindowProperty (display, window, property, + 0, 0, False, AnyPropertyType, + actual_type_ret, actual_format_ret, + actual_size_ret, + &bytes_remaining, &tmp_data); + UNBLOCK_INPUT; + if (result != Success) + { + *data_ret = 0; + *bytes_ret = 0; + return; + } + BLOCK_INPUT; + XFree ((char *) tmp_data); + UNBLOCK_INPUT; + + if (*actual_type_ret == None || *actual_format_ret == 0) + { + if (delete_p) XDeleteProperty (display, window, property); + return; + } + + total_size = bytes_remaining + 1; + *data_ret = (unsigned char *) xmalloc (total_size); + + /* Now read, until weve gotten it all. */ + BLOCK_INPUT; + while (bytes_remaining) + { +#if 0 + int last = bytes_remaining; +#endif + result + = XGetWindowProperty (display, window, property, + offset/4, buffer_size/4, + (delete_p ? True : False), + AnyPropertyType, + actual_type_ret, actual_format_ret, + actual_size_ret, &bytes_remaining, &tmp_data); +#if 0 + fprintf (stderr, "<< read %d\n", last-bytes_remaining); +#endif + /* If this doesn't return Success at this point, it means that + some clod deleted the selection while we were in the midst of + reading it. Deal with that, I guess.... + */ + if (result != Success) break; + *actual_size_ret *= *actual_format_ret / 8; + bcopy (tmp_data, (*data_ret) + offset, *actual_size_ret); + offset += *actual_size_ret; + XFree ((char *) tmp_data); + } + UNBLOCK_INPUT; + *bytes_ret = offset; +} + +static void +receive_incremental_selection (display, window, property, target_type, + min_size_bytes, data_ret, size_bytes_ret, + type_ret, format_ret, size_ret) + Display *display; + Window window; + Atom property; + Lisp_Object target_type; /* for error messages only */ + unsigned int min_size_bytes; + unsigned char **data_ret; + int *size_bytes_ret; + Atom *type_ret; + unsigned long *size_ret; + int *format_ret; +{ + int offset = 0; + int prop_id; + *size_bytes_ret = min_size_bytes; + *data_ret = (unsigned char *) xmalloc (*size_bytes_ret); +#if 0 + fprintf (stderr, "\nread INCR %d\n", min_size_bytes); +#endif + /* At this point, we have read an INCR property, and deleted it (which + is how we ack its receipt: the sending window will be selecting + PropertyNotify events on our window to notice this.) + + Now, we must loop, waiting for the sending window to put a value on + that property, then reading the property, then deleting it to ack. + We are done when the sender places a property of length 0. + */ + prop_id = expect_property_change (display, window, property, + PropertyNewValue); + while (1) + { + unsigned char *tmp_data; + int tmp_size_bytes; + wait_for_property_change (prop_id); + /* expect it again immediately, because x_get_window_property may + .. no it wont, I dont get it. + .. Ok, I get it now, the Xt code that implements INCR is broken. + */ + prop_id = expect_property_change (display, window, property, + PropertyNewValue); + x_get_window_property (display, window, property, + &tmp_data, &tmp_size_bytes, + type_ret, format_ret, size_ret, 1); + + if (tmp_size_bytes == 0) /* we're done */ + { +#if 0 + fprintf (stderr, " read INCR done\n"); +#endif + unexpect_property_change (prop_id); + if (tmp_data) xfree (tmp_data); + break; + } +#if 0 + fprintf (stderr, " read INCR %d\n", tmp_size_bytes); +#endif + if (*size_bytes_ret < offset + tmp_size_bytes) + { +#if 0 + fprintf (stderr, " read INCR realloc %d -> %d\n", + *size_bytes_ret, offset + tmp_size_bytes); +#endif + *size_bytes_ret = offset + tmp_size_bytes; + *data_ret = (unsigned char *) xrealloc (*data_ret, *size_bytes_ret); + } + memcpy ((*data_ret) + offset, tmp_data, tmp_size_bytes); + offset += tmp_size_bytes; + xfree (tmp_data); + } +} + +/* Once a requested selection is "ready" (we got a SelectionNotify event), + fetch value from property PROPERTY of X window WINDOW on display DISPLAY. + TARGET_TYPE and SELECTION_ATOM are used in error message if this fails. */ + +static Lisp_Object +x_get_window_property_as_lisp_data (display, window, property, target_type, + selection_atom) + Display *display; + Window window; + Atom property; + Lisp_Object target_type; /* for error messages only */ + Atom selection_atom; /* for error messages only */ +{ + Atom actual_type; + int actual_format; + unsigned long actual_size; + unsigned char *data = 0; + int bytes = 0; + Lisp_Object val; + + x_get_window_property (display, window, property, &data, &bytes, + &actual_type, &actual_format, &actual_size, 1); + if (! data) + { + int there_is_a_selection_owner; + BLOCK_INPUT; + there_is_a_selection_owner + = XGetSelectionOwner (display, selection_atom); + UNBLOCK_INPUT; + while (1) /* Note debugger can no longer return, so this is obsolete */ + Fsignal (Qerror, + there_is_a_selection_owner ? + Fcons (build_string ("selection owner couldn't convert"), + actual_type + ? Fcons (target_type, + Fcons (x_atom_to_symbol (display, actual_type), + Qnil)) + : Fcons (target_type, Qnil)) + : Fcons (build_string ("no selection"), + Fcons (x_atom_to_symbol (display, selection_atom), + Qnil))); + } + + if (actual_type == Xatom_INCR) + { + /* That wasn't really the data, just the beginning. */ + + unsigned int min_size_bytes = * ((unsigned int *) data); + BLOCK_INPUT; + XFree ((char *) data); + UNBLOCK_INPUT; + receive_incremental_selection (display, window, property, target_type, + min_size_bytes, &data, &bytes, + &actual_type, &actual_format, + &actual_size); + } + + /* It's been read. Now convert it to a lisp object in some semi-rational + manner. */ + val = selection_data_to_lisp_data (display, data, bytes, + actual_type, actual_format); + + xfree ((char *) data); + return val; +} + +/* These functions convert from the selection data read from the server into + something that we can use from Lisp, and vice versa. + + Type: Format: Size: Lisp Type: + ----- ------- ----- ----------- + * 8 * String + ATOM 32 1 Symbol + ATOM 32 > 1 Vector of Symbols + * 16 1 Integer + * 16 > 1 Vector of Integers + * 32 1 if <=16 bits: Integer + if > 16 bits: Cons of top16, bot16 + * 32 > 1 Vector of the above + + When converting a Lisp number to C, it is assumed to be of format 16 if + it is an integer, and of format 32 if it is a cons of two integers. + + When converting a vector of numbers from Lisp to C, it is assumed to be + of format 16 if every element in the vector is an integer, and is assumed + to be of format 32 if any element is a cons of two integers. + + When converting an object to C, it may be of the form (SYMBOL . <data>) + where SYMBOL is what we should claim that the type is. Format and + representation are as above. */ + + + +static Lisp_Object +selection_data_to_lisp_data (display, data, size, type, format) + Display *display; + unsigned char *data; + Atom type; + int size, format; +{ + + if (type == Xatom_NULL) + return QNULL; + + /* Convert any 8-bit data to a string, for compactness. */ + else if (format == 8) + return make_string ((char *) data, size); + + /* Convert a single atom to a Lisp_Symbol. Convert a set of atoms to + a vector of symbols. + */ + else if (type == XA_ATOM) + { + int i; + if (size == sizeof (Atom)) + return x_atom_to_symbol (display, *((Atom *) data)); + else + { + Lisp_Object v = Fmake_vector (size / sizeof (Atom), 0); + for (i = 0; i < size / sizeof (Atom); i++) + Faset (v, i, x_atom_to_symbol (display, ((Atom *) data) [i])); + return v; + } + } + + /* Convert a single 16 or small 32 bit number to a Lisp_Int. + If the number is > 16 bits, convert it to a cons of integers, + 16 bits in each half. + */ + else if (format == 32 && size == sizeof (long)) + return long_to_cons (((unsigned long *) data) [0]); + else if (format == 16 && size == sizeof (short)) + return make_number ((int) (((unsigned short *) data) [0])); + + /* Convert any other kind of data to a vector of numbers, represented + as above (as an integer, or a cons of two 16 bit integers.) + */ + else if (format == 16) + { + int i; + Lisp_Object v = Fmake_vector (size / 4, 0); + for (i = 0; i < size / 4; i++) + { + int j = (int) ((unsigned short *) data) [i]; + Faset (v, i, make_number (j)); + } + return v; + } + else + { + int i; + Lisp_Object v = Fmake_vector (size / 4, 0); + for (i = 0; i < size / 4; i++) + { + unsigned long j = ((unsigned long *) data) [i]; + Faset (v, i, long_to_cons (j)); + } + return v; + } +} + + +static void +lisp_data_to_selection_data (display, obj, + data_ret, type_ret, size_ret, format_ret) + Display *display; + Lisp_Object obj; + unsigned char **data_ret; + Atom *type_ret; + unsigned int *size_ret; + int *format_ret; +{ + Lisp_Object type = Qnil; + if (CONSP (obj) && SYMBOLP (XCONS (obj)->car)) + { + type = XCONS (obj)->car; + obj = XCONS (obj)->cdr; + if (CONSP (obj) && NILP (XCONS (obj)->cdr)) + obj = XCONS (obj)->car; + } + + if (EQ (obj, QNULL) || (EQ (type, QNULL))) + { /* This is not the same as declining */ + *format_ret = 32; + *size_ret = 0; + *data_ret = 0; + type = QNULL; + } + else if (STRINGP (obj)) + { + *format_ret = 8; + *size_ret = XSTRING (obj)->size; + *data_ret = (unsigned char *) xmalloc (*size_ret); + memcpy (*data_ret, (char *) XSTRING (obj)->data, *size_ret); + if (NILP (type)) type = QSTRING; + } + else if (SYMBOLP (obj)) + { + *format_ret = 32; + *size_ret = 1; + *data_ret = (unsigned char *) xmalloc (sizeof (Atom) + 1); + (*data_ret) [sizeof (Atom)] = 0; + (*(Atom **) data_ret) [0] = symbol_to_x_atom (display, obj); + if (NILP (type)) type = QATOM; + } + else if (FIXNUMP (obj) + && XINT (obj) < 0xFFFF + && XINT (obj) > -0xFFFF) + { + *format_ret = 16; + *size_ret = 1; + *data_ret = (unsigned char *) xmalloc (sizeof (short) + 1); + (*data_ret) [sizeof (short)] = 0; + (*(short **) data_ret) [0] = (short) XINT (obj); + if (NILP (type)) type = QINTEGER; + } + else if (FIXNUMP (obj) || CONSP (obj)) + { + *format_ret = 32; + *size_ret = 1; + *data_ret = (unsigned char *) xmalloc (sizeof (long) + 1); + (*data_ret) [sizeof (long)] = 0; + (*(unsigned long **) data_ret) [0] = cons_to_long (obj); + if (NILP (type)) type = QINTEGER; + } + else if (VECTORP (obj)) + { + /* Lisp_Vectors may represent a set of ATOMs; + a set of 16 or 32 bit INTEGERs; + or a set of ATOM_PAIRs (represented as [[A1 A2] [A3 A4] ...] + */ + int i; + + if (SYMBOLP (XVECTOR (obj)->contents [0])) + /* This vector is an ATOM set */ + { + if (NILP (type)) type = QATOM; + *size_ret = XVECTOR (obj)->size; + *format_ret = 32; + *data_ret = (unsigned char *) xmalloc ((*size_ret) * sizeof (Atom)); + for (i = 0; i < *size_ret; i++) + if (SYMBOLP (XVECTOR (obj)->contents [i])) + (*(Atom **) data_ret) [i] + = symbol_to_x_atom (display, XVECTOR (obj)->contents [i]); + else + Fsignal (Qerror, /* Qselection_error */ + Fcons (build_string + ("all elements of selection vector must have same type"), + Fcons (obj, Qnil))); + } +#if 0 /* #### MULTIPLE doesn't work yet */ + else if (VECTORP (XVECTOR (obj)->contents [0])) + /* This vector is an ATOM_PAIR set */ + { + if (NILP (type)) type = QATOM_PAIR; + *size_ret = XVECTOR (obj)->size; + *format_ret = 32; + *data_ret = (unsigned char *) + xmalloc ((*size_ret) * sizeof (Atom) * 2); + for (i = 0; i < *size_ret; i++) + if (VECTORP (XVECTOR (obj)->contents [i])) + { + Lisp_Object pair = XVECTOR (obj)->contents [i]; + if (XVECTOR (pair)->size != 2) + Fsignal (Qerror, + Fcons (build_string + ("elements of the vector must be vectors of exactly two elements"), + Fcons (pair, Qnil))); + + (*(Atom **) data_ret) [i * 2] + = symbol_to_x_atom (display, XVECTOR (pair)->contents [0]); + (*(Atom **) data_ret) [(i * 2) + 1] + = symbol_to_x_atom (display, XVECTOR (pair)->contents [1]); + } + else + Fsignal (Qerror, + Fcons (build_string + ("all elements of the vector must be of the same type"), + Fcons (obj, Qnil))); + + } +#endif + else + /* This vector is an INTEGER set, or something like it */ + { + *size_ret = XVECTOR (obj)->size; + if (NILP (type)) type = QINTEGER; + *format_ret = 16; + for (i = 0; i < *size_ret; i++) + if (CONSP (XVECTOR (obj)->contents [i])) + *format_ret = 32; + else if (!FIXNUMP (XVECTOR (obj)->contents [i])) + Fsignal (Qerror, /* Qselection_error */ + Fcons (build_string + ("elements of selection vector must be integers or conses of integers"), + Fcons (obj, Qnil))); + + *data_ret = (unsigned char *) xmalloc (*size_ret * (*format_ret/8)); + for (i = 0; i < *size_ret; i++) + if (*format_ret == 32) + (*((unsigned long **) data_ret)) [i] + = cons_to_long (XVECTOR (obj)->contents [i]); + else + (*((unsigned short **) data_ret)) [i] + = (unsigned short) cons_to_long (XVECTOR (obj)->contents [i]); + } + } + else + Fsignal (Qerror, /* Qselection_error */ + Fcons (build_string ("unrecognised selection data"), + Fcons (obj, Qnil))); + + *type_ret = symbol_to_x_atom (display, type); +} + +static Lisp_Object +clean_local_selection_data (obj) + Lisp_Object obj; +{ + if (CONSP (obj) + && FIXNUMP (XCONS (obj)->car) + && CONSP (XCONS (obj)->cdr) + && FIXNUMP (XCONS (XCONS (obj)->cdr)->car) + && NILP (XCONS (XCONS (obj)->cdr)->cdr)) + obj = Fcons (XCONS (obj)->car, XCONS (obj)->cdr); + + if (CONSP (obj) + && FIXNUMP (XCONS (obj)->car) + && FIXNUMP (XCONS (obj)->cdr)) + { + if (XINT (XCONS (obj)->car) == 0) + return XCONS (obj)->cdr; + if (XINT (XCONS (obj)->car) == -1) + return make_number (- XINT (XCONS (obj)->cdr)); + } + if (VECTORP (obj)) + { + int i; + int size = XVECTOR (obj)->size; + Lisp_Object copy; + if (size == 1) + return clean_local_selection_data (XVECTOR (obj)->contents [0]); + copy = Fmake_vector (size, Qnil); + for (i = 0; i < size; i++) + XVECTOR (copy)->contents [i] + = clean_local_selection_data (XVECTOR (obj)->contents [i]); + return copy; + } + return obj; +} + +/* Called from XTread_socket to handle SelectionNotify events. + If it's the selection we are waiting for, stop waiting. */ + +void +x_handle_selection_notify (event) + XSelectionEvent *event; +{ + if (event->requestor != reading_selection_window) + return; + if (event->selection != reading_which_selection) + return; + + XCONS (reading_selection_reply)->car = Qt; +} + + +DEFUN ("x-own-selection-internal", + Fx_own_selection_internal, Sx_own_selection_internal, + 2, 2, 0, + "Assert an X selection of the given TYPE with the given VALUE.\n\ +TYPE is a symbol, typically `PRIMARY', `SECONDARY', or `CLIPBOARD'.\n\ +\(Those are literal upper-case symbol names, since that's what X expects.)\n\ +VALUE is typically a string, or a cons of two markers, but may be\n\ +anything that the functions on selection-converter-alist know about.") + (selection_name, selection_value) + Lisp_Object selection_name, selection_value; +{ + CHECK_SYMBOL (selection_name, 0); + if (NILP (selection_value)) error ("selection-value may not be nil."); + x_own_selection (selection_name, selection_value); + return selection_value; +} + + +/* Request the selection value from the owner. If we are the owner, + simply return our selection value. If we are not the owner, this + will block until all of the data has arrived. */ + +DEFUN ("x-get-selection-internal", + Fx_get_selection_internal, Sx_get_selection_internal, 2, 2, 0, + "Return text selected from some X window.\n\ +SELECTION is a symbol, typically `PRIMARY', `SECONDARY', or `CLIPBOARD'.\n\ +\(Those are literal upper-case symbol names, since that's what X expects.)\n\ +TYPE is the type of data desired, typically STRING.") + (selection_symbol, target_type) + Lisp_Object selection_symbol, target_type; +{ + Lisp_Object val = Qnil; + struct gcpro gcpro1, gcpro2; + GCPRO2 (target_type, val); /* we store newly consed data into these */ + CHECK_SYMBOL (selection_symbol, 0); + +#if 0 /* #### MULTIPLE doesn't work yet */ + if (CONSP (target_type) + && XCONS (target_type)->car == QMULTIPLE) + { + CHECK_VECTOR (XCONS (target_type)->cdr, 0); + /* So we don't destructively modify this... */ + target_type = copy_multiple_data (target_type); + } + else +#endif + CHECK_SYMBOL (target_type, 0); + + val = x_get_local_selection (selection_symbol, target_type); + + if (NILP (val)) + { + val = x_get_foreign_selection (selection_symbol, target_type); + goto DONE; + } + + if (CONSP (val) + && SYMBOLP (XCONS (val)->car)) + { + val = XCONS (val)->cdr; + if (CONSP (val) && NILP (XCONS (val)->cdr)) + val = XCONS (val)->car; + } + val = clean_local_selection_data (val); + DONE: + UNGCPRO; + return val; +} + +DEFUN ("x-disown-selection-internal", + Fx_disown_selection_internal, Sx_disown_selection_internal, 1, 2, 0, + "If we own the named selection, then disown it (make there be no selection).") + (selection, time) + Lisp_Object selection; + Lisp_Object time; +{ + Display *display = x_current_display; + Time timestamp; + Atom selection_atom; + XSelectionClearEvent event; + + CHECK_SYMBOL (selection, 0); + if (NILP (time)) + timestamp = mouse_timestamp; + else + timestamp = cons_to_long (time); + + if (NILP (assq_no_quit (selection, Vselection_alist))) + return Qnil; /* Don't disown the selection when we're not the owner. */ + + selection_atom = symbol_to_x_atom (display, selection); + + BLOCK_INPUT; + XSetSelectionOwner (display, selection_atom, None, timestamp); + UNBLOCK_INPUT; + + /* It doesn't seem to be guarenteed that a SelectionClear event will be + generated for a window which owns the selection when that window sets + the selection owner to None. The NCD server does, the MIT Sun4 server + doesn't. So we synthesize one; this means we might get two, but + that's ok, because the second one won't have any effect. */ + event.display = display; + event.selection = selection_atom; + event.time = timestamp; + x_handle_selection_clear (&event); + + return Qt; +} + + +DEFUN ("x-selection-owner-p", Fx_selection_owner_p, Sx_selection_owner_p, + 0, 1, 0, + "Whether the current emacs process owns the given X Selection.\n\ +The arg should be the name of the selection in question, typically one of\n\ +the symbols `PRIMARY', `SECONDARY', or `CLIPBOARD'.\n\ +\(Those are literal upper-case symbol names, since that's what X expects.)\n\ +For convenience, the symbol nil is the same as `PRIMARY',\n\ +and t is the same as `SECONDARY'.)") + (selection) + Lisp_Object selection; +{ + CHECK_SYMBOL (selection, 0); + if (EQ (selection, Qnil)) selection = QPRIMARY; + if (EQ (selection, Qt)) selection = QSECONDARY; + + if (NILP (Fassq (selection, Vselection_alist))) + return Qnil; + return Qt; +} + +DEFUN ("x-selection-exists-p", Fx_selection_exists_p, Sx_selection_exists_p, + 0, 1, 0, + "Whether there is an owner for the given X Selection.\n\ +The arg should be the name of the selection in question, typically one of\n\ +the symbols `PRIMARY', `SECONDARY', or `CLIPBOARD'.\n\ +\(Those are literal upper-case symbol names, since that's what X expects.)\n\ +For convenience, the symbol nil is the same as `PRIMARY',\n\ +and t is the same as `SECONDARY'.)") + (selection) + Lisp_Object selection; +{ + Window owner; + Display *dpy = x_current_display; + CHECK_SYMBOL (selection, 0); + if (!NILP (Fx_selection_owner_p (selection))) + return Qt; + BLOCK_INPUT; + owner = XGetSelectionOwner (dpy, symbol_to_x_atom (dpy, selection)); + UNBLOCK_INPUT; + return (owner ? Qt : Qnil); +} + + +#ifdef CUT_BUFFER_SUPPORT + +static int cut_buffers_initialized; /* Whether we're sure they all exist */ + +/* Ensure that all 8 cut buffers exist. ICCCM says we gotta... */ +static void +initialize_cut_buffers (display, window) + Display *display; + Window window; +{ + unsigned char *data = (unsigned char *) ""; + BLOCK_INPUT; +#define FROB(atom) XChangeProperty (display, window, atom, XA_STRING, 8, \ + PropModeAppend, data, 0) + FROB (XA_CUT_BUFFER0); + FROB (XA_CUT_BUFFER1); + FROB (XA_CUT_BUFFER2); + FROB (XA_CUT_BUFFER3); + FROB (XA_CUT_BUFFER4); + FROB (XA_CUT_BUFFER5); + FROB (XA_CUT_BUFFER6); + FROB (XA_CUT_BUFFER7); +#undef FROB + UNBLOCK_INPUT; + cut_buffers_initialized = 1; +} + + +#define CHECK_CUTBUFFER(symbol,n) \ + { CHECK_SYMBOL ((symbol), (n)); \ + if (!EQ((symbol), QCUT_BUFFER0) && !EQ((symbol), QCUT_BUFFER1) \ + && !EQ((symbol), QCUT_BUFFER2) && !EQ((symbol), QCUT_BUFFER3) \ + && !EQ((symbol), QCUT_BUFFER4) && !EQ((symbol), QCUT_BUFFER5) \ + && !EQ((symbol), QCUT_BUFFER6) && !EQ((symbol), QCUT_BUFFER7)) \ + Fsignal (Qerror, \ + Fcons (build_string ("doesn't name a cutbuffer"), \ + Fcons ((symbol), Qnil))); \ + } + +DEFUN ("x-get-cutbuffer-internal", Fx_get_cutbuffer_internal, + Sx_get_cutbuffer_internal, 1, 1, 0, + "Returns the value of the named cutbuffer (typically CUT_BUFFER0).") + (buffer) + Lisp_Object buffer; +{ + Display *display = x_current_display; + Window window = RootWindow (display, 0); /* Cutbuffers are on screen 0 */ + Atom buffer_atom; + unsigned char *data; + int bytes; + Atom type; + int format; + unsigned long size; + Lisp_Object ret; + + CHECK_CUTBUFFER (buffer, 0); + buffer_atom = symbol_to_x_atom (display, buffer); + + x_get_window_property (display, window, buffer_atom, &data, &bytes, + &type, &format, &size, 0); + if (!data) return Qnil; + + if (format != 8 || type != XA_STRING) + Fsignal (Qerror, + Fcons (build_string ("cut buffer doesn't contain 8-bit data"), + Fcons (x_atom_to_symbol (display, type), + Fcons (make_number (format), Qnil)))); + + ret = (bytes ? make_string ((char *) data, bytes) : Qnil); + xfree (data); + return ret; +} + + +DEFUN ("x-store-cutbuffer-internal", Fx_store_cutbuffer_internal, + Sx_store_cutbuffer_internal, 2, 2, 0, + "Sets the value of the named cutbuffer (typically CUT_BUFFER0).") + (buffer, string) + Lisp_Object buffer, string; +{ + Display *display = x_current_display; + Window window = RootWindow (display, 0); /* Cutbuffers are on screen 0 */ + Atom buffer_atom; + unsigned char *data; + int bytes; + int bytes_remaining; + int max_bytes = SELECTION_QUANTUM (display); + if (max_bytes > MAX_SELECTION_QUANTUM) max_bytes = MAX_SELECTION_QUANTUM; + + CHECK_CUTBUFFER (buffer, 0); + CHECK_STRING (string, 0); + buffer_atom = symbol_to_x_atom (display, buffer); + data = (unsigned char *) XSTRING (string)->data; + bytes = XSTRING (string)->size; + bytes_remaining = bytes; + + if (! cut_buffers_initialized) initialize_cut_buffers (display, window); + + BLOCK_INPUT; + while (bytes_remaining) + { + int chunk = (bytes_remaining < max_bytes + ? bytes_remaining : max_bytes); + XChangeProperty (display, window, buffer_atom, XA_STRING, 8, + (bytes_remaining == bytes + ? PropModeReplace + : PropModeAppend), + data, chunk); + data += chunk; + bytes_remaining -= chunk; + } + UNBLOCK_INPUT; + return string; +} + + +DEFUN ("x-rotate-cutbuffers-internal", Fx_rotate_cutbuffers_internal, + Sx_rotate_cutbuffers_internal, 1, 1, 0, + "Rotate the values of the cutbuffers by the given number of steps;\n\ +positive means move values forward, negative means backward.") + (n) + Lisp_Object n; +{ + Display *display = x_current_display; + Window window = RootWindow (display, 0); /* Cutbuffers are on screen 0 */ + Atom props [8]; + + CHECK_FIXNUM (n, 0); + if (XINT (n) == 0) return n; + if (! cut_buffers_initialized) initialize_cut_buffers (display, window); + props[0] = XA_CUT_BUFFER0; + props[1] = XA_CUT_BUFFER1; + props[2] = XA_CUT_BUFFER2; + props[3] = XA_CUT_BUFFER3; + props[4] = XA_CUT_BUFFER4; + props[5] = XA_CUT_BUFFER5; + props[6] = XA_CUT_BUFFER6; + props[7] = XA_CUT_BUFFER7; + BLOCK_INPUT; + XRotateWindowProperties (display, window, props, 8, XINT (n)); + UNBLOCK_INPUT; + return n; +} + +#endif + +static void +atoms_of_xselect () +{ +#define ATOM(x) XInternAtom (x_current_display, (x), False) + + BLOCK_INPUT; + /* Non-predefined atoms that we might end up using a lot */ + Xatom_CLIPBOARD = ATOM ("CLIPBOARD"); + Xatom_TIMESTAMP = ATOM ("TIMESTAMP"); + Xatom_TEXT = ATOM ("TEXT"); + Xatom_DELETE = ATOM ("DELETE"); + Xatom_MULTIPLE = ATOM ("MULTIPLE"); + Xatom_INCR = ATOM ("INCR"); + Xatom_EMACS_TMP = ATOM ("_EMACS_TMP_"); + Xatom_TARGETS = ATOM ("TARGETS"); + Xatom_NULL = ATOM ("NULL"); + Xatom_ATOM_PAIR = ATOM ("ATOM_PAIR"); + UNBLOCK_INPUT; +} + +void +syms_of_xselect () +{ + atoms_of_select (); + + defsubr (&Sx_get_selection_internal); + defsubr (&Sx_own_selection_internal); + defsubr (&Sx_disown_selection_internal); + defsubr (&Sx_selection_owner_p); + defsubr (&Sx_selection_exists_p); + +#ifdef CUT_BUFFER_SUPPORT + defsubr (&Sx_get_cutbuffer_internal); + defsubr (&Sx_store_cutbuffer_internal); + defsubr (&Sx_rotate_cutbuffers_internal); + cut_buffers_initialized = 0; +#endif + + reading_selection_reply = Fcons (Qnil, Qnil); + staticpro (&reading_selection_reply); + reading_selection_window = 0; + reading_which_selection = 0; + + property_change_wait_list = 0; + prop_location_tick = 0; + property_change_reply = Fcons (Qnil, Qnil); + staticpro (&property_change_reply); + + Vselection_alist = Qnil; + staticpro (&Vselection_alist); + + DEFVAR_LISP ("selection-converter-alist", &Vselection_converter_alist, + "An alist associating X Windows selection-types with functions.\n\ +These functions are called to convert the selection, with three args:\n\ +the name of the selection (typically `PRIMARY', `SECONDARY', or `CLIPBOARD');\n\ +a desired type to which the selection should be converted;\n\ +and the local selection value (whatever was given to `x-own-selection').\n\ +\n\ +The function should return the value to send to the X server\n\ +\(typically a string). A return value of nil\n\ +means that the conversion could not be done.\n\ +A return value which is the symbol `NULL'\n\ +means that a side-effect was executed,\n\ +and there is no meaningful selection value."); + Vselection_converter_alist = Qnil; + + DEFVAR_LISP ("x-lost-selection-hooks", &Vx_lost_selection_hooks, + "A list of functions to be called when Emacs loses an X selection.\n\ +\(This happens when some other X client makes its own selection\n\ +or when a Lisp program explicitly clears the selection.)\n\ +The functions are called with one argument, the selection type\n\ +\(a symbol, typically `PRIMARY', `SECONDARY', or `CLIPBOARD'.)"); + Vx_lost_selection_hooks = Qnil; + + DEFVAR_LISP ("x-sent-selection-hooks", &Vx_sent_selection_hooks, + "A list of functions to be called when Emacs answers a selection request.\n\ +The functions are called with four arguments:\n\ + - the selection name (typically `PRIMARY', `SECONDARY', or `CLIPBOARD');\n\ + - the selection-type which Emacs was asked to convert the\n\ + selection into before sending (for example, `STRING' or `LENGTH');\n\ + - a flag indicating success or failure for responding to the request.\n\ +We might have failed (and declined the request) for any number of reasons,\n\ +including being asked for a selection that we no longer own, or being asked\n\ +to convert into a type that we don't know about or that is inappropriate.\n\ +This hook doesn't let you change the behavior of Emacs's selection replies,\n\ +it merely informs you that they have happened."); + Vx_sent_selection_hooks = Qnil; + + DEFVAR_INT ("x-selection-timeout", &x_selection_timeout, + "Number of seconds to wait for a selection reply from another X client.\n\ +If the selection owner doens't reply in this many seconds, we give up.\n\ +A value of 0 means wait as long as necessary. This is initialized from the\n\ +\"*selectionTimeout\" resource (which is expressed in milliseconds)."); + x_selection_timeout = 0; + + QPRIMARY = intern ("PRIMARY"); staticpro (&QPRIMARY); + QSECONDARY = intern ("SECONDARY"); staticpro (&QSECONDARY); + QSTRING = intern ("STRING"); staticpro (&QSTRING); + QINTEGER = intern ("INTEGER"); staticpro (&QINTEGER); + QCLIPBOARD = intern ("CLIPBOARD"); staticpro (&QCLIPBOARD); + QTIMESTAMP = intern ("TIMESTAMP"); staticpro (&QTIMESTAMP); + QTEXT = intern ("TEXT"); staticpro (&QTEXT); + QTIMESTAMP = intern ("TIMESTAMP"); staticpro (&QTIMESTAMP); + QDELETE = intern ("DELETE"); staticpro (&QDELETE); + QMULTIPLE = intern ("MULTIPLE"); staticpro (&QMULTIPLE); + QINCR = intern ("INCR"); staticpro (&QINCR); + QEMACS_TMP = intern ("_EMACS_TMP_"); staticpro (&QEMACS_TMP); + QTARGETS = intern ("TARGETS"); staticpro (&QTARGETS); + QATOM = intern ("ATOM"); staticpro (&QATOM); + QATOM_PAIR = intern ("ATOM_PAIR"); staticpro (&QATOM_PAIR); + QNULL = intern ("NULL"); staticpro (&QNULL); + +#ifdef CUT_BUFFER_SUPPORT + QCUT_BUFFER0 = intern ("CUT_BUFFER0"); staticpro (&QCUT_BUFFER0); + QCUT_BUFFER1 = intern ("CUT_BUFFER1"); staticpro (&QCUT_BUFFER1); + QCUT_BUFFER2 = intern ("CUT_BUFFER2"); staticpro (&QCUT_BUFFER2); + QCUT_BUFFER3 = intern ("CUT_BUFFER3"); staticpro (&QCUT_BUFFER3); + QCUT_BUFFER4 = intern ("CUT_BUFFER4"); staticpro (&QCUT_BUFFER4); + QCUT_BUFFER5 = intern ("CUT_BUFFER5"); staticpro (&QCUT_BUFFER5); + QCUT_BUFFER6 = intern ("CUT_BUFFER6"); staticpro (&QCUT_BUFFER6); + QCUT_BUFFER7 = intern ("CUT_BUFFER7"); staticpro (&QCUT_BUFFER7); +#endif + +} |