Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 178 additions & 22 deletions xsel.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,14 @@ static Atom null_atom; /* The NULL atom */
static Atom text_atom; /* The TEXT atom */
static Atom utf8_atom; /* The UTF8 atom */
static Atom compound_text_atom; /* The COMPOUND_TEXT atom */
static Atom mime_atom; /* The MIME type atom */

/* Number of selection targets served by this.
* (MULTIPLE, INCR, TARGETS, TIMESTAMP, DELETE, TEXT, UTF8_STRING and STRING)
* (MULTIPLE, INCR, TARGETS, TIMESTAMP, DELETE, TEXT, UTF8_STRING, STRING and MIME type)
* NB. We do not currently serve COMPOUND_TEXT; we can retrieve it but do not
* perform charset conversion.
*/
#define MAX_NUM_TARGETS 9
#define MAX_NUM_TARGETS 10
static int NUM_TARGETS;
static Atom supported_targets[MAX_NUM_TARGETS];

Expand All @@ -81,6 +82,41 @@ static Bool do_follow = False;
/* nodaemon: Disable daemon mode if True. */
static Bool no_daemon = False;

/* mime_type: MIME type string for selection target, NULL for default text */
static char * mime_type = NULL;

/* sel_length: Actual length of selection data for binary MIME types */
static size_t sel_length = 0;

/*
* is_text_mime_type (mime_type)
*
* Check if a MIME type should register text fallbacks (STRING, UTF8_STRING)
* in addition to the specific MIME type.
*/
static Bool
is_text_mime_type (const char * mime_type)
{
if (!mime_type) return False;

/* Text-like MIME types that should provide text fallbacks */
if (strncmp(mime_type, "text/", 5) == 0) return True;
if (strncmp(mime_type, "application/json", 16) == 0) return True;
if (strncmp(mime_type, "application/xml", 15) == 0) return True;
if (strncmp(mime_type, "application/xhtml", 17) == 0) return True;
if (strncmp(mime_type, "application/javascript", 22) == 0) return True;
if (strncmp(mime_type, "application/x-", 14) == 0) {
/* Many application/x-* types are text-based */
const char * subtype = mime_type + 14;
if (strncmp(subtype, "sh", 2) == 0) return True;
if (strncmp(subtype, "perl", 4) == 0) return True;
if (strncmp(subtype, "python", 6) == 0) return True;
if (strncmp(subtype, "ruby", 4) == 0) return True;
}

return False;
}

/* logfile: name of file to log error messages to when detached */
static char logfile[MAXFNAME];

Expand Down Expand Up @@ -123,7 +159,8 @@ usage (void)
printf (" -z, --zeroflush Overwrites selection when zero ('\\0') is received\n");
printf (" -i, --input Read standard input into the selection\n\n");
printf ("Output options\n");
printf (" -o, --output Write the selection to standard output\n\n");
printf (" -o, --output Write the selection to standard output\n");
printf (" --query List available target types\n\n");
printf ("Action options\n");
printf (" -c, --clear Clear the selection\n");
printf (" -d, --delete Request that the selection be cleared and that\n");
Expand All @@ -146,6 +183,8 @@ usage (void)
printf (" specifies no timeout (default)\n\n");
printf ("Miscellaneous options\n");
printf (" --trim Remove newline ('\\n') char from end of input / output\n");
printf (" --type MIMETYPE Specify MIME type for selection (e.g. image/png)\n");
printf (" --html Shortcut for --type text/html\n");
printf (" -l, --logfile Specify file to log errors to when detached.\n");
printf (" -n, --nodetach Do not detach from the controlling terminal. Without\n");
printf (" this option, xsel will fork to become a background\n");
Expand Down Expand Up @@ -252,6 +291,7 @@ get_atom_name (Atom atom)
if (atom == null_atom) return "NULL";
if (atom == text_atom) return "TEXT";
if (atom == utf8_atom) return "UTF8_STRING";
if (atom == mime_atom) return mime_type;

ret = XGetAtomName (display, atom);
strncpy (atom_name, ret, MAXLINE+1);
Expand Down Expand Up @@ -950,6 +990,9 @@ read_input (unsigned char * read_buffer, Bool do_select)

print_debug (D_TRACE, "Accumulated %d bytes input", total_input);

/* Set global length for binary data handling */
sel_length = total_input;

return read_buffer;
}

Expand Down Expand Up @@ -1457,6 +1500,31 @@ handle_utf8_string (Display * display, Window requestor, Atom property,
selection, time, mparent);
}

/*
* handle_mime_string (display, requestor, property, sel)
*
* Handle a MIME type request; setting 'sel' as the data
*/
static HandleResult
handle_mime_string (Display * display, Window requestor, Atom property,
unsigned char * sel, Atom selection, Time time,
MultTrack * mparent)
{
size_t data_length;

/* Use actual byte length for binary data, strlen for text data */
if (mime_type && !is_text_mime_type(mime_type) && sel_length > 0) {
data_length = sel_length;
} else {
data_length = xs_strlen(sel);
}

return
change_property (display, requestor, property, mime_atom, 8,
PropModeReplace, sel, data_length,
selection, time, mparent);
}

/*
* handle_delete (display, requestor, property)
*
Expand Down Expand Up @@ -1500,14 +1568,22 @@ process_multiple (MultTrack * mt, Bool do_parent)
} else if (mt->atoms[i] == multiple_atom) {
retval |= handle_multiple (mt->display, mt->requestor, mt->atoms[i+1],
mt->sel, mt->selection, mt->time, mt);
} else if (mt->atoms[i] == mime_atom) {
retval |= handle_mime_string (mt->display, mt->requestor, mt->atoms[i+1],
mt->sel, mt->selection, mt->time, mt);
} else if (mt->atoms[i] == delete_atom) {
retval |= handle_delete (mt->display, mt->requestor, mt->atoms[i+1]);
} else if (mime_type && !is_text_mime_type(mime_type)) {
/* ================================================================
* If non-text MIME type set, everything after this point is ignored
* ================================================================ */
mt->atoms[i] = None;
} else if (mt->atoms[i] == XA_STRING || mt->atoms[i] == text_atom) {
retval |= handle_string (mt->display, mt->requestor, mt->atoms[i+1],
mt->sel, mt->selection, mt->time, mt);
} else if (mt->atoms[i] == utf8_atom) {
retval |= handle_utf8_string (mt->display, mt->requestor, mt->atoms[i+1],
mt->sel, mt->selection, mt->time, mt);
} else if (mt->atoms[i] == delete_atom) {
retval |= handle_delete (mt->display, mt->requestor, mt->atoms[i+1]);
} else if (mt->atoms[i] == None) {
/* the only other thing we know to handle is None, for which we
* do nothing. This block is, like, __so__ redundant. Welcome to
Expand Down Expand Up @@ -1676,6 +1752,21 @@ handle_selection_request (XEvent event, unsigned char * sel)
hr = handle_multiple (ev.display, ev.requestor, ev.property, sel,
ev.selection, ev.time, NULL);
}
} else if (ev.target == mime_atom) {
/* Received MIME type request */
ev.property = xsr->property;
hr = handle_mime_string (ev.display, ev.requestor, ev.property, sel,
ev.selection, ev.time, NULL);
} else if (ev.target == delete_atom) {
/* Received DELETE request */
ev.property = xsr->property;
hr = handle_delete (ev.display, ev.requestor, ev.property);
retval = False;
} else if (mime_type && !is_text_mime_type(mime_type)) {
/* ================================================================
* If non-text MIME type set, everything after this point is ignored
* ================================================================ */
ev.property = None;
} else if (ev.target == XA_STRING || ev.target == text_atom) {
/* Received STRING or TEXT request */
ev.property = xsr->property;
Expand All @@ -1686,11 +1777,6 @@ handle_selection_request (XEvent event, unsigned char * sel)
ev.property = xsr->property;
hr = handle_utf8_string (ev.display, ev.requestor, ev.property, sel,
ev.selection, ev.time, NULL);
} else if (ev.target == delete_atom) {
/* Received DELETE request */
ev.property = xsr->property;
hr = handle_delete (ev.display, ev.requestor, ev.property);
retval = False;
} else {
/* Cannot convert to requested target. This includes most non-string
* datatypes, and INSERT_SELECTION, INSERT_PROPERTY */
Expand Down Expand Up @@ -2033,6 +2119,7 @@ main(int argc, char *argv[])
Bool force_input = False, force_output = False;
Bool want_clipboard = False, do_delete = False;
Bool trim_trailing_newline = False;
Bool do_query = False;
Window root;
Atom selection = XA_PRIMARY, test_atom;
XClassHint * class_hints;
Expand Down Expand Up @@ -2113,6 +2200,13 @@ main(int argc, char *argv[])
want_clipboard = True;
} else if (OPT("--trim")) {
trim_trailing_newline = True;
} else if (OPT("--type")) {
i++; if (i >= argc) goto usage_err;
mime_type = argv[i];
} else if (OPT("--html")) {
mime_type = "text/html";
} else if (OPT("--query")) {
do_query = True;
} else if (OPT("--keep") || OPT("-k")) {
do_keep = True;
} else if (OPT("--exchange") || OPT("-x")) {
Expand Down Expand Up @@ -2252,22 +2346,31 @@ main(int argc, char *argv[])
supported_targets[s++] = incr_atom;
NUM_TARGETS++;

/* Get the TEXT atom */
text_atom = XInternAtom (display, "TEXT", False);
supported_targets[s++] = text_atom;
NUM_TARGETS++;
if (!mime_type || is_text_mime_type(mime_type)) {
/* Get the TEXT atom */
text_atom = XInternAtom (display, "TEXT", False);
supported_targets[s++] = text_atom;
NUM_TARGETS++;

/* Get the UTF8_STRING atom */
utf8_atom = XInternAtom (display, "UTF8_STRING", True);
if(utf8_atom != None) {
supported_targets[s++] = utf8_atom;
NUM_TARGETS++;
} else {
utf8_atom = XA_STRING;
}

/* Get the UTF8_STRING atom */
utf8_atom = XInternAtom (display, "UTF8_STRING", True);
if(utf8_atom != None) {
supported_targets[s++] = utf8_atom;
supported_targets[s++] = XA_STRING;
NUM_TARGETS++;
} else {
utf8_atom = XA_STRING;
}

supported_targets[s++] = XA_STRING;
NUM_TARGETS++;
/* Get the MIME type atom if specified */
if (mime_type) {
mime_atom = XInternAtom (display, mime_type, False);
supported_targets[s++] = mime_atom;
NUM_TARGETS++;
}

if (NUM_TARGETS > MAX_NUM_TARGETS) {
exit_err ("internal error num-targets (%d) > max-num-targets (%d)\n",
Expand Down Expand Up @@ -2300,6 +2403,59 @@ main(int argc, char *argv[])
_exit (0);
}

/* handle query and exit if so */
if (do_query) {
Atom prop;
XEvent event;
Atom target;
int format;
unsigned long length, bytesafter;
unsigned char * value;
Atom * atoms;
unsigned long i;

/* Find the selection to query */
if (want_clipboard) {
selection = XInternAtom (display, "CLIPBOARD", False);
}

/* Request TARGETS from selection owner */
prop = XInternAtom (display, "XSEL_QUERY", False);
XConvertSelection (display, selection, targets_atom, prop, window, timestamp);
XSync (display, False);

/* Wait for the response */
while (1) {
XNextEvent (display, &event);
if (event.type == SelectionNotify) {
if (event.xselection.property == None) {
printf ("No targets available\n");
fflush (stdout);
exit (0);
}

/* Get the TARGETS property */
XGetWindowProperty (event.xselection.display, event.xselection.requestor,
event.xselection.property, 0L, 1000000, True,
XA_ATOM, &target, &format, &length, &bytesafter, &value);

if (target == XA_ATOM && format == 32 && length > 0) {
atoms = (Atom *) value;
for (i = 0; i < length; i++) {
printf ("%s\n", get_atom_name (atoms[i]));
}
} else {
printf ("Invalid TARGETS response\n");
}

if (value) XFree (value);
break;
}
}
fflush (stdout);
exit (0);
}

/* Find the "CLIPBOARD" selection if required */
if (want_clipboard) {
selection = XInternAtom (display, "CLIPBOARD", False);
Expand Down