mirror of
git://git.sv.gnu.org/emacs.git
synced 2025-12-26 07:11:34 -08:00
950 lines
27 KiB
C
950 lines
27 KiB
C
/* X Selection processing for emacs
|
||
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 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. */
|
||
|
||
#include "config.h"
|
||
#include "lisp.h"
|
||
#include "xterm.h"
|
||
#include "buffer.h"
|
||
#include "frame.h"
|
||
|
||
#ifdef HAVE_X11
|
||
|
||
/* Macros for X Selections */
|
||
#define MAX_SELECTION(dpy) (((dpy)->max_request_size << 2) - 100)
|
||
#define SELECTION_LENGTH(len,format) ((len) * ((format) >> 2))
|
||
|
||
/* 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 CLIPBOARD selection. */
|
||
Lisp_Object Vx_clipboard_value;
|
||
|
||
/* The value of the current PRIMARY selection. */
|
||
Lisp_Object Vx_selection_value;
|
||
|
||
/* 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;
|
||
|
||
/* Clipboard atom. */
|
||
Atom Xatom_clipboard;
|
||
|
||
/* Atom for indicating incremental selection transfer. */
|
||
Atom Xatom_incremental;
|
||
|
||
/* Atom for indicating multiple selection request list */
|
||
Atom Xatom_multiple;
|
||
|
||
/* Atom for what targets emacs handles. */
|
||
Atom Xatom_targets;
|
||
|
||
/* Atom for indicating timstamp selection request */
|
||
Atom Xatom_timestamp;
|
||
|
||
/* Atom requesting we delete our selection. */
|
||
Atom Xatom_delete;
|
||
|
||
/* Selection magic. */
|
||
Atom Xatom_insert_selection;
|
||
|
||
/* Type of property for INSERT_SELECTION. */
|
||
Atom Xatom_pair;
|
||
|
||
/* More selection magic. */
|
||
Atom Xatom_insert_property;
|
||
|
||
/* 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;
|
||
int incr_nbytes;
|
||
unsigned char *incr_value;
|
||
unsigned char *incr_ptr;
|
||
|
||
/* 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;
|
||
|
||
|
||
/* 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;
|
||
|
||
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 = 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)
|
||
return 0;
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* 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-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;
|
||
{
|
||
Atom selection_type;
|
||
Lisp_Object val;
|
||
Time event_time = last_event_timestamp;
|
||
CHECK_STRING (string, 0);
|
||
|
||
val = Qnil;
|
||
|
||
if (NILP (selection) || EQ (selection, Qprimary))
|
||
{
|
||
BLOCK_INPUT;
|
||
if (own_selection (XA_PRIMARY, event_time))
|
||
{
|
||
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");
|
||
|
||
return val;
|
||
}
|
||
|
||
/* Clear our selection ownership data, as some other client has
|
||
become the owner. */
|
||
|
||
void
|
||
x_disown_selection (old_owner, selection, changed_owner_time)
|
||
Window *old_owner;
|
||
Atom selection;
|
||
Time changed_owner_time;
|
||
{
|
||
struct frame *s = x_window_to_frame (old_owner);
|
||
|
||
if (s) /* We are the owner */
|
||
{
|
||
if (selection == XA_PRIMARY)
|
||
{
|
||
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;
|
||
Vx_clipboard_value = Qnil;
|
||
}
|
||
else
|
||
abort ();
|
||
}
|
||
else
|
||
abort (); /* Inconsistent state. */
|
||
}
|
||
|
||
|
||
/* 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.
|
||
|
||
If the data exceeds the maximum amount the server can send,
|
||
then prepare to send it incrementally, and reply to the client with
|
||
the total size of the data.
|
||
|
||
But first, check for all the other crufty stuff we could get. */
|
||
|
||
void
|
||
x_answer_selection_request (event)
|
||
XSelectionRequestEvent event;
|
||
{
|
||
Time emacs_own_time;
|
||
Lisp_Object selection_value;
|
||
XSelectionEvent evt;
|
||
int format = 8; /* We have only byte sized (text) data. */
|
||
|
||
evt.type = SelectionNotify; /* Construct reply event */
|
||
evt.display = event.display;
|
||
evt.requestor = event.requestor;
|
||
evt.selection = event.selection;
|
||
evt.time = event.time;
|
||
evt.target = event.target;
|
||
|
||
if (event.selection == XA_PRIMARY)
|
||
{
|
||
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;
|
||
selection_value = Vx_clipboard_value;
|
||
}
|
||
else
|
||
abort ();
|
||
|
||
if (event.time != CurrentTime
|
||
&& event.time < emacs_own_time)
|
||
evt.property = None;
|
||
else
|
||
{
|
||
if (event.property == None) /* obsolete client */
|
||
evt.property = event.target;
|
||
else
|
||
evt.property = event.property;
|
||
}
|
||
|
||
if (event.target == Xatom_targets) /* Send List of target atoms */
|
||
{
|
||
}
|
||
else if (event.target == Xatom_multiple) /* Recvd list: <target, prop> */
|
||
{
|
||
Atom type;
|
||
int return_format;
|
||
unsigned long items, bytes_left;
|
||
unsigned char *data;
|
||
int result, i;
|
||
|
||
if (event.property == 0 /* 0 == NILP */
|
||
|| event.property == None)
|
||
return;
|
||
|
||
result = XGetWindowProperty (event.display, event.requestor,
|
||
event.property, 0L, 10000000L,
|
||
True, Xatom_pair, &type, &return_format,
|
||
&items, &bytes_left, &data);
|
||
|
||
if (result == Success && type == Xatom_pair)
|
||
for (i = items; i > 0; i--)
|
||
{
|
||
/* Convert each element of the list. */
|
||
}
|
||
|
||
(void) XSendEvent (x_current_display, evt.requestor, False,
|
||
0L, (XEvent *) &evt);
|
||
return;
|
||
}
|
||
else if (event.target == Xatom_timestamp) /* Send ownership timestamp */
|
||
{
|
||
if (! emacs_own_time)
|
||
abort ();
|
||
|
||
format = 32;
|
||
XChangeProperty (evt.display, evt.requestor, evt.property,
|
||
evt.target, format, PropModeReplace,
|
||
(unsigned char *) &emacs_own_time, 1);
|
||
return;
|
||
}
|
||
else if (event.target == Xatom_delete) /* Delete our selection. */
|
||
{
|
||
if (EQ (Qnil, selection_value))
|
||
abort ();
|
||
|
||
x_disown_selection (event.owner, event.selection, event.time);
|
||
|
||
/* Now return property of type NILP, length 0. */
|
||
XChangeProperty (event.display, event.requestor, event.property,
|
||
0, format, PropModeReplace, (unsigned char *) 0, 0);
|
||
return;
|
||
}
|
||
else if (event.target == Xatom_insert_selection)
|
||
{
|
||
Atom type;
|
||
int return_format;
|
||
unsigned long items, bytes_left;
|
||
unsigned char *data;
|
||
int result = XGetWindowProperty (event.display, event.requestor,
|
||
event.property, 0L, 10000000L,
|
||
True, Xatom_pair, &type, &return_format,
|
||
&items, &bytes_left, &data);
|
||
if (result == Success && type == Xatom_pair)
|
||
{
|
||
/* Convert the first atom to (a selection) to the target
|
||
indicated by the second atom. */
|
||
}
|
||
}
|
||
else if (event.target == Xatom_insert_property)
|
||
{
|
||
Atom type;
|
||
int return_format;
|
||
unsigned long items, bytes_left;
|
||
unsigned char *data;
|
||
int result = XGetWindowProperty (event.display, event.requestor,
|
||
event.property, 0L, 10000000L,
|
||
True, XA_STRING, &type, &return_format,
|
||
&items, &bytes_left, &data);
|
||
|
||
if (result == Success && type == XA_STRING && return_format == 8)
|
||
{
|
||
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
|
||
abort ();
|
||
}
|
||
|
||
return;
|
||
}
|
||
else if ((event.target == Xatom_text
|
||
|| event.target == XA_STRING))
|
||
{
|
||
int size = XSTRING (selection_value)->size;
|
||
unsigned char *data = XSTRING (selection_value)->data;
|
||
|
||
if (EQ (Qnil, selection_value))
|
||
abort ();
|
||
|
||
/* Place data on requestor window's property. */
|
||
if (SELECTION_LENGTH (size, format)
|
||
<= MAX_SELECTION (x_current_display))
|
||
{
|
||
x_converting_selection = 1;
|
||
XChangeProperty (evt.display, evt.requestor, evt.property,
|
||
evt.target, format, PropModeReplace,
|
||
data, size);
|
||
if (x_selection_alloc_error)
|
||
{
|
||
x_selection_alloc_error = 0;
|
||
abort ();
|
||
}
|
||
x_converting_selection = 0;
|
||
}
|
||
else /* Send incrementally */
|
||
{
|
||
evt.target = Xatom_incremental;
|
||
incr_requestor = evt.requestor;
|
||
incr_property = evt.property;
|
||
x_converting_selection = 1;
|
||
|
||
/* Need to handle Alloc errors on these requests. */
|
||
XChangeProperty (evt.display, incr_requestor, incr_property,
|
||
Xatom_incremental, 32,
|
||
PropModeReplace,
|
||
(unsigned char *) &size, 1);
|
||
if (x_selection_alloc_error)
|
||
{
|
||
x_selection_alloc_error = 0;
|
||
x_converting_selection = 0;
|
||
abort ();
|
||
/* Now abort the send. */
|
||
}
|
||
|
||
incr_nbytes = size;
|
||
incr_value = data;
|
||
incr_ptr = data;
|
||
|
||
/* Ask for notification when requestor deletes property. */
|
||
XSelectInput (x_current_display, incr_requestor, PropertyChangeMask);
|
||
|
||
/* If we're sending incrementally, perhaps block here
|
||
until all sent? */
|
||
}
|
||
}
|
||
else
|
||
evt.property = None;
|
||
|
||
/* Don't do this if there was an Alloc error: abort the transfer
|
||
by sending None. */
|
||
(void) XSendEvent (x_current_display, evt.requestor, False,
|
||
0L, (XEvent *) &evt);
|
||
}
|
||
|
||
/* Send an increment of selection data in response to a PropertyNotify event.
|
||
The increment is placed in a property on the requestor's window.
|
||
When the requestor has processed the increment, it deletes the property,
|
||
which sends us another PropertyNotify event.
|
||
|
||
When there is no more data to send, we send a zero-length increment. */
|
||
|
||
void
|
||
x_send_incremental (event)
|
||
XPropertyEvent event;
|
||
{
|
||
if (incr_requestor
|
||
&& incr_requestor == event.window
|
||
&& incr_property == event.atom
|
||
&& event.state == PropertyDelete)
|
||
{
|
||
int format = 8;
|
||
int length = MAX_SELECTION (x_current_display);
|
||
int bytes_left = (incr_nbytes - (incr_ptr - incr_value));
|
||
|
||
if (length > bytes_left) /* Also sends 0 len when finished. */
|
||
length = bytes_left;
|
||
XChangeProperty (x_current_display, incr_requestor,
|
||
incr_property, XA_STRING, format,
|
||
PropModeAppend, incr_ptr, length);
|
||
if (x_selection_alloc_error)
|
||
{
|
||
x_selection_alloc_error = 0;
|
||
x_converting_selection = 0;
|
||
/* Abandon the transmission. */
|
||
abort ();
|
||
}
|
||
if (length > 0)
|
||
incr_ptr += length;
|
||
else
|
||
{ /* Everything's sent */
|
||
XSelectInput (x_current_display, incr_requestor, 0L);
|
||
incr_requestor = (Window) 0;
|
||
incr_property = (Atom) 0;
|
||
incr_nbytes = 0;
|
||
incr_value = (unsigned char *) 0;
|
||
incr_ptr = (unsigned char *) 0;
|
||
x_converting_selection = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* Requesting the value of a selection. */
|
||
|
||
static Lisp_Object x_selection_arrival ();
|
||
|
||
/* Predicate function used to match a requested event. */
|
||
|
||
Bool
|
||
XCheckSelectionEvent (dpy, event, window)
|
||
Display *dpy;
|
||
XEvent *event;
|
||
char *window;
|
||
{
|
||
if (event->type == SelectionNotify)
|
||
if (event->xselection.requestor == (Window) window)
|
||
return True;
|
||
|
||
return False;
|
||
}
|
||
|
||
/* Request a selection value from its owner. This will block until
|
||
all the data is arrived. */
|
||
|
||
static Lisp_Object
|
||
get_selection_value (type)
|
||
Atom type;
|
||
{
|
||
XEvent event;
|
||
Lisp_Object val;
|
||
Time requestor_time; /* Timestamp of selection request. */
|
||
Window requestor_window;
|
||
|
||
BLOCK_INPUT;
|
||
requestor_time = last_event_timestamp;
|
||
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,
|
||
XCheckSelectionEvent,
|
||
(char *) requestor_window);
|
||
val = x_selection_arrival (&event, requestor_window, requestor_time);
|
||
UNBLOCK_INPUT;
|
||
|
||
return val;
|
||
}
|
||
|
||
/* 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-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 (selection) || EQ (selection, Qprimary))
|
||
{
|
||
if (!NILP (Vx_selection_value))
|
||
return Vx_selection_value;
|
||
|
||
return get_selection_value (XA_PRIMARY);
|
||
}
|
||
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 (selection, Qclipboard))
|
||
{
|
||
if (!NILP (Vx_clipboard_value))
|
||
return Vx_clipboard_value;
|
||
|
||
return get_selection_value (Xatom_clipboard);
|
||
}
|
||
else
|
||
error ("Invalid X selection type");
|
||
}
|
||
|
||
static Lisp_Object
|
||
x_selection_arrival (event, requestor_window, requestor_time)
|
||
register XSelectionEvent *event;
|
||
Window requestor_window;
|
||
Time requestor_time;
|
||
{
|
||
int result;
|
||
Atom type, selection;
|
||
int format;
|
||
unsigned long items;
|
||
unsigned long bytes_left;
|
||
unsigned char *data = 0;
|
||
int offset = 0;
|
||
|
||
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
|
||
abort ();
|
||
|
||
if (event->requestor == requestor_window
|
||
&& event->time == requestor_time
|
||
&& event->property != None)
|
||
if (event->target != Xatom_incremental)
|
||
{
|
||
unsigned char *return_string =
|
||
(unsigned char *) alloca (MAX_SELECTION (x_current_display));
|
||
|
||
do
|
||
{
|
||
result = XGetWindowProperty (x_current_display, requestor_window,
|
||
event->property, 0L,
|
||
10000000L, True, XA_STRING,
|
||
&type, &format, &items,
|
||
&bytes_left, &data);
|
||
if (result == Success && type == XA_STRING && format == 8
|
||
&& offset < MAX_SELECTION (x_current_display))
|
||
{
|
||
bcopy (data, return_string + offset, items);
|
||
offset += items;
|
||
}
|
||
XFree ((char *) data);
|
||
}
|
||
while (bytes_left);
|
||
|
||
return make_string (return_string, offset);
|
||
}
|
||
else /* Prepare incremental transfer. */
|
||
{
|
||
unsigned char *increment_value;
|
||
unsigned char *increment_ptr;
|
||
int total_size;
|
||
int *increment_nbytes = 0;
|
||
|
||
result = XGetWindowProperty (x_current_display, requestor_window,
|
||
selection, 0L, 10000000L, False,
|
||
event->property, &type, &format,
|
||
&items, &bytes_left,
|
||
(unsigned char **) &increment_nbytes);
|
||
if (result == Success)
|
||
{
|
||
XPropertyEvent property_event;
|
||
|
||
total_size = *increment_nbytes;
|
||
increment_value = (unsigned char *) alloca (total_size);
|
||
increment_ptr = increment_value;
|
||
|
||
XDeleteProperty (x_current_display, event->requestor,
|
||
event->property);
|
||
XFlush (x_current_display);
|
||
XFree ((char *) increment_nbytes);
|
||
|
||
do
|
||
{ /* NOTE: this blocks. */
|
||
XWindowEvent (x_current_display, requestor_window,
|
||
PropertyChangeMask,
|
||
(XEvent *) &property_event);
|
||
|
||
if (property_event.atom == selection
|
||
&& property_event.state == PropertyNewValue)
|
||
do
|
||
{
|
||
result = XGetWindowProperty (x_current_display,
|
||
requestor_window,
|
||
selection, 0L,
|
||
10000000L, True,
|
||
AnyPropertyType,
|
||
&type, &format,
|
||
&items, &bytes_left,
|
||
&data);
|
||
if (result == Success && type == XA_STRING
|
||
&& format == 8)
|
||
{
|
||
bcopy (data, increment_ptr, items);
|
||
increment_ptr += items;
|
||
}
|
||
}
|
||
while (bytes_left);
|
||
|
||
}
|
||
while (increment_ptr < (increment_value + total_size));
|
||
|
||
return make_string (increment_value,
|
||
(increment_ptr - increment_value));
|
||
}
|
||
}
|
||
|
||
return Qnil;
|
||
}
|
||
|
||
|
||
/* 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;
|
||
}
|
||
}
|
||
|
||
|
||
/* Bureaucracy. */
|
||
|
||
void
|
||
syms_of_xselect ()
|
||
{
|
||
DEFVAR_LISP ("x-selection-value", &Vx_selection_value,
|
||
"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;
|
||
|
||
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 */
|