mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2024-12-01 08:17:38 +00:00
667 lines
18 KiB
C
667 lines
18 KiB
C
/* X Selection processing for emacs
|
|
Copyright (C) 1990 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)
|
|
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 "screen.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))
|
|
|
|
/* The last 23 bits of the timestamp of the last mouse button event. */
|
|
extern Time mouse_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 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;
|
|
|
|
/* Emacs' selection property identifier. */
|
|
Atom Xatom_emacs_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;
|
|
|
|
/* These are to handle incremental selection transfer. */
|
|
Window incr_requestor;
|
|
Atom incr_property;
|
|
int incr_nbytes;
|
|
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;
|
|
{
|
|
Window owner_window, selecting_window;
|
|
Time event_time;
|
|
|
|
CHECK_STRING (string, 0);
|
|
|
|
BLOCK_INPUT;
|
|
selecting_window = selected_screen->display.x->window_desc;
|
|
|
|
if (EQ (Qnil, Vx_selection_value)) /* We are not the owner. */
|
|
{
|
|
event_time = mouse_timestamp;
|
|
XSetSelectionOwner (x_current_display, XA_PRIMARY,
|
|
selecting_window, event_time);
|
|
owner_window = XGetSelectionOwner (x_current_display, XA_PRIMARY);
|
|
|
|
if (owner_window != selecting_window)
|
|
{
|
|
UNBLOCK_INPUT;
|
|
error ("X error: could not acquire selection ownership");
|
|
}
|
|
}
|
|
|
|
x_begin_selection_own = event_time;
|
|
Vx_selection_value = string;
|
|
UNBLOCK_INPUT;
|
|
|
|
return Qnil;
|
|
}
|
|
|
|
/* CLIPBOARD OWNERSHIP */
|
|
|
|
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;
|
|
{
|
|
Window owner_window, selecting_window;
|
|
Time event_time;
|
|
|
|
CHECK_STRING (string, 0);
|
|
|
|
BLOCK_INPUT;
|
|
selecting_window = selected_screen->display.x->window_desc;
|
|
|
|
if (EQ (Qnil, Vx_clipboard_value))
|
|
{
|
|
event_time = mouse_timestamp;
|
|
XSetSelectionOwner (x_current_display, Xatom_clipboard,
|
|
selecting_window, event_time);
|
|
owner_window = XGetSelectionOwner (x_current_display, Xatom_clipboard);
|
|
|
|
if (owner_window != selecting_window)
|
|
{
|
|
UNBLOCK_INPUT;
|
|
error ("X error: could not acquire selection ownership");
|
|
}
|
|
}
|
|
|
|
x_begin_clipboard_own = event_time;
|
|
Vx_clipboard_value = string;
|
|
UNBLOCK_INPUT;
|
|
|
|
return Qnil;
|
|
}
|
|
|
|
/* 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 screen *s = x_window_to_screen (old_owner);
|
|
|
|
if (s) /* We are the owner */
|
|
{
|
|
if (selection == XA_PRIMARY)
|
|
{
|
|
x_begin_selection_own = 0;
|
|
Vx_selection_value = Qnil;
|
|
}
|
|
else if (selection == Xatom_clipboard)
|
|
{
|
|
x_begin_clipboard_own = 0;
|
|
Vx_clipboard_value = Qnil;
|
|
}
|
|
else
|
|
abort ();
|
|
}
|
|
else
|
|
abort (); /* Inconsistent state. */
|
|
}
|
|
|
|
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.
|
|
|
|
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 == 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 == NULL */
|
|
|| 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, format);
|
|
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 NULL, 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_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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* SELECTION REQUESTOR CODE */
|
|
|
|
/* 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 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", 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.")
|
|
()
|
|
{
|
|
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 = mouse_timestamp;
|
|
requestor_window = selected_screen->display.x->window_desc;
|
|
XConvertSelection (x_current_display, XA_PRIMARY, 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 the clipboard contents from its owner. If we are the owner,
|
|
simply return the clipboard string. */
|
|
|
|
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.")
|
|
()
|
|
{
|
|
XEvent event;
|
|
Lisp_Object val;
|
|
Time requestor_time; /* Timestamp of selection request. */
|
|
Window requestor_window;
|
|
|
|
if (!EQ (Qnil, Vx_clipboard_value)) /* We are the owner */
|
|
return Vx_selection_value;
|
|
|
|
BLOCK_INPUT;
|
|
requestor_time = mouse_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 val;
|
|
}
|
|
|
|
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 == 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;
|
|
}
|
|
|
|
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-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);
|
|
}
|
|
#endif /* X11 */
|