summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/xselect.c1949
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
+
+}