488 lines
12 KiB
C
488 lines
12 KiB
C
/*
|
|
*
|
|
*
|
|
* xclib.c - xclip library to look after xlib mechanics for xclip
|
|
* Copyright (C) 2001 Kim Saunders
|
|
* Copyright (C) 2007-2008 Peter Åstrand
|
|
*
|
|
* This program 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 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xatom.h>
|
|
#include "xcdef.h"
|
|
#include "xcprint.h"
|
|
#include "xclib.h"
|
|
|
|
/* check a pointer to allocater memory, print an error if it's null */
|
|
void
|
|
xcmemcheck(void *ptr)
|
|
{
|
|
if (ptr == NULL)
|
|
errmalloc();
|
|
}
|
|
|
|
/* wrapper for malloc that checks for errors */
|
|
void *
|
|
xcmalloc(size_t size)
|
|
{
|
|
void *mem;
|
|
|
|
mem = malloc(size);
|
|
xcmemcheck(mem);
|
|
|
|
return (mem);
|
|
}
|
|
|
|
/* wrapper for realloc that checks for errors */
|
|
void *
|
|
xcrealloc(void *ptr, size_t size)
|
|
{
|
|
void *mem;
|
|
|
|
mem = realloc(ptr, size);
|
|
xcmemcheck(mem);
|
|
|
|
return (mem);
|
|
}
|
|
|
|
/* a strdup() implementation since ANSI C doesn't include strdup() */
|
|
void *
|
|
xcstrdup(const char *string)
|
|
{
|
|
void *mem;
|
|
|
|
/* allocate a buffer big enough to hold the characters and the
|
|
* null terminator, then copy the string into the buffer
|
|
*/
|
|
mem = xcmalloc(strlen(string) + sizeof(char));
|
|
strcpy(mem, string);
|
|
|
|
return (mem);
|
|
}
|
|
|
|
/* Returns the machine-specific number of bytes per data element
|
|
* returned by XGetWindowProperty */
|
|
static size_t
|
|
mach_itemsize(int format)
|
|
{
|
|
if (format == 8)
|
|
return sizeof(char);
|
|
if (format == 16)
|
|
return sizeof(short);
|
|
if (format == 32)
|
|
return sizeof(long);
|
|
return 0;
|
|
}
|
|
|
|
/* Retrieves the contents of a selections. Arguments are:
|
|
*
|
|
* A display that has been opened.
|
|
*
|
|
* A window
|
|
*
|
|
* An event to process
|
|
*
|
|
* The selection to return
|
|
*
|
|
* The target(UTF8_STRING or XA_STRING) to return
|
|
*
|
|
* A pointer to an atom that receives the type of the data
|
|
*
|
|
* A pointer to a char array to put the selection into.
|
|
*
|
|
* A pointer to a long to record the length of the char array
|
|
*
|
|
* A pointer to an int to record the context in which to process the event
|
|
*
|
|
* Return value is 1 if the retrieval of the selection data is complete,
|
|
* otherwise it's 0.
|
|
*/
|
|
int
|
|
xcout(Display * dpy,
|
|
Window win,
|
|
XEvent evt, Atom sel, Atom target, Atom * type, unsigned char **txt, unsigned long *len,
|
|
unsigned int *context)
|
|
{
|
|
/* a property for other windows to put their selection into */
|
|
static Atom pty;
|
|
static Atom inc;
|
|
int pty_format;
|
|
|
|
/* buffer for XGetWindowProperty to dump data into */
|
|
unsigned char *buffer;
|
|
unsigned long pty_size, pty_items, pty_machsize;
|
|
|
|
/* local buffer of text to return */
|
|
unsigned char *ltxt = *txt;
|
|
|
|
if (!pty) {
|
|
pty = XInternAtom(dpy, "XCLIP_OUT", False);
|
|
}
|
|
|
|
if (!inc) {
|
|
inc = XInternAtom(dpy, "INCR", False);
|
|
}
|
|
|
|
switch (*context) {
|
|
/* there is no context, do an XConvertSelection() */
|
|
case XCLIB_XCOUT_NONE:
|
|
/* initialise return length to 0 */
|
|
if (*len > 0) {
|
|
free(*txt);
|
|
*len = 0;
|
|
}
|
|
|
|
/* send a selection request */
|
|
XConvertSelection(dpy, sel, target, pty, win, CurrentTime);
|
|
*context = XCLIB_XCOUT_SENTCONVSEL;
|
|
return (0);
|
|
|
|
case XCLIB_XCOUT_SENTCONVSEL:
|
|
if (evt.type != SelectionNotify)
|
|
return (0);
|
|
|
|
/* return failure when the current target failed */
|
|
if (evt.xselection.property == None) {
|
|
*context = XCLIB_XCOUT_BAD_TARGET;
|
|
return (0);
|
|
}
|
|
|
|
/* find the size and format of the data in property */
|
|
XGetWindowProperty(dpy,
|
|
win,
|
|
pty,
|
|
0,
|
|
0,
|
|
False,
|
|
AnyPropertyType, type, &pty_format, &pty_items, &pty_size, &buffer);
|
|
XFree(buffer);
|
|
|
|
if (*type == inc) {
|
|
/* start INCR mechanism by deleting property */
|
|
XDeleteProperty(dpy, win, pty);
|
|
XFlush(dpy);
|
|
*context = XCLIB_XCOUT_INCR;
|
|
return (0);
|
|
}
|
|
|
|
/* not using INCR mechanism, just read the property */
|
|
XGetWindowProperty(dpy,
|
|
win,
|
|
pty,
|
|
0,
|
|
(long) pty_size,
|
|
False,
|
|
AnyPropertyType, type, &pty_format, &pty_items, &pty_size, &buffer);
|
|
|
|
/* finished with property, delete it */
|
|
XDeleteProperty(dpy, win, pty);
|
|
|
|
/* compute the size of the data buffer we received */
|
|
pty_machsize = pty_items * mach_itemsize(pty_format);
|
|
|
|
/* copy the buffer to the pointer for returned data */
|
|
ltxt = (unsigned char *) xcmalloc(pty_machsize);
|
|
memcpy(ltxt, buffer, pty_machsize);
|
|
|
|
/* set the length of the returned data */
|
|
*len = pty_machsize;
|
|
*txt = ltxt;
|
|
|
|
/* free the buffer */
|
|
XFree(buffer);
|
|
|
|
*context = XCLIB_XCOUT_NONE;
|
|
|
|
/* complete contents of selection fetched, return 1 */
|
|
return (1);
|
|
|
|
case XCLIB_XCOUT_INCR:
|
|
/* To use the INCR method, we basically delete the
|
|
* property with the selection in it, wait for an
|
|
* event indicating that the property has been created,
|
|
* then read it, delete it, etc.
|
|
*/
|
|
|
|
/* make sure that the event is relevant */
|
|
if (evt.type != PropertyNotify)
|
|
return (0);
|
|
|
|
/* skip unless the property has a new value */
|
|
if (evt.xproperty.state != PropertyNewValue)
|
|
return (0);
|
|
|
|
/* check size and format of the property */
|
|
XGetWindowProperty(dpy,
|
|
win,
|
|
pty,
|
|
0,
|
|
0,
|
|
False,
|
|
AnyPropertyType,
|
|
type, &pty_format, &pty_items, &pty_size, (unsigned char **) &buffer);
|
|
|
|
if (pty_size == 0) {
|
|
/* no more data, exit from loop */
|
|
XFree(buffer);
|
|
XDeleteProperty(dpy, win, pty);
|
|
*context = XCLIB_XCOUT_NONE;
|
|
|
|
/* this means that an INCR transfer is now
|
|
* complete, return 1
|
|
*/
|
|
return (1);
|
|
}
|
|
|
|
XFree(buffer);
|
|
|
|
/* if we have come this far, the propery contains
|
|
* text, we know the size.
|
|
*/
|
|
XGetWindowProperty(dpy,
|
|
win,
|
|
pty,
|
|
0,
|
|
(long) pty_size,
|
|
False,
|
|
AnyPropertyType,
|
|
type, &pty_format, &pty_items, &pty_size, (unsigned char **) &buffer);
|
|
|
|
/* compute the size of the data buffer we received */
|
|
pty_machsize = pty_items * mach_itemsize(pty_format);
|
|
|
|
/* allocate memory to accommodate data in *txt */
|
|
if (*len == 0) {
|
|
*len = pty_machsize;
|
|
ltxt = (unsigned char *) xcmalloc(*len);
|
|
}
|
|
else {
|
|
*len += pty_machsize;
|
|
ltxt = (unsigned char *) xcrealloc(ltxt, *len);
|
|
}
|
|
|
|
/* add data to ltxt */
|
|
memcpy(<xt[*len - pty_machsize], buffer, pty_machsize);
|
|
|
|
*txt = ltxt;
|
|
XFree(buffer);
|
|
|
|
/* delete property to get the next item */
|
|
XDeleteProperty(dpy, win, pty);
|
|
XFlush(dpy);
|
|
return (0);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* put data into a selection, in response to a SelecionRequest event from
|
|
* another window (and any subsequent events relating to an INCR transfer).
|
|
*
|
|
* Arguments are:
|
|
*
|
|
* A display
|
|
*
|
|
* A window
|
|
*
|
|
* The event to respond to
|
|
*
|
|
* A pointer to an Atom. This gets set to the property nominated by the other
|
|
* app in it's SelectionRequest. Things are likely to break if you change the
|
|
* value of this yourself.
|
|
*
|
|
* The target(UTF8_STRING or XA_STRING) to respond to
|
|
*
|
|
* A pointer to an array of chars to read selection data from.
|
|
*
|
|
* The length of the array of chars.
|
|
*
|
|
* In the case of an INCR transfer, the position within the array of chars
|
|
* that is being processed.
|
|
*
|
|
* The context that event is the be processed within.
|
|
*/
|
|
int
|
|
xcin(Display * dpy,
|
|
Window * win,
|
|
XEvent evt,
|
|
Atom * pty, Atom target, unsigned char *txt, unsigned long len, unsigned long *pos,
|
|
unsigned int *context)
|
|
{
|
|
unsigned long chunk_len; /* length of current chunk (for incr
|
|
* transfers only)
|
|
*/
|
|
XEvent res; /* response to event */
|
|
static Atom inc;
|
|
static Atom targets;
|
|
static long chunk_size;
|
|
|
|
if (!targets) {
|
|
targets = XInternAtom(dpy, "TARGETS", False);
|
|
}
|
|
|
|
if (!inc) {
|
|
inc = XInternAtom(dpy, "INCR", False);
|
|
}
|
|
|
|
/* We consider selections larger than a quarter of the maximum
|
|
request size to be "large". See ICCCM section 2.5 */
|
|
if (!chunk_size) {
|
|
chunk_size = XExtendedMaxRequestSize(dpy) / 4;
|
|
if (!chunk_size) {
|
|
chunk_size = XMaxRequestSize(dpy) / 4;
|
|
}
|
|
}
|
|
|
|
switch (*context) {
|
|
case XCLIB_XCIN_NONE:
|
|
if (evt.type != SelectionRequest)
|
|
return (0);
|
|
|
|
/* set the window and property that is being used */
|
|
*win = evt.xselectionrequest.requestor;
|
|
*pty = evt.xselectionrequest.property;
|
|
|
|
/* reset position to 0 */
|
|
*pos = 0;
|
|
|
|
/* put the data into an property */
|
|
if (evt.xselectionrequest.target == targets) {
|
|
Atom types[2] = { targets, target };
|
|
|
|
/* send data all at once (not using INCR) */
|
|
XChangeProperty(dpy,
|
|
*win,
|
|
*pty,
|
|
XA_ATOM,
|
|
32, PropModeReplace, (unsigned char *) types,
|
|
(int) (sizeof(types) / sizeof(Atom))
|
|
);
|
|
}
|
|
else if (len > chunk_size) {
|
|
/* send INCR response */
|
|
XChangeProperty(dpy, *win, *pty, inc, 32, PropModeReplace, 0, 0);
|
|
|
|
/* With the INCR mechanism, we need to know
|
|
* when the requestor window changes (deletes)
|
|
* its properties
|
|
*/
|
|
XSelectInput(dpy, *win, PropertyChangeMask);
|
|
|
|
*context = XCLIB_XCIN_INCR;
|
|
}
|
|
else {
|
|
/* send data all at once (not using INCR) */
|
|
XChangeProperty(dpy,
|
|
*win,
|
|
*pty, target, 8, PropModeReplace, (unsigned char *) txt, (int) len);
|
|
}
|
|
|
|
/* Perhaps FIXME: According to ICCCM section 2.5, we should
|
|
confirm that XChangeProperty succeeded without any Alloc
|
|
errors before replying with SelectionNotify. However, doing
|
|
so would require an error handler which modifies a global
|
|
variable, plus doing XSync after each XChangeProperty. */
|
|
|
|
/* set values for the response event */
|
|
res.xselection.property = *pty;
|
|
res.xselection.type = SelectionNotify;
|
|
res.xselection.display = evt.xselectionrequest.display;
|
|
res.xselection.requestor = *win;
|
|
res.xselection.selection = evt.xselectionrequest.selection;
|
|
res.xselection.target = evt.xselectionrequest.target;
|
|
res.xselection.time = evt.xselectionrequest.time;
|
|
|
|
/* send the response event */
|
|
XSendEvent(dpy, evt.xselectionrequest.requestor, 0, 0, &res);
|
|
XFlush(dpy);
|
|
|
|
/* don't treat TARGETS request as contents request */
|
|
if (evt.xselectionrequest.target == targets)
|
|
return 0;
|
|
|
|
/* if len < chunk_size, then the data was sent all at
|
|
* once and the transfer is now complete, return 1
|
|
*/
|
|
if (len > chunk_size)
|
|
return (0);
|
|
else
|
|
return (1);
|
|
|
|
break;
|
|
|
|
case XCLIB_XCIN_INCR:
|
|
/* length of current chunk */
|
|
|
|
/* ignore non-property events */
|
|
if (evt.type != PropertyNotify)
|
|
return (0);
|
|
|
|
/* ignore the event unless it's to report that the
|
|
* property has been deleted
|
|
*/
|
|
if (evt.xproperty.state != PropertyDelete)
|
|
return (0);
|
|
|
|
/* set the chunk length to the maximum size */
|
|
chunk_len = chunk_size;
|
|
|
|
/* if a chunk length of maximum size would extend
|
|
* beyond the end ot txt, set the length to be the
|
|
* remaining length of txt
|
|
*/
|
|
if ((*pos + chunk_len) > len)
|
|
chunk_len = len - *pos;
|
|
|
|
/* if the start of the chunk is beyond the end of txt,
|
|
* then we've already sent all the data, so set the
|
|
* length to be zero
|
|
*/
|
|
if (*pos > len)
|
|
chunk_len = 0;
|
|
|
|
if (chunk_len) {
|
|
/* put the chunk into the property */
|
|
XChangeProperty(dpy,
|
|
*win, *pty, target, 8, PropModeReplace, &txt[*pos], (int) chunk_len);
|
|
}
|
|
else {
|
|
/* make an empty property to show we've
|
|
* finished the transfer
|
|
*/
|
|
XChangeProperty(dpy, *win, *pty, target, 8, PropModeReplace, 0, 0);
|
|
}
|
|
XFlush(dpy);
|
|
|
|
/* all data has been sent, break out of the loop */
|
|
if (!chunk_len)
|
|
*context = XCLIB_XCIN_NONE;
|
|
|
|
*pos += chunk_size;
|
|
|
|
/* if chunk_len == 0, we just finished the transfer,
|
|
* return 1
|
|
*/
|
|
if (chunk_len > 0)
|
|
return (0);
|
|
else
|
|
return (1);
|
|
break;
|
|
}
|
|
return (0);
|
|
}
|