/* X Selection processing for emacs
- Copyright (C) 1990, 1992 Free Software Foundation.
+ Copyright (C) 1990, 1992, 1993 Free Software Foundation.
This file is part of GNU Emacs.
/* The value of the current SECONDARY selection. */
Lisp_Object Vx_secondary_selection_value;
-/* Types of selections we may make. Note that Qcut_buffer0 isn't really
- a selection, but it acts like one for the sake of Fx_own_selection and
- Fx_selection_value. */
-Lisp_Object Qprimary, Qsecondary, Qclipboard, Qcut_buffer0;
+/* Types of selections we may make. */
+Lisp_Object Qprimary, Qsecondary, Qclipboard;
/* Emacs' selection property identifiers. */
Atom Xatom_emacs_selection;
unsigned char *incr_value;
unsigned char *incr_ptr;
-/* SELECTION OWNER CODE */
+/* Declarations for handling cut buffers.
+
+ Whenever we set a cut buffer or read a cut buffer's value, we cache
+ it in cut_buffer_value. We look for PropertyNotify events about
+ the CUT_BUFFER properties, and invalidate our cache accordingly.
+ We ignore PropertyNotify events that we suspect were caused by our
+ own changes to the cut buffers, so we can keep the cache valid
+ longer.
+
+ IS ALL THIS HAIR WORTH IT? Well, these functions get called every
+ time an element goes into or is retrieved from the kill ring, and
+ those ought to be quick. It's not fun in time or space to wait for
+ 50k cut buffers to fly back and forth across the net. */
+
+/* The number of CUT_BUFFER properties defined under X. */
+#define NUM_CUT_BUFFERS (8)
+
+/* cut_buffer_atom[n] is the atom naming the nth cut buffer. */
+static Atom cut_buffer_atom[NUM_CUT_BUFFERS] = {
+ XA_CUT_BUFFER0, XA_CUT_BUFFER1, XA_CUT_BUFFER2, XA_CUT_BUFFER3,
+ XA_CUT_BUFFER4, XA_CUT_BUFFER5, XA_CUT_BUFFER6, XA_CUT_BUFFER7
+};
+
+/* cut_buffer_value is an eight-element vector;
+ (aref cut_buffer_value n) is the cached value of cut buffer n, or
+ Qnil if cut buffer n is unset. */
+static Lisp_Object cut_buffer_value;
+
+/* Bit N of cut_buffer_cached is true if (aref cut_buffer_value n) is
+ known to be valid. This is cleared by PropertyNotify events
+ handled by x_invalidate_cut_buffer_cache. It would be wonderful if
+ that routine could just set the appropriate element of
+ cut_buffer_value to some special value meaning "uncached", but that
+ would lose if a GC happened to be in progress.
+
+ Bit N of cut_buffer_just_set is true if cut buffer N has been set since
+ the last PropertyNotify event; since we get an event even when we set
+ the property ourselves, we should ignore one event after setting
+ a cut buffer, so we don't have to throw away our cache. */
+#ifdef __STDC__
+volatile
+#endif
+static cut_buffer_cached, cut_buffer_just_set;
+
+\f
+/* Acquiring ownership of a selection. */
/* Request selection ownership if we do not already have it. */
{
Window owner_window, selecting_window;
- if ((EQ (selection_type, Qprimary) && !NILP (Vx_selection_value))
- || ((EQ (selection_type, Qsecondary)) && !NILP (Vx_secondary_selection_value))
- || ((EQ (selection_type, Qclipboard)) && !NILP (Vx_clipboard_value)))
+ if ((selection_type == XA_PRIMARY
+ && !NILP (Vx_selection_value))
+ || (selection_type == XA_SECONDARY
+ && !NILP (Vx_secondary_selection_value))
+ || (selection_type == Xatom_clipboard
+ && !NILP (Vx_clipboard_value)))
return 1;
- selecting_window = selected_frame->display.x->window_desc;
+ selecting_window = FRAME_X_WINDOW (selected_frame);
XSetSelectionOwner (x_current_display, selection_type,
selecting_window, time);
owner_window = XGetSelectionOwner (x_current_display, selection_type);
If we are already the owner, merely change data and timestamp values.
This avoids generating SelectionClear events for ourselves. */
-DEFUN ("x-own-selection", Fx_own_selection, Sx_own_selection,
- 1, 2, "",
- "Make STRING the selection value. Default is the primary selection,\n\
-but optional second argument TYPE may specify secondary or clipboard.\n\
+DEFUN ("x-set-selection", Fx_set_selection, Sx_set_selection,
+ 2, 2, "",
+ "Set the value of SELECTION to STRING.\n\
+SELECTION may be `primary', `secondary', or `clipboard'.\n\
\n\
-TYPE may also be cut-buffer0, indicating that Emacs should set the X\n\
-cut buffer 0 to STRING. This is for compatibility with older X\n\
-applications which still use the cut buffers; new applications should\n\
-use X selections.")
- (string, type)
- register Lisp_Object string, type;
+Selections are a mechanism for cutting and pasting information between\n\
+X Windows clients. Emacs's kill ring commands set the `primary'\n\
+selection to the top string of the kill ring, making it available to\n\
+other clients, like xterm. Those commands also use the `primary'\n\
+selection to retrieve information from other clients.\n\
+\n\
+According to the Inter-Client Communications Conventions Manual:\n\
+\n\
+The `primary' selection \"... is used for all commands that take only a\n\
+ single argument and is the principal means of communication between\n\
+ clients that use the selection mechanism.\" In Emacs, this means\n\
+ that the kill ring commands set the primary selection to the text\n\
+ put in the kill ring.\n\
+\n\
+The `secondary' selection \"... is used as the second argument to\n\
+ commands taking two arguments (for example, `exchange primary and\n\
+ secondary selections'), and as a means of obtaining data when there\n\
+ is a primary selection and the user does not want to disturb it.\"\n\
+ I am not sure how Emacs should use the secondary selection; if you\n\
+ come up with ideas, this function will at least let you get at it.\n\
+\n\
+The `clipboard' selection \"... is used to hold data that is being\n\
+ transferred between clients, that is, data that usually is being\n\
+ cut or copied, and then pasted.\" It seems that the `clipboard'\n\
+ selection is for the most part equivalent to the `primary'\n\
+ selection, so Emacs sets them both.\n\
+\n\
+Also see `x-selection', and the `interprogram-cut-function' variable.")
+ (selection, string)
+ register Lisp_Object selection, string;
{
Atom selection_type;
Lisp_Object val;
val = Qnil;
- if (NILP (type) || EQ (type, Qprimary))
+ if (NILP (selection) || EQ (selection, Qprimary))
{
BLOCK_INPUT;
if (own_selection (XA_PRIMARY, event_time))
}
UNBLOCK_INPUT;
}
- else if (EQ (type, Qsecondary))
+ else if (EQ (selection, Qsecondary))
{
BLOCK_INPUT;
if (own_selection (XA_SECONDARY, event_time))
}
UNBLOCK_INPUT;
}
- else if (EQ (type, Qclipboard))
+ else if (EQ (selection, Qclipboard))
{
BLOCK_INPUT;
if (own_selection (Xatom_clipboard, event_time))
}
UNBLOCK_INPUT;
}
- else if (EQ (type, Qcut_buffer0))
- {
- BLOCK_INPUT;
- XStoreBytes (x_current_display,
- (char *) XSTRING (string)->data,
- XSTRING (string)->size);
- val = string;
- UNBLOCK_INPUT;
- }
else
error ("Invalid X selection type");
abort (); /* Inconsistent state. */
}
+\f
+/* Answering selection requests. */
+
int x_selection_alloc_error;
int x_converting_selection;
-/* Reply to some client's request for our selection data. Data is
- placed in a property supplied by the requesting window.
+/* Reply to some client's request for our selection data.
+ Data is placed in a property supplied by the requesting window.
If the data exceeds the maximum amount the server can send,
then prepare to send it incrementally, and reply to the client with
}
}
-/* SELECTION REQUESTOR CODE */
+\f
+/* Requesting the value of a selection. */
+
+static Lisp_Object x_selection_arrival ();
/* Predicate function used to match a requested event. */
BLOCK_INPUT;
requestor_time = last_event_timestamp;
- requestor_window = selected_frame->display.x->window_desc;
+ requestor_window = FRAME_X_WINDOW (selected_frame);
XConvertSelection (x_current_display, type, XA_STRING,
Xatom_emacs_selection, requestor_window, requestor_time);
XIfEvent (x_current_display,
simply return our selection value. If we are not the owner, this
will block until all of the data has arrived. */
-DEFUN ("x-selection-value", Fx_selection_value, Sx_selection_value,
- 0, 1, "",
- "Return the value of one of the selections. Default is the primary\n\
-selection, but optional argument TYPE may specify secondary or clipboard.")
- (type)
- register Lisp_Object type;
+DEFUN ("x-selection", Fx_selection, Sx_selection,
+ 1, 1, "",
+ "Return the value of SELECTION.\n\
+SELECTION is one of `primary', `secondary', or `clipboard'.\n\
+\n\
+Selections are a mechanism for cutting and pasting information between\n\
+X Windows clients. When the user selects text in an X application,\n\
+the application should set the primary selection to that text; Emacs's\n\
+kill ring commands will then check the value of the `primary'\n\
+selection, and return it as the most recent kill.\n\
+The documentation for `x-set-selection' gives more information on how\n\
+the different selection types are intended to be used.\n\
+Also see the `interprogram-paste-function' variable.")
+ (selection)
+ register Lisp_Object selection;
{
Atom selection_type;
- if (NILP (type) || EQ (type, Qprimary))
+ if (NILP (selection) || EQ (selection, Qprimary))
{
if (!NILP (Vx_selection_value))
- return Vx_selection_value;
+ return Vx_selection_value;
return get_selection_value (XA_PRIMARY);
}
- else if (EQ (type, Qsecondary))
+ else if (EQ (selection, Qsecondary))
{
if (!NILP (Vx_secondary_selection_value))
return Vx_secondary_selection_value;
return get_selection_value (XA_SECONDARY);
}
- else if (EQ (type, Qclipboard))
+ else if (EQ (selection, Qclipboard))
{
if (!NILP (Vx_clipboard_value))
return Vx_clipboard_value;
return get_selection_value (Xatom_clipboard);
}
- else if (EQ (type, Qcut_buffer0))
- {
- char *data;
- int size;
- Lisp_Object string;
-
- BLOCK_INPUT;
- data = XFetchBytes (x_current_display, &size);
- if (data == 0)
- string = Qnil;
- else
- string = make_string (data, size);
- UNBLOCK_INPUT;
-
- return string;
- }
else
error ("Invalid X selection type");
}
-Lisp_Object
+static Lisp_Object
x_selection_arrival (event, requestor_window, requestor_time)
register XSelectionEvent *event;
Window requestor_window;
return Qnil;
}
+\f
+/* Cut buffer management. */
+
+DEFUN ("x-get-cut-buffer", Fx_get_cut_buffer, Sx_get_cut_buffer, 0, 1, "",
+ "Return the value of cut buffer N, or nil if it is unset.\n\
+If N is omitted, it defaults to zero.\n\
+Note that cut buffers have some problems that selections don't; try to\n\
+write your code to use cut buffers only for backward compatibility,\n\
+and use selections for the serious work.")
+ (n)
+ Lisp_Object n;
+{
+ int buf_num;
+
+ if (NILP (n))
+ buf_num = 0;
+ else
+ {
+ CHECK_NUMBER (n, 0);
+ buf_num = XINT (n);
+ }
+
+ if (buf_num < 0 || buf_num >= NUM_CUT_BUFFERS)
+ error ("cut buffer numbers must be from zero to seven");
+
+ {
+ Lisp_Object value;
+
+ /* Note that no PropertyNotify events will be processed while
+ input is blocked. */
+ BLOCK_INPUT;
+
+ if (cut_buffer_cached & (1 << buf_num))
+ value = XVECTOR (cut_buffer_value)->contents[buf_num];
+ else
+ {
+ /* Our cache is invalid; retrieve the property's value from
+ the server. */
+ int buf_len;
+ char *buf = XFetchBuffer (x_current_display, &buf_len, buf_num);
+
+ if (buf_len == 0)
+ value = Qnil;
+ else
+ value = make_string (buf, buf_len);
+
+ XVECTOR (cut_buffer_value)->contents[buf_num] = value;
+ cut_buffer_cached |= (1 << buf_num);
+
+ XFree (buf);
+ }
+
+ UNBLOCK_INPUT;
+
+ return value;
+ }
+}
+
+DEFUN ("x-set-cut-buffer", Fx_set_cut_buffer, Sx_set_cut_buffer, 2, 2, "",
+ "Set the value of cut buffer N to STRING.\n\
+Note that cut buffers have some problems that selections don't; try to\n\
+write your code to use cut buffers only for backward compatibility,\n\
+and use selections for the serious work.")
+ (n, string)
+ Lisp_Object n, string;
+{
+ int buf_num;
+
+ CHECK_NUMBER (n, 0);
+ CHECK_STRING (string, 1);
+
+ buf_num = XINT (n);
+
+ if (buf_num < 0 || buf_num >= NUM_CUT_BUFFERS)
+ error ("cut buffer numbers must be from zero to seven");
+
+ BLOCK_INPUT;
+
+ /* DECwindows and some other servers don't seem to like setting
+ properties to values larger than about 20k. For very large
+ values, they signal an error, but for intermediate values they
+ just seem to hang.
+
+ We could just truncate the request, but it's better to let the
+ user know that the strategy he/she's using isn't going to work
+ than to have it work partially, but incorrectly. */
+
+ if (XSTRING (string)->size == 0
+ || XSTRING (string)->size > MAX_SELECTION (x_current_display))
+ {
+ XStoreBuffer (x_current_display, (char *) 0, 0, buf_num);
+ string = Qnil;
+ }
+ else
+ {
+ XStoreBuffer (x_current_display,
+ (char *) XSTRING (string)->data, XSTRING (string)->size,
+ buf_num);
+ }
+
+ XVECTOR (cut_buffer_value)->contents[buf_num] = string;
+ cut_buffer_cached |= (1 << buf_num);
+ cut_buffer_just_set |= (1 << buf_num);
+
+ UNBLOCK_INPUT;
+
+ return string;
+}
+
+/* Ask the server to send us an event if any cut buffer is modified. */
+
+void
+x_watch_cut_buffer_cache ()
+{
+ XSelectInput (x_current_display, ROOT_WINDOW, PropertyChangeMask);
+}
+
+/* The server has told us that a cut buffer has been modified; deal with that.
+ Note that this function is called at interrupt level. */
+void
+x_invalidate_cut_buffer_cache (XPropertyEvent *event)
+{
+ int i;
+
+ /* See which cut buffer this is about, if any. */
+ for (i = 0; i < NUM_CUT_BUFFERS; i++)
+ if (event->atom == cut_buffer_atom[i])
+ {
+ int mask = (1 << i);
+
+ if (cut_buffer_just_set & mask)
+ cut_buffer_just_set &= ~mask;
+ else
+ cut_buffer_cached &= ~mask;
+
+ break;
+ }
+}
+
+\f
+/* Bureaucracy. */
+
void
syms_of_xselect ()
{
staticpro (&Qsecondary);
Qclipboard = intern ("clipboard");
staticpro (&Qclipboard);
- Qcut_buffer0 = intern ("cut-buffer0");
- staticpro (&Qcut_buffer0);
- defsubr (&Sx_own_selection);
- defsubr (&Sx_selection_value);
+ defsubr (&Sx_set_selection);
+ defsubr (&Sx_selection);
+
+ cut_buffer_value = Fmake_vector (make_number (NUM_CUT_BUFFERS), Qnil);
+ staticpro (&cut_buffer_value);
+
+ defsubr (&Sx_get_cut_buffer);
+ defsubr (&Sx_set_cut_buffer);
}
#endif /* X11 */