/* X Selection processing for emacs
- Copyright (C) 1990 Free Software Foundation.
+ Copyright (C) 1990, 1992, 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 1, or (at your option)
+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,
#include "config.h"
#include "lisp.h"
#include "xterm.h"
-#include "screen.h"
+#include "buffer.h"
+#include "frame.h"
#ifdef HAVE_X11
/* Macros for X Selections */
-#define MAX_SELECTION(dpy) (((dpy)->max_request_size << 3) - 100)
-#define SELECTION_LENGTH(len,format) ((len) * ((format) >> 3))
+#define MAX_SELECTION(dpy) (((dpy)->max_request_size << 2) - 100)
+#define SELECTION_LENGTH(len,format) ((len) * ((format) >> 2))
-/* From keyboard.c. This is almost always the right timestamp for
- ownership arbitration, since the last event was either the
- keystroke bound to the command now running, or something else read
- by that command. */
-extern unsigned long last_event_timestamp;
+/* The timestamp of the last input event we received from the X server. */
+unsigned long last_event_timestamp;
/* t if a mouse button is depressed. */
extern Lisp_Object Vmouse_grabbed;
/* When emacs became the PRIMARY selection owner. */
Time x_begin_selection_own;
+/* When emacs became the SECONDARY selection owner. */
+Time x_begin_secondary_selection_own;
+
/* When emacs became the CLIPBOARD selection owner. */
Time x_begin_clipboard_own;
/* The value of the current PRIMARY selection. */
Lisp_Object Vx_selection_value;
-/* Emacs' selection property identifier. */
+/* The value of the current SECONDARY selection. */
+Lisp_Object Vx_secondary_selection_value;
+
+/* Types of selections we may make. */
+Lisp_Object Qprimary, Qsecondary, Qclipboard;
+
+/* Emacs' selection property identifiers. */
Atom Xatom_emacs_selection;
+Atom Xatom_emacs_secondary_selection;
/* Clipboard selection atom. */
Atom Xatom_clipboard_selection;
/* Atom for indicating property type TEXT */
Atom Xatom_text;
+/* Kinds of protocol things we may receive. */
+Atom Xatom_wm_take_focus;
+Atom Xatom_wm_save_yourself;
+Atom Xatom_wm_delete_window;
+
+/* Communication with window managers. */
+Atom Xatom_wm_protocols;
+
/* These are to handle incremental selection transfer. */
Window incr_requestor;
Atom incr_property;
unsigned char *incr_value;
unsigned char *incr_ptr;
-/* SELECTION OWNER CODE */
-
-/* Become the selection owner and make our data the selection value.
- 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, 1, "sStore text for pasting: ",
- "Stores string STRING for pasting in another X window.\n\
-This is done with the X11 selection mechanism.")
- (string)
- register Lisp_Object string;
+/* 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. */
+
+static int
+own_selection (selection_type, time)
+ Atom selection_type;
+ Time time;
{
Window owner_window, selecting_window;
- Time event_time;
-
- CHECK_STRING (string, 0);
- BLOCK_INPUT;
- selecting_window = selected_screen->display.x->window_desc;
+ 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;
- if (EQ (Qnil, Vx_selection_value)) /* We are not the owner. */
- {
- event_time = last_event_timestamp;
- XSetSelectionOwner (x_current_display, XA_PRIMARY,
- selecting_window, event_time);
- owner_window = XGetSelectionOwner (x_current_display, XA_PRIMARY);
+ 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 (owner_window != selecting_window)
- {
- UNBLOCK_INPUT;
- error ("X error: could not acquire selection ownership");
- }
- }
+ return 0;
- x_begin_selection_own = event_time;
- Vx_selection_value = string;
- UNBLOCK_INPUT;
-
- return Qnil;
+ return 1;
}
-/* CLIPBOARD OWNERSHIP */
+/* Become the selection owner and make our data the selection value.
+ If we are already the owner, merely change data and timestamp values.
+ This avoids generating SelectionClear events for ourselves. */
-DEFUN ("x-own-clipboard", Fx_own_clipboard, Sx_own_clipboard,
- 1, 1, "sCLIPBOARD string: ",
- "Assert X clipboard ownership with value STRING.")
- (string)
- register Lisp_Object string;
+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\
+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;
{
- Window owner_window, selecting_window;
- Time event_time;
-
+ Atom selection_type;
+ Lisp_Object val;
+ Time event_time = last_event_timestamp;
CHECK_STRING (string, 0);
- BLOCK_INPUT;
- selecting_window = selected_screen->display.x->window_desc;
+ val = Qnil;
- if (EQ (Qnil, Vx_clipboard_value))
+ if (NILP (selection) || EQ (selection, Qprimary))
{
- event_time = last_event_timestamp;
- XSetSelectionOwner (x_current_display, Xatom_clipboard,
- selecting_window, event_time);
- owner_window = XGetSelectionOwner (x_current_display, Xatom_clipboard);
-
- if (owner_window != selecting_window)
+ BLOCK_INPUT;
+ if (own_selection (XA_PRIMARY, event_time))
{
- UNBLOCK_INPUT;
- error ("X error: could not acquire selection ownership");
+ x_begin_selection_own = event_time;
+ val = Vx_selection_value = string;
}
+ UNBLOCK_INPUT;
}
+ else if (EQ (selection, Qsecondary))
+ {
+ BLOCK_INPUT;
+ if (own_selection (XA_SECONDARY, event_time))
+ {
+ x_begin_secondary_selection_own = event_time;
+ val = Vx_secondary_selection_value = string;
+ }
+ UNBLOCK_INPUT;
+ }
+ else if (EQ (selection, Qclipboard))
+ {
+ BLOCK_INPUT;
+ if (own_selection (Xatom_clipboard, event_time))
+ {
+ x_begin_clipboard_own = event_time;
+ val = Vx_clipboard_value = string;
+ }
+ UNBLOCK_INPUT;
+ }
+ else
+ error ("Invalid X selection type");
- x_begin_clipboard_own = event_time;
- Vx_clipboard_value = string;
- UNBLOCK_INPUT;
-
- return Qnil;
+ return val;
}
/* Clear our selection ownership data, as some other client has
Atom selection;
Time changed_owner_time;
{
- struct screen *s = x_window_to_screen (old_owner);
+ struct frame *s = x_window_to_frame (old_owner);
if (s) /* We are the owner */
{
x_begin_selection_own = 0;
Vx_selection_value = Qnil;
}
+ else if (selection == XA_SECONDARY)
+ {
+ x_begin_secondary_selection_own = 0;
+ Vx_secondary_selection_value = Qnil;
+ }
else if (selection == Xatom_clipboard)
{
x_begin_clipboard_own = 0;
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 propery 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
emacs_own_time = x_begin_selection_own;
selection_value = Vx_selection_value;
}
+ else if (event.selection == XA_SECONDARY)
+ {
+ emacs_own_time = x_begin_secondary_selection_own;
+ selection_value = Vx_secondary_selection_value;
+ }
else if (event.selection == Xatom_clipboard)
{
emacs_own_time = x_begin_clipboard_own;
unsigned char *data;
int result, i;
- if (event.property == 0 /* 0 == NULL */
+ if (event.property == 0 /* 0 == NILP */
|| event.property == None)
return;
format = 32;
XChangeProperty (evt.display, evt.requestor, evt.property,
evt.target, format, PropModeReplace,
- (unsigned char *) &emacs_own_time, format);
+ (unsigned char *) &emacs_own_time, 1);
return;
}
else if (event.target == Xatom_delete) /* Delete our selection. */
x_disown_selection (event.owner, event.selection, event.time);
- /* Now return property of type NULL, length 0. */
+ /* Now return property of type NILP, length 0. */
XChangeProperty (event.display, event.requestor, event.property,
0, format, PropModeReplace, (unsigned char *) 0, 0);
return;
{
if (event.selection == Xatom_emacs_selection)
Vx_selection_value = make_string (data);
+ else if (event.selection == Xatom_emacs_secondary_selection)
+ Vx_secondary_selection_value = make_string (data);
else if (event.selection == Xatom_clipboard_selection)
Vx_clipboard_value = make_string (data);
else
}
}
-/* SELECTION REQUESTOR CODE */
+\f
+/* Requesting the value of a selection. */
+
+static Lisp_Object x_selection_arrival ();
/* Predicate function used to match a requested event. */
return False;
}
-/* 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. */
+/* Request a selection value from its owner. This will block until
+ all the data is arrived. */
-DEFUN ("x-get-selection", Fx_get_selection, Sx_get_selection, 0, 0, 0,
- "Return text selected from some X window.\n\
-This is done with the X11 selection mechanism.")
- ()
+static Lisp_Object
+get_selection_value (type)
+ Atom type;
{
XEvent event;
Lisp_Object val;
Time requestor_time; /* Timestamp of selection request. */
Window requestor_window;
- if (!EQ (Qnil, Vx_selection_value)) /* We are the owner */
- return Vx_selection_value;
-
BLOCK_INPUT;
requestor_time = last_event_timestamp;
- requestor_window = selected_screen->display.x->window_desc;
- XConvertSelection (x_current_display, XA_PRIMARY, XA_STRING,
+ 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,
&event,
return val;
}
-/* Request the clipboard contents from its owner. If we are the owner,
- simply return the clipboard string. */
+/* Request a 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-clipboard", Fx_get_clipboard, Sx_get_clipboard, 0, 0, 0,
- "Return text pasted to the clipboard.\n\
-This is done with the X11 selection mechanism.")
- ()
+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;
{
- XEvent event;
- Lisp_Object val;
- Time requestor_time; /* Timestamp of selection request. */
- Window requestor_window;
+ Atom selection_type;
- if (!EQ (Qnil, Vx_clipboard_value)) /* We are the owner */
- return Vx_selection_value;
+ if (NILP (selection) || EQ (selection, Qprimary))
+ {
+ if (!NILP (Vx_selection_value))
+ return Vx_selection_value;
- BLOCK_INPUT;
- requestor_time = last_event_timestamp;
- requestor_window = selected_screen->display.x->window_desc;
- XConvertSelection (x_current_display, Xatom_clipboard, XA_STRING,
- Xatom_clipboard_selection,
- requestor_window, requestor_time);
- XIfEvent (x_current_display,
- &event,
- XCheckSelectionEvent,
- (char *) requestor_window);
- val = x_selection_arrival (&event, requestor_window, requestor_time);
- UNBLOCK_INPUT;
+ return get_selection_value (XA_PRIMARY);
+ }
+ else if (EQ (selection, Qsecondary))
+ {
+ if (!NILP (Vx_secondary_selection_value))
+ return Vx_secondary_selection_value;
- return val;
+ return get_selection_value (XA_SECONDARY);
+ }
+ else if (EQ (selection, Qclipboard))
+ {
+ if (!NILP (Vx_clipboard_value))
+ return Vx_clipboard_value;
+
+ return get_selection_value (Xatom_clipboard);
+ }
+ 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;
if (event->selection == XA_PRIMARY)
selection = Xatom_emacs_selection;
+ else if (event->selection == XA_SECONDARY)
+ selection = Xatom_emacs_secondary_selection;
else if (event->selection == Xatom_clipboard)
selection = Xatom_clipboard_selection;
else
do
{
- result = XGetWindowProperty (x_current_display,
- requestor_window,
+ result = XGetWindowProperty (x_current_display, requestor_window,
event->property, 0L,
10000000L, True, XA_STRING,
&type, &format, &items,
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 ()
{
"The value of emacs' last cut-string.");
Vx_selection_value = Qnil;
+ DEFVAR_LISP ("x-secondary-selection-value", &Vx_secondary_selection_value,
+ "The value of emacs' last secondary cut-string.");
+ Vx_secondary_selection_value = Qnil;
+
DEFVAR_LISP ("x-clipboard-value", &Vx_clipboard_value,
"The string emacs last sent to the clipboard.");
Vx_clipboard_value = Qnil;
- defsubr (&Sx_own_selection);
- defsubr (&Sx_get_selection);
- defsubr (&Sx_own_clipboard);
- defsubr (&Sx_get_clipboard);
+ Qprimary = intern ("primary");
+ staticpro (&Qprimary);
+ Qsecondary = intern ("secondary");
+ staticpro (&Qsecondary);
+ Qclipboard = intern ("clipboard");
+ staticpro (&Qclipboard);
+
+ 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 */