Initializer/xclip/xclip.c

635 lines
18 KiB
C

/*
*
*
* xclip.c - command line interface to X server selections
* 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 <unistd.h>
#include <ctype.h>
#ifdef HAVE_ICONV
#include <errno.h>
#include <iconv.h>
#endif
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xmu/Atoms.h>
#include "xcdef.h"
#include "xcprint.h"
#include "xclib.h"
/* command line option table for XrmParseCommand() */
XrmOptionDescRec opt_tab[14];
/* Options that get set on the command line */
int sloop = 0; /* number of loops */
char *sdisp = NULL; /* X display to connect to */
Atom sseln = XA_PRIMARY; /* X selection to work with */
Atom target = XA_STRING;
/* Flags for command line options */
static int fverb = OSILENT; /* output level */
static int fdiri = T; /* direction is in */
static int ffilt = F; /* filter mode */
static int frmnl = F; /* remove (single) newline character at the very end if present */
Display *dpy; /* connection to X11 display */
XrmDatabase opt_db = NULL; /* database for options */
char **fil_names; /* names of files to read */
int fil_number = 0; /* number of files to read */
int fil_current = 0;
FILE *fil_handle = NULL;
/* variables to hold Xrm database record and type */
XrmValue rec_val;
char *rec_typ;
int tempi = 0;
/* Use XrmParseCommand to parse command line options to option variable */
static void
doOptMain(int argc, char *argv[])
{
/* Initialise resource manager and parse options into database */
XrmInitialize();
XrmParseCommand(&opt_db, opt_tab, sizeof(opt_tab) / sizeof(opt_tab[0]), PACKAGE_NAME, &argc,
argv);
/* set output level */
if (XrmGetResource(opt_db, "xclip.olevel", "Xclip.Olevel", &rec_typ, &rec_val)
) {
/* set verbose flag according to option */
if (strcmp(rec_val.addr, "S") == 0)
fverb = OSILENT;
if (strcmp(rec_val.addr, "Q") == 0)
fverb = OQUIET;
if (strcmp(rec_val.addr, "V") == 0)
fverb = OVERBOSE;
}
/* set direction flag (in or out) */
if (XrmGetResource(opt_db, "xclip.direction", "Xclip.Direction", &rec_typ, &rec_val)
) {
if (strcmp(rec_val.addr, "I") == 0)
fdiri = T;
if (strcmp(rec_val.addr, "O") == 0)
fdiri = F;
}
/* set filter mode */
if (XrmGetResource(opt_db, "xclip.filter", "Xclip.Filter", &rec_typ, &rec_val)
) {
/* filter mode only allowed in silent mode */
if (fverb == OSILENT)
ffilt = T;
}
/* set "remove last newline character if present" mode */
if (XrmGetResource(opt_db, "xclip.rmlastnl", "Xclip.RmLastNl", &rec_typ, &rec_val)
) {
frmnl = T;
}
/* check for -help and -version */
if (XrmGetResource(opt_db, "xclip.print", "Xclip.Print", &rec_typ, &rec_val)
) {
if (strcmp(rec_val.addr, "H") == 0)
prhelp(argv[0]);
if (strcmp(rec_val.addr, "V") == 0)
prversion();
}
/* check for -display */
if (XrmGetResource(opt_db, "xclip.display", "Xclip.Display", &rec_typ, &rec_val)
) {
sdisp = rec_val.addr;
if (fverb == OVERBOSE) /* print in verbose mode only */
fprintf(stderr, "Display: %s\n", sdisp);
}
/* check for -loops */
if (XrmGetResource(opt_db, "xclip.loops", "Xclip.Loops", &rec_typ, &rec_val)
) {
sloop = atoi(rec_val.addr);
if (fverb == OVERBOSE) /* print in verbose mode only */
fprintf(stderr, "Loops: %i\n", sloop);
}
/* Read remaining options (filenames) */
while ((fil_number + 1) < argc) {
if (fil_number > 0) {
fil_names = xcrealloc(fil_names, (fil_number + 1) * sizeof(char *)
);
}
else {
fil_names = xcmalloc(sizeof(char *));
}
fil_names[fil_number] = argv[fil_number + 1];
fil_number++;
}
}
/* process selection command line option */
static void
doOptSel(void)
{
/* set selection to work with */
if (XrmGetResource(opt_db, "xclip.selection", "Xclip.Selection", &rec_typ, &rec_val)
) {
switch (tolower(rec_val.addr[0])) {
case 'p':
sseln = XA_PRIMARY;
break;
case 's':
sseln = XA_SECONDARY;
break;
case 'c':
sseln = XA_CLIPBOARD(dpy);
break;
case 'b':
sseln = XA_STRING;
break;
}
if (fverb == OVERBOSE) {
fprintf(stderr, "Using selection: ");
if (sseln == XA_PRIMARY)
fprintf(stderr, "XA_PRIMARY");
if (sseln == XA_SECONDARY)
fprintf(stderr, "XA_SECONDARY");
if (sseln == XA_CLIPBOARD(dpy))
fprintf(stderr, "XA_CLIPBOARD");
if (sseln == XA_STRING)
fprintf(stderr, "XA_STRING");
fprintf(stderr, "\n");
}
}
}
/* process noutf8 and target command line options */
static void
doOptTarget(void)
{
/* check for -noutf8 */
if (XrmGetResource(opt_db, "xclip.noutf8", "Xclip.noutf8", &rec_typ, &rec_val)
) {
if (fverb == OVERBOSE) /* print in verbose mode only */
fprintf(stderr, "Using old UNICODE instead of UTF8.\n");
}
else if (XrmGetResource(opt_db, "xclip.target", "Xclip.Target", &rec_typ, &rec_val)
) {
target = XInternAtom(dpy, rec_val.addr, False);
if (fverb == OVERBOSE) /* print in verbose mode only */
fprintf(stderr, "Using %s.\n", rec_val.addr);
}
else {
target = XA_UTF8_STRING(dpy);
if (fverb == OVERBOSE) /* print in verbose mode only */
fprintf(stderr, "Using UTF8_STRING.\n");
}
}
static int
doIn(Window win, const char *progname)
{
unsigned char *sel_buf; /* buffer for selection data */
unsigned long sel_len = 0; /* length of sel_buf */
unsigned long sel_all = 0; /* allocated size of sel_buf */
XEvent evt; /* X Event Structures */
int dloop = 0; /* done loops counter */
/* in mode */
sel_all = 16; /* Reasonable ballpark figure */
sel_buf = xcmalloc(sel_all * sizeof(char));
/* Put chars into inc from stdin or files until we hit EOF */
do {
if (fil_number == 0) {
/* read from stdin if no files specified */
fil_handle = stdin;
}
else {
if ((fil_handle = fopen(fil_names[fil_current], "r")) == NULL) {
errperror(3, progname, ": ", fil_names[fil_current]
);
return EXIT_FAILURE;
}
else {
/* file opened successfully. Print
* message (verbose mode only).
*/
if (fverb == OVERBOSE)
fprintf(stderr, "Reading %s...\n", fil_names[fil_current]
);
}
}
fil_current++;
while (!feof(fil_handle)) {
/* If sel_buf is full (used elems =
* allocated elems)
*/
if (sel_len == sel_all) {
/* double the number of
* allocated elements
*/
sel_all *= 2;
sel_buf = (unsigned char *) xcrealloc(sel_buf, sel_all * sizeof(char)
);
}
sel_len += fread(sel_buf + sel_len, sizeof(char), sel_all - sel_len, fil_handle);
}
} while (fil_current < fil_number);
/* remove the last newline character if necessary */
if (frmnl && sel_len && sel_buf[sel_len - 1] == '\n') {
sel_len--;
}
/* if there are no files being read from (i.e., input
* is from stdin not files, and we are in filter mode,
* spit all the input back out to stdout
*/
if ((fil_number == 0) && ffilt) {
fwrite(sel_buf, sizeof(char), sel_len, stdout);
fclose(stdout);
}
/* Handle cut buffer if needed */
if (sseln == XA_STRING) {
XStoreBuffer(dpy, (char *) sel_buf, (int) sel_len, 0);
return EXIT_SUCCESS;
}
/* take control of the selection so that we receive
* SelectionRequest events from other windows
*/
/* FIXME: Should not use CurrentTime, according to ICCCM section 2.1 */
XSetSelectionOwner(dpy, sseln, win, CurrentTime);
/* fork into the background, exit parent process if we
* are in silent mode
*/
if (fverb == OSILENT) {
pid_t pid;
pid = fork();
/* exit the parent process; */
if (pid)
exit(EXIT_SUCCESS);
}
/* print a message saying what we're waiting for */
if (fverb > OSILENT) {
if (sloop == 1)
fprintf(stderr, "Waiting for one selection request.\n");
if (sloop < 1)
fprintf(stderr, "Waiting for selection requests, Control-C to quit\n");
if (sloop > 1)
fprintf(stderr, "Waiting for %i selection requests, Control-C to quit\n", sloop);
}
/* Avoid making the current directory in use, in case it will need to be umounted */
if (chdir("/") == -1) {
errperror(3, progname, ": ", "chdir to \"/\"");
return EXIT_FAILURE;
}
/* loop and wait for the expected number of
* SelectionRequest events
*/
while (dloop < sloop || sloop < 1) {
/* print messages about what we're waiting for
* if not in silent mode
*/
if (fverb > OSILENT) {
if (sloop > 1)
fprintf(stderr, " Waiting for selection request %i of %i.\n", dloop + 1, sloop);
if (sloop == 1)
fprintf(stderr, " Waiting for a selection request.\n");
if (sloop < 1)
fprintf(stderr, " Waiting for selection request number %i\n", dloop + 1);
}
/* wait for a SelectionRequest event */
while (1) {
static unsigned int clear = 0;
static unsigned int context = XCLIB_XCIN_NONE;
static unsigned long sel_pos = 0;
static Window cwin;
static Atom pty;
int finished;
XNextEvent(dpy, &evt);
finished = xcin(dpy, &cwin, evt, &pty, target, sel_buf, sel_len, &sel_pos, &context);
if (evt.type == SelectionClear)
clear = 1;
if ((context == XCLIB_XCIN_NONE) && clear)
return EXIT_SUCCESS;
if (finished)
break;
}
dloop++; /* increment loop counter */
}
return EXIT_SUCCESS;
}
static void
printSelBuf(FILE * fout, Atom sel_type, unsigned char *sel_buf, size_t sel_len)
{
#ifdef HAVE_ICONV
Atom html = XInternAtom(dpy, "text/html", True);
#endif
if (fverb == OVERBOSE) { /* print in verbose mode only */
char *atom_name = XGetAtomName(dpy, sel_type);
fprintf(stderr, "Type is %s.\n", atom_name);
XFree(atom_name);
}
if (sel_type == XA_INTEGER) {
/* if the buffer contains integers, print them */
long *long_buf = (long *) sel_buf;
size_t long_len = sel_len / sizeof(long);
while (long_len--)
fprintf(fout, "%ld\n", *long_buf++);
return;
}
if (sel_type == XA_ATOM) {
/* if the buffer contains atoms, print their names */
Atom *atom_buf = (Atom *) sel_buf;
size_t atom_len = sel_len / sizeof(Atom);
while (atom_len--) {
char *atom_name = XGetAtomName(dpy, *atom_buf++);
fprintf(fout, "%s\n", atom_name);
XFree(atom_name);
}
return;
}
#ifdef HAVE_ICONV
if (html != None && sel_type == html) {
/* if the buffer contains UCS-2 (UTF-16), convert to
* UTF-8. Mozilla-based browsers do this for the
* text/html target.
*/
iconv_t cd;
char *sel_charset = NULL;
if (sel_buf[0] == 0xFF && sel_buf[1] == 0xFE)
sel_charset = "UTF-16LE";
else if (sel_buf[0] == 0xFE && sel_buf[1] == 0xFF)
sel_charset = "UTF-16BE";
if (sel_charset != NULL && (cd = iconv_open("UTF-8", sel_charset)) != (iconv_t) - 1) {
char *out_buf_start = malloc(sel_len), *in_buf = (char *) sel_buf + 2,
*out_buf = out_buf_start;
size_t in_bytesleft = sel_len - 2, out_bytesleft = sel_len;
while (iconv(cd, &in_buf, &in_bytesleft, &out_buf, &out_bytesleft) == -1
&& errno == E2BIG) {
fwrite(out_buf_start, sizeof(char), sel_len - out_bytesleft, fout);
out_buf = out_buf_start;
out_bytesleft = sel_len;
}
if (out_buf != out_buf_start)
fwrite(out_buf_start, sizeof(char), sel_len - out_bytesleft, fout);
free(out_buf_start);
iconv_close(cd);
return;
}
}
#endif
/* otherwise, print the raw buffer out */
fwrite(sel_buf, sizeof(char), sel_len, fout);
}
static int
doOut(Window win)
{
Atom sel_type = None;
unsigned char *sel_buf; /* buffer for selection data */
unsigned long sel_len = 0; /* length of sel_buf */
XEvent evt; /* X Event Structures */
unsigned int context = XCLIB_XCOUT_NONE;
if (sseln == XA_STRING)
sel_buf = (unsigned char *) XFetchBuffer(dpy, (int *) &sel_len, 0);
else {
while (1) {
/* only get an event if xcout() is doing something */
if (context != XCLIB_XCOUT_NONE)
XNextEvent(dpy, &evt);
/* fetch the selection, or part of it */
xcout(dpy, win, evt, sseln, target, &sel_type, &sel_buf, &sel_len, &context);
if (context == XCLIB_XCOUT_BAD_TARGET) {
if (target == XA_UTF8_STRING(dpy)) {
/* fallback is needed. set XA_STRING to target and restart the loop. */
context = XCLIB_XCOUT_NONE;
target = XA_STRING;
continue;
}
else {
/* no fallback available, exit with failure */
char *atom_name = XGetAtomName(dpy, target);
fprintf(stderr, "Error: target %s not available\n", atom_name);
XFree(atom_name);
return EXIT_FAILURE;
}
}
/* only continue if xcout() is doing something */
if (context == XCLIB_XCOUT_NONE)
break;
}
}
/* remove the last newline character if necessary */
if (frmnl && sel_len && sel_buf[sel_len - 1] == '\n') {
sel_len--;
}
if (sel_len) {
/* only print the buffer out, and free it, if it's not
* empty
*/
printSelBuf(stdout, sel_type, sel_buf, sel_len);
if (sseln == XA_STRING)
XFree(sel_buf);
else
free(sel_buf);
}
return EXIT_SUCCESS;
}
int
main(int argc, char *argv[])
{
/* Declare variables */
Window win; /* Window */
int exit_code;
/* set up option table. I can't figure out a better way than this to
* do it while sticking to pure ANSI C. The option and specifier
* members have a type of volatile char *, so they need to be allocated
* by strdup or malloc, you can't set them to a string constant at
* declare time, this is not pure ANSI C apparently, although it does
* work with gcc
*/
/* loop option entry */
opt_tab[0].option = xcstrdup("-loops");
opt_tab[0].specifier = xcstrdup(".loops");
opt_tab[0].argKind = XrmoptionSepArg;
opt_tab[0].value = (XPointer) NULL;
/* display option entry */
opt_tab[1].option = xcstrdup("-display");
opt_tab[1].specifier = xcstrdup(".display");
opt_tab[1].argKind = XrmoptionSepArg;
opt_tab[1].value = (XPointer) NULL;
/* selection option entry */
opt_tab[2].option = xcstrdup("-selection");
opt_tab[2].specifier = xcstrdup(".selection");
opt_tab[2].argKind = XrmoptionSepArg;
opt_tab[2].value = (XPointer) NULL;
/* filter option entry */
opt_tab[3].option = xcstrdup("-filter");
opt_tab[3].specifier = xcstrdup(".filter");
opt_tab[3].argKind = XrmoptionNoArg;
opt_tab[3].value = (XPointer) xcstrdup(ST);
/* in option entry */
opt_tab[4].option = xcstrdup("-in");
opt_tab[4].specifier = xcstrdup(".direction");
opt_tab[4].argKind = XrmoptionNoArg;
opt_tab[4].value = (XPointer) xcstrdup("I");
/* out option entry */
opt_tab[5].option = xcstrdup("-out");
opt_tab[5].specifier = xcstrdup(".direction");
opt_tab[5].argKind = XrmoptionNoArg;
opt_tab[5].value = (XPointer) xcstrdup("O");
/* version option entry */
opt_tab[6].option = xcstrdup("-version");
opt_tab[6].specifier = xcstrdup(".print");
opt_tab[6].argKind = XrmoptionNoArg;
opt_tab[6].value = (XPointer) xcstrdup("V");
/* help option entry */
opt_tab[7].option = xcstrdup("-help");
opt_tab[7].specifier = xcstrdup(".print");
opt_tab[7].argKind = XrmoptionNoArg;
opt_tab[7].value = (XPointer) xcstrdup("H");
/* silent option entry */
opt_tab[8].option = xcstrdup("-silent");
opt_tab[8].specifier = xcstrdup(".olevel");
opt_tab[8].argKind = XrmoptionNoArg;
opt_tab[8].value = (XPointer) xcstrdup("S");
/* quiet option entry */
opt_tab[9].option = xcstrdup("-quiet");
opt_tab[9].specifier = xcstrdup(".olevel");
opt_tab[9].argKind = XrmoptionNoArg;
opt_tab[9].value = (XPointer) xcstrdup("Q");
/* verbose option entry */
opt_tab[10].option = xcstrdup("-verbose");
opt_tab[10].specifier = xcstrdup(".olevel");
opt_tab[10].argKind = XrmoptionNoArg;
opt_tab[10].value = (XPointer) xcstrdup("V");
/* utf8 option entry */
opt_tab[11].option = xcstrdup("-noutf8");
opt_tab[11].specifier = xcstrdup(".noutf8");
opt_tab[11].argKind = XrmoptionNoArg;
opt_tab[11].value = (XPointer) xcstrdup("N");
/* target option entry */
opt_tab[12].option = xcstrdup("-target");
opt_tab[12].specifier = xcstrdup(".target");
opt_tab[12].argKind = XrmoptionSepArg;
opt_tab[12].value = (XPointer) NULL;
/* "remove newline if it is the last character" entry */
opt_tab[13].option = xcstrdup("-rmlastnl");
opt_tab[13].specifier = xcstrdup(".rmlastnl");
opt_tab[13].argKind = XrmoptionNoArg;
opt_tab[13].value = (XPointer) xcstrdup(ST);
/* parse command line options */
doOptMain(argc, argv);
/* Connect to the X server. */
if ((dpy = XOpenDisplay(sdisp))) {
/* successful */
if (fverb == OVERBOSE)
fprintf(stderr, "Connected to X server.\n");
}
else {
/* couldn't connect to X server. Print error and exit */
errxdisplay(sdisp);
}
/* parse selection command line option */
doOptSel();
/* parse noutf8 and target command line options */
doOptTarget();
/* Create a window to trap events */
win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, 1, 1, 0, 0, 0);
/* get events about property changes */
XSelectInput(dpy, win, PropertyChangeMask);
if (fdiri)
exit_code = doIn(win, argv[0]);
else
exit_code = doOut(win);
/* Disconnect from the X server */
XCloseDisplay(dpy);
/* exit */
return exit_code;
}