xsel

Program for manipulating the X clipboard
git clone https://git.sinitax.com/kfish/xsel
Log | Files | Refs | README | LICENSE | sfeed.txt

commit 5eb9195c4dcb8c80be65ec008831f43d41e2e391
Author: conrad <conrad@9c49b5d1-7df3-0310-bce2-b7278e68f44c>
Date:   Wed, 19 Sep 2007 00:24:07 +0000

import of 0.9.5 sources


git-svn-id: http://svn.kfish.org/xsel/trunk@188 9c49b5d1-7df3-0310-bce2-b7278e68f44c

Diffstat:
AImakefile | 10++++++++++
AMakefile | 757+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axsel.1x | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axsel.c | 1954+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axsel.h | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 2954 insertions(+), 0 deletions(-)

diff --git a/Imakefile b/Imakefile @@ -0,0 +1,10 @@ + INCLUDES = -I$(TOP) -I$(TOP)/X11 +LOCAL_LIBRARIES = $(XLIB) + SRCS = xsel.c + OBJS = xsel.o + CFLAGS += -Wall + +ComplexProgramTarget(xsel) + +shar: + shar Imakefile xsel.c xsel.h xsel.1x >xsel.shar diff --git a/Makefile b/Makefile @@ -0,0 +1,757 @@ +# Makefile generated by imake - do not edit! +# $TOG: imake.c /main/97 1997/06/20 20:23:51 kaleb $ + +# ---------------------------------------------------------------------- +# Makefile generated from "Imake.tmpl" and <Imakefile> +# $TOG: Imake.tmpl /main/245 1997/05/20 10:05:47 kaleb $ +# +# +# +# +# $XFree86: xc/config/cf/Imake.tmpl,v 3.32.2.12 1999/01/09 14:18:57 dawes Exp $ +# ---------------------------------------------------------------------- + +all:: + +.SUFFIXES: .i + +# $TOG: Imake.cf /main/28 1997/06/25 08:31:36 barstow $ +# $XFree86: xc/config/cf/Imake.cf,v 3.34.2.14 1999/07/29 09:22:23 hohndel Exp $ + +# ----------------------------------------------------------------------- +# site-specific configuration parameters that need to come before +# the platform-specific parameters - edit site.def to change + +# site: $XConsortium: site.def /main/revisionist/4 1996/12/31 08:02:07 kaleb $ +# site: $XFree86: xc/config/cf/site.def,v 3.17.2.1 1997/06/22 10:32:21 dawes Exp $ + +# $XFree86: xc/config/cf/xf86site.def,v 3.101.2.31 1999/08/02 08:37:48 hohndel Exp $ + +# ---------------------------------------------------------------------- +# platform-specific configuration parameters - edit linux.cf to change + +# platform: $TOG: linux.cf /main/36 1997/06/16 22:21:03 kaleb $ +# platform: $XFree86: xc/config/cf/linux.cf,v 3.57.2.18 1999/07/22 08:13:51 hohndel Exp $ + +# operating system: Linux 2.2.16eepro-smp i686 [ELF] (2.2.16) +# libc: (6.1.2) +# binutils: (29) + +# $XConsortium: lnxLib.rules /main/13 1996/09/28 16:11:01 rws $ +# $XFree86: xc/config/cf/lnxLib.rules,v 3.28.2.4 1998/12/18 11:56:08 dawes Exp $ + +# $XFree86: xc/config/cf/xfree86.cf,v 3.129.2.47 1999/08/03 09:41:30 hohndel Exp $ + +# $XConsortium: xfree86.cf /main/34 1996/12/06 11:45:18 rws $ + +LINKKITDIR = $(USRLIBDIR)/Server +XF98LINKKITDIR = $(USRLIBDIR)/Server + + XF86SRC = $(SERVERSRC)/hw/xfree86 + XF86ACCELSRC = $(XF86SRC)/accel + XF86COMSRC = $(XF86SRC)/common + XF86CONFIGSRC = $(XF86COMSRC) + XF86HWSRC = $(XF86SRC)/common_hw + XF86OSSRC = $(XF86SRC)/os-support + VGADRIVERSRC = $(XF86SRC)/vga256/drivers +VGA16DRIVERSRC = $(XF86SRC)/vga16/drivers + VGA2DRIVERSRC = $(XF86SRC)/vga2/drivers + MONODRIVERSRC = $(XF86SRC)/mono/drivers + S3DRIVERSRC = $(XF86SRC)/accel/s3/drivers + S3VDRIVERSRC = $(XF86SRC)/accel/s3_virge/drivers + + XF68SRC = $(SERVERSRC)/hw/xfree68 + XF68COMSRC = $(XF68SRC)/common + XF68CONFIGSRC = $(XF68COMSRC) + XF68OSSRC = $(XF68SRC)/os-support + + XF98SRC = $(SERVERSRC)/hw/xfree98 + XF98ACCELSRC = $(XF98SRC)/accel + XF98COMSRC = $(XF98SRC)/common + XF98CONFIGSRC = $(XF98COMSRC) + XF98HWSRC = $(XF98SRC)/common_hw/generic + XF98HWNECSRC = $(XF98SRC)/common_hw/nec + XF98HWPWSKBSRC = $(XF98SRC)/common_hw/pwskb + XF98HWPWLBSRC = $(XF98SRC)/common_hw/pwlb + XF98HWGA968SRC = $(XF98SRC)/common_hw/ga968 + XF98OSSRC = $(XF98SRC)/os-support + XF98VGADRIVERSRC = $(XF98SRC)/vga256/drivers +XF98VGA16DRIVERSRC = $(XF98SRC)/vga16/drivers + XF98VGA2DRIVERSRC = $(XF98SRC)/vga2/drivers + XF98MONODRIVERSRC = $(XF98SRC)/mono/drivers +XF98NECS3DRIVERSRC = $(XF98SRC)/accel/s3nec/drivers +XF98PWSKBDRIVERSRC = $(XF98SRC)/accel/s3pwskb/drivers + XF98PWLBDRIVERSRC = $(XF98SRC)/accel/s3pwlb/drivers +XF98GA968DRIVERSRC = $(XF98SRC)/accel/s3ga968/drivers + + XFREE86DOCDIR = $(DOCDIR) + XFREE86PSDOCDIR = $(DOCPSDIR) + XFREE86HTMLDOCDIR = $(DOCHTMLDIR) +XFREE86JAPANESEDOCDIR = $(DOCDIR)/Japanese + +# $XConsortium: xf86.rules /main/9 1996/10/31 14:54:26 kaleb $ +# $XFree86: xc/config/cf/xf86.rules,v 3.16.2.2 1999/07/23 09:00:21 hohndel Exp $ + +# ---------------------------------------------------------------------- +# site-specific configuration parameters that go after +# the platform-specific parameters - edit site.def to change + +# site: $XConsortium: site.def /main/revisionist/4 1996/12/31 08:02:07 kaleb $ +# site: $XFree86: xc/config/cf/site.def,v 3.17.2.1 1997/06/22 10:32:21 dawes Exp $ + +# --------------------------------------------------------------------- +# Imake rules for building libraries, programs, scripts, and data files +# rules: $TOG: Imake.rules /main/222 1997/07/17 20:04:40 kaleb $ +# rules: $XFree86: xc/config/cf/Imake.rules,v 3.33.2.10 1998/11/04 10:53:01 dawes Exp $ + + _NULLCMD_ = @ echo -n + +TKLIBNAME = tk + +TKLIBDIR = /usr/lib + +TCLLIBNAME = tcl + +TCLIBDIR = /usr/lib + +JPTKLIBNAME = + +JPTKLIBDIR = + +JPTCLLIBNAME = + +JPTCLIBDIR = + + PATHSEP = / + SHELL = /bin/sh + + TOP = . + CURRENT_DIR = . + + IMAKE = imake + DEPEND = gccmakedep + MKDIRHIER = mkdir -p + EXPORTLISTGEN = + CONFIGSRC = $(TOP)/config + IMAKESRC = $(CONFIGSRC)/imake + DEPENDSRC = $(CONFIGSRC)/util + + INCROOT = /usr/X11R6/include + USRLIBDIR = /usr/X11R6/lib + VARLIBDIR = /var/lib + SHLIBDIR = /usr/X11R6/lib + LINTLIBDIR = $(USRLIBDIR)/lint + MANPATH = /usr/X11R6/man + MANSOURCEPATH = $(MANPATH)/man + MANDIR = $(MANSOURCEPATH)1 + LIBMANDIR = $(MANSOURCEPATH)3 + FILEMANDIR = $(MANSOURCEPATH)5 + + AR = ar clq + BOOTSTRAPCFLAGS = + CC = gcc + AS = as + +.SUFFIXES: .cc + + CXX = c++ + CXXFILT = c++filt + CXXLIB = + CXXDEBUGFLAGS = -O2 -fno-strength-reduce +CXXDEPENDINCLUDES = + CXXEXTRA_DEFINES = +CXXEXTRA_INCLUDES = + CXXSTD_DEFINES = -Dlinux -D__i386__ -D_POSIX_C_SOURCE=199309L -D_POSIX_SOURCE -D_XOPEN_SOURCE=500L -D_BSD_SOURCE -D_SVID_SOURCE $(CXXPROJECT_DEFINES) + CXXOPTIONS = + CXXINCLUDES = $(INCLUDES) $(TOP_INCLUDES) $(CXXEXTRA_INCLUDES) + CXXDEFINES = $(CXXINCLUDES) $(CXXSTD_DEFINES) $(THREADS_CXXDEFINES) $(CXXEXTRA_DEFINES) $(DEFINES) + CXXFLAGS = $(CXXDEBUGFLAGS) $(CXXOPTIONS) $(THREADS_CXXFLAGS) $(CXXDEFINES) + + COMPRESS = compress + GZIPCMD = gzip + CPP = /lib/cpp $(STD_CPP_DEFINES) + PREPROCESSCMD = gcc -E $(STD_CPP_DEFINES) + INSTALL = install + INSTALLFLAGS = -c + LD = ld + LEX = flex -l + LEXLIB = -lfl + YACC = bison -y + CCYACC = bison -y + LINT = lint + LINTLIBFLAG = -C + LINTOPTS = -axz + LN = ln -s + MAKE = make + MV = mv -f + CP = cp + + RANLIB = ranlib + RANLIBINSTFLAGS = + + RM = rm -f + MANSUFFIX = 1x + LIBMANSUFFIX = 3x + FILEMANSUFFIX = 5x + TROFF = groff -Tps + NROFF = nroff + MSMACROS = -ms + MANMACROS = -man + TBL = tbl + EQN = eqn + NEQN = neqn + COL = col + COLFLAGS = -b + + DVIPS = dvips + LATEX = latex + + STD_INCLUDES = + STD_CPP_DEFINES = -traditional -Dlinux -D__i386__ -D_POSIX_C_SOURCE=199309L -D_POSIX_SOURCE -D_XOPEN_SOURCE=500L -D_BSD_SOURCE -D_SVID_SOURCE $(PROJECT_DEFINES) + STD_DEFINES = -Dlinux -D__i386__ -D_POSIX_C_SOURCE=199309L -D_POSIX_SOURCE -D_XOPEN_SOURCE=500L -D_BSD_SOURCE -D_SVID_SOURCE $(PROJECT_DEFINES) + EXTRA_LOAD_FLAGS = + EXTRA_LDOPTIONS = + EXTRA_LIBRARIES = + TAGS = ctags + + PARALLELMFLAGS = + + SHAREDCODEDEF = + SHLIBDEF = + + SHLIBLDFLAGS = -shared + + PICFLAGS = -fPIC + + CXXPICFLAGS = -fPIC + + PROTO_DEFINES = -DFUNCPROTO=15 -DNARROWPROTO + + INSTPGMFLAGS = -s + + INSTBINFLAGS = -m 0755 + INSTUIDFLAGS = -m 4711 + INSTLIBFLAGS = -m 0644 + INSTINCFLAGS = -m 0444 + INSTMANFLAGS = -m 0444 + INSTDATFLAGS = -m 0444 + INSTKMEMFLAGS = -m 4711 + + PROJECTROOT = /usr/X11R6 + + CDEBUGFLAGS = -O2 -fno-strength-reduce + CCOPTIONS = + + ALLINCLUDES = $(INCLUDES) $(EXTRA_INCLUDES) $(TOP_INCLUDES) $(STD_INCLUDES) + ALLDEFINES = $(ALLINCLUDES) $(STD_DEFINES) $(EXTRA_DEFINES) $(PROTO_DEFINES) $(THREADS_DEFINES) $(DEFINES) + CFLAGS = $(CDEBUGFLAGS) $(CCOPTIONS) $(THREADS_CFLAGS) $(ALLDEFINES) + LINTFLAGS = $(LINTOPTS) -DLINT $(ALLDEFINES) $(DEPEND_DEFINES) + LDPRELIB = -L$(USRLIBDIR) + LDPOSTLIB = + LDOPTIONS = $(CDEBUGFLAGS) $(CCOPTIONS) $(EXTRA_LDOPTIONS) $(THREADS_LDFLAGS) $(LOCAL_LDFLAGS) $(LDPRELIBS) + CXXLDOPTIONS = $(CXXDEBUGFLAGS) $(CXXOPTIONS) $(EXTRA_LDOPTIONS) $(THREADS_CXXLDFLAGS) $(LOCAL_LDFLAGS) $(LDPRELIBS) + + LDLIBS = $(LDPOSTLIBS) $(THREADS_LIBS) $(SYS_LIBRARIES) $(EXTRA_LIBRARIES) + + CCLINK = $(CC) + + CXXLINK = $(CXX) + + LDSTRIPFLAGS = -x + LDCOMBINEFLAGS = -r + DEPENDFLAGS = + +# Not sure this belongs here + TKLIBDIR = /usr/lib + TKINCDIR = /usr/include + TKLIBNAME = tk + TKLIBRARY = -L$(TKLIBDIR) -l$(TKLIBNAME) + TCLLIBDIR = /usr/lib + TCLINCDIR = /usr/include + TCLLIBNAME = tcl + TCLLIBRARY = -L$(TCLLIBDIR) -l$(TCLLIBNAME) + JPTKLIBDIR = + JPTKINCDIR = + JPTKLIBNAME = + JPTKLIBRARY = -L$(JPTKLIBDIR) -l$(JPTKLIBNAME) + JPTCLLIBDIR = + JPTCLINCDIR = + JPTCLLIBNAME = + JPTCLLIBRARY = -L$(JPTCLLIBDIR) -l$(JPTCLLIBNAME) + + MACROFILE = linux.cf + RM_CMD = $(RM) + + IMAKE_DEFINES = + + IRULESRC = $(CONFIGDIR) + IMAKE_CMD = $(IMAKE) -DUseInstalled -I$(IRULESRC) $(IMAKE_DEFINES) + + ICONFIGFILES = $(IRULESRC)/Imake.tmpl $(IRULESRC)/X11.tmpl $(IRULESRC)/site.def $(IRULESRC)/$(MACROFILE) $(IRULESRC)/xfree86.cf $(IRULESRC)/xf86.rules $(IRULESRC)/xf86site.def $(IRULESRC)/host.def $(EXTRA_ICONFIGFILES) + +# $TOG: X11.rules /main/4 1997/04/30 15:23:24 kaleb $ +# +# +# +# $XFree86: xc/config/cf/X11.rules,v 1.1.1.1.2.4 1999/04/21 07:20:58 hohndel Exp $ + +# ---------------------------------------------------------------------- +# X Window System Build Parameters and Rules +# $TOG: X11.tmpl /main/292 1997/05/20 10:05:59 kaleb $ +# +# +# +# +# $XFree86: xc/config/cf/X11.tmpl,v 1.8.2.9 1998/12/30 10:04:09 dawes Exp $ + +CONNECTION_FLAGS = -DUNIXCONN -DTCPCONN -DHAS_STICKY_DIR_BIT + +# ----------------------------------------------------------------------- +# X Window System make variables; these need to be coordinated with rules + + XTOP = $(TOP) + BINDIR = /usr/X11R6/bin + BUILDINCROOT = $(TOP)/exports + BUILDINCDIR = $(BUILDINCROOT)/include + BUILDINCTOP = ../.. + BUILDLIBDIR = $(TOP)/exports/lib + BUILDLIBTOP = ../.. + BUILDBINDIR = $(TOP)/exports/bin + BUILDBINTOP = ../.. + XBUILDINCROOT = $(XTOP)/exports + XBUILDINCDIR = $(XBUILDINCROOT)/include/X11 + XBUILDINCTOP = ../../.. + XBUILDBINDIR = $(XBUILDINCROOT)/bin + INCDIR = $(INCROOT) + ADMDIR = /usr/adm + LIBDIR = $(USRLIBDIR)/X11 + TOP_X_INCLUDES = + + VARDIR = /var/X11 + + DOCDIR = $(LIBDIR)/doc + DOCHTMLDIR = $(DOCDIR)/html + DOCPSDIR = $(DOCDIR)/PostScript + FONTDIR = $(LIBDIR)/fonts + XINITDIR = $(LIBDIR)/xinit + XDMDIR = $(LIBDIR)/xdm + XDMVARDIR = $(VARLIBDIR)/xdm + TWMDIR = $(LIBDIR)/twm + XSMDIR = $(LIBDIR)/xsm + NLSDIR = $(LIBDIR)/nls + XLOCALEDIR = $(LIBDIR)/locale + PEXAPIDIR = $(LIBDIR)/PEX + LBXPROXYDIR = $(LIBDIR)/lbxproxy + PROXYMANAGERDIR = $(LIBDIR)/proxymngr + XPRINTDIR = $(LIBDIR) + XAPPLOADDIR = $(LIBDIR)/app-defaults + FONTCFLAGS = -t + + INSTAPPFLAGS = $(INSTDATFLAGS) + + RGB = rgb + FONTC = bdftopcf + MKFONTDIR = mkfontdir + + DOCUTILSRC = $(XTOP)/doc/util + CLIENTSRC = $(TOP)/clients + DEMOSRC = $(TOP)/demos + XDOCMACROS = $(DOCUTILSRC)/macros.t + XIDXMACROS = $(DOCUTILSRC)/indexmacros.t + PROGRAMSRC = $(TOP)/programs + LIBSRC = $(XTOP)/lib + FONTSRC = $(XTOP)/fonts + INCLUDESRC = $(BUILDINCROOT)/include + XINCLUDESRC = $(INCLUDESRC)/X11 + SERVERSRC = $(XTOP)/programs/Xserver + CONTRIBSRC = $(XTOP)/../contrib + UNSUPPORTEDSRC = $(XTOP)/unsupported + DOCSRC = $(XTOP)/doc + RGBSRC = $(XTOP)/programs/rgb + BDFTOPCFSRC = $(PROGRAMSRC)/bdftopcf + MKFONTDIRSRC = $(PROGRAMSRC)/mkfontdir + FONTSERVERSRC = $(PROGRAMSRC)/xfs + FONTINCSRC = $(XTOP)/include/fonts + EXTINCSRC = $(XTOP)/include/extensions + TRANSCOMMSRC = $(LIBSRC)/xtrans + TRANS_INCLUDES = -I$(TRANSCOMMSRC) + + XENVLIBDIR = $(USRLIBDIR) + CLIENTENVSETUP = LD_LIBRARY_PATH=$(XENVLIBDIR) + +# $XConsortium: lnxLib.tmpl,v 1.5 95/01/11 21:44:44 kaleb Exp $ +# $XFree86: xc/config/cf/lnxLib.tmpl,v 3.9 1996/02/24 04:32:52 dawes Exp $ + + XLIBSRC = $(LIBSRC)/X11 + +SOXLIBREV = 6.1 +DEPXONLYLIB = +XONLYLIB = -lX11 + +LINTXONLY = $(LINTLIBDIR)/llib-lX11.ln + + XLIBONLY = $(XONLYLIB) + + XEXTLIBSRC = $(LIBSRC)/Xext + +SOXEXTREV = 6.3 +DEPEXTENSIONLIB = +EXTENSIONLIB = -lXext + +LINTEXTENSION = $(LINTLIBDIR)/llib-lXext.ln + +LINTEXTENSIONLIB = $(LINTEXTENSION) + DEPXLIB = $(DEPEXTENSIONLIB) $(DEPXONLYLIB) + XLIB = $(EXTENSIONLIB) $(XONLYLIB) + LINTXLIB = $(LINTXONLYLIB) + + XSSLIBSRC = $(LIBSRC)/Xss + +DEPXSSLIB = $(USRLIBDIR)/libXss.a +XSSLIB = -lXss + +LINTXSS = $(LINTLIBDIR)/llib-lXss.ln + + XXF86MISCLIBSRC = $(LIBSRC)/Xxf86misc + +DEPXXF86MISCLIB = $(USRLIBDIR)/libXxf86misc.a +XXF86MISCLIB = -lXxf86misc + +LINTXXF86MISC = $(LINTLIBDIR)/llib-lXxf86misc.ln + + XXF86VMLIBSRC = $(LIBSRC)/Xxf86vm + +DEPXXF86VMLIB = $(USRLIBDIR)/libXxf86vm.a +XXF86VMLIB = -lXxf86vm + +LINTXXF86VM = $(LINTLIBDIR)/llib-lXxf86vm.ln + + XXF86DGALIBSRC = $(LIBSRC)/Xxf86dga + +DEPXXF86DGALIB = $(USRLIBDIR)/libXxf86dga.a +XXF86DGALIB = -lXxf86dga + +LINTXXF86DGA = $(LINTLIBDIR)/llib-lXxf86dga.ln + + XDPMSLIBSRC = $(LIBSRC)/Xdpms + +DEPXDPMSLIB = $(USRLIBDIR)/libXdpms.a +XDPMSLIB = -lXdpms + +LINTXDPMS = $(LINTLIBDIR)/llib-lXdpms.ln + + XAUTHSRC = $(LIBSRC)/Xau + +DEPXAUTHLIB = $(USRLIBDIR)/libXau.a +XAUTHLIB = -lXau + +LINTXAUTH = $(LINTLIBDIR)/llib-lXau.ln + + XDMCPLIBSRC = $(LIBSRC)/Xdmcp + +DEPXDMCPLIB = $(USRLIBDIR)/libXdmcp.a +XDMCPLIB = -lXdmcp + +LINTXDMCP = $(LINTLIBDIR)/llib-lXdmcp.ln + + XMUSRC = $(LIBSRC)/Xmu + +SOXMUREV = 6.0 +DEPXMULIB = +XMULIB = -lXmu + +LINTXMU = $(LINTLIBDIR)/llib-lXmu.ln + + OLDXLIBSRC = $(LIBSRC)/oldX + +DEPOLDXLIB = $(USRLIBDIR)/liboldX.a +OLDXLIB = -loldX + +LINTOLDX = $(LINTLIBDIR)/llib-loldX.ln + + XPLIBSRC = $(LIBSRC)/Xp + +SOXPREV = 6.2 +DEPXPLIB = +XPLIB = -lXp + +LINTXP = $(LINTLIBDIR)/llib-lXp.ln + + TOOLKITSRC = $(LIBSRC)/Xt + +SOXTREV = 6.0 +DEPXTOOLONLYLIB = +XTOOLONLYLIB = -lXt + +LINTXTOOLONLY = $(LINTLIBDIR)/llib-lXt.ln + + DEPXTOOLLIB = $(DEPXTOOLONLYLIB) $(DEPSMLIB) $(DEPICELIB) + XTOOLLIB = $(XTOOLONLYLIB) $(SMLIB) $(ICELIB) + LINTXTOOLLIB = $(LINTXTOOLONLYLIB) + + XALIBSRC = $(LIBSRC)/Xa + +SOXAREV = 1.0 +DEPXALIB = +XALIB = -lXa + +LINTXA = $(LINTLIBDIR)/llib-lXa.ln + + AWIDGETSRC = $(LIBSRC)/Xaw + +SOXAWREV = 6.1 +DEPXAWLIB = +XAWLIB = -lXaw + +LINTXAW = $(LINTLIBDIR)/llib-lXaw.ln + + XILIBSRC = $(LIBSRC)/Xi + +SOXINPUTREV = 6.0 +DEPXILIB = +XILIB = -lXi + +LINTXI = $(LINTLIBDIR)/llib-lXi.ln + + XTESTLIBSRC = $(LIBSRC)/Xtst + +SOXTESTREV = 6.1 +DEPXTESTLIB = +XTESTLIB = -lXtst + +LINTXTEST = $(LINTLIBDIR)/llib-lXtst.ln + + PEXLIBSRC = $(LIBSRC)/PEX5 + +SOPEXREV = 6.0 +DEPPEXLIB = +PEXLIB = -lPEX5 + +LINTPEX = $(LINTLIBDIR)/llib-lPEX5.ln + + XIELIBSRC = $(LIBSRC)/XIE + +SOXIEREV = 6.0 +DEPXIELIB = +XIELIB = -lXIE + +LINTXIE = $(LINTLIBDIR)/llib-lXIE.ln + + PHIGSLIBSRC = $(LIBSRC)/PHIGS + +DEPPHIGSLIB = $(USRLIBDIR)/libphigs.a +PHIGSLIB = -lphigs + +LINTPHIGS = $(LINTLIBDIR)/llib-lphigs.ln + +DEPXBSDLIB = $(USRLIBDIR)/libXbsd.a +XBSDLIB = -lXbsd + +LINTXBSD = $(LINTLIBDIR)/llib-lXbsd.ln + + ICESRC = $(LIBSRC)/ICE + +SOICEREV = 6.3 +DEPICELIB = +ICELIB = -lICE + +LINTICE = $(LINTLIBDIR)/llib-lICE.ln + + SMSRC = $(LIBSRC)/SM + +SOSMREV = 6.0 +DEPSMLIB = +SMLIB = -lSM + +LINTSM = $(LINTLIBDIR)/llib-lSM.ln + + XKEYSRC = $(LIBSRC)/Xkey + +SOXKEYREV = 6.0 +DEPXKEYLIB = +XKEYLIB = -lXkey + +LINTXKEY = $(LINTLIBDIR)/llib-lXkey.ln + + FSLIBSRC = $(LIBSRC)/FS + +DEPFSLIB = $(USRLIBDIR)/libFS.a +FSLIB = -lFS + +LINTFS = $(LINTLIBDIR)/llib-lFS.ln + + FONTLIBSRC = $(LIBSRC)/font + +DEPFONTLIB = $(USRLIBDIR)/libfont.a +FONTLIB = -lfont + +LINTFONT = $(LINTLIBDIR)/llib-lfont.ln + + XPMLIBSRC = $(LIBSRC)/Xpm + +DEPXPMLIB = $(USRLIBDIR)/libXpm.a +XPMLIB = -lXpm + +LINTXPM = $(LINTLIBDIR)/llib-lXpm.ln + + XKBFILELIBSRC = $(LIBSRC)/xkbfile + +DEPXKBFILELIB = $(USRLIBDIR)/libxkbfile.a +XKBFILELIB = -lxkbfile + +LINTXKBFILE = $(LINTLIBDIR)/llib-lxkbfile.ln + + XKBCOMPCMD = xkbcomp + + XKBUILIBSRC = $(LIBSRC)/xkbui + +DEPXKBUILIB = $(USRLIBDIR)/libxkbui.a +XKBUILIB = -lxkbui + +LINTXKBUI = $(LINTLIBDIR)/llib-lxkbui.ln + + DEPLIBS = $(DEPXAWLIB) $(DEPXMULIB) $(DEPXTOOLLIB) $(DEPXLIB) + + DEPLIBS1 = $(DEPLIBS) + DEPLIBS2 = $(DEPLIBS) + DEPLIBS3 = $(DEPLIBS) + DEPLIBS4 = $(DEPLIBS) + DEPLIBS5 = $(DEPLIBS) + DEPLIBS6 = $(DEPLIBS) + DEPLIBS7 = $(DEPLIBS) + DEPLIBS8 = $(DEPLIBS) + DEPLIBS9 = $(DEPLIBS) + DEPLIBS10 = $(DEPLIBS) + +XMULIBONLY = -lXmu +XMULIB = $(XMULIBONLY) $(XTOOLLIB) $(XLIB) + + CONFIGDIR = $(LIBDIR)/config + + USRLIBDIRPATH = $(USRLIBDIR) + LDPRELIBS = -L$(USRLIBDIR) + LDPOSTLIBS = + TOP_INCLUDES = -I$(INCROOT) $(TOP_X_INCLUDES) + PROJECT_DEFINES = + +CXXPROJECT_DEFINES = + +# ---------------------------------------------------------------------- +# start of Imakefile + + INCLUDES = -I$(TOP) -I$(TOP)/X11 +LOCAL_LIBRARIES = $(XLIB) + SRCS = xsel.c + OBJS = xsel.o + CFLAGS += -Wall + + PROGRAM = xsel + +all:: xsel + +xsel: $(OBJS) $(DEPLIBS) + $(RM) $@ + $(CCLINK) -o $@ $(LDOPTIONS) $(OBJS) $(LOCAL_LIBRARIES) $(LDLIBS) $(EXTRA_LOAD_FLAGS) + +install:: xsel + @if [ -d $(DESTDIR)$(BINDIR) ]; then set +x; \ + else (set -x; $(MKDIRHIER) $(DESTDIR)$(BINDIR)); fi + $(INSTALL) $(INSTALLFLAGS) $(INSTPGMFLAGS) xsel $(DESTDIR)$(BINDIR)/xsel + +install.man:: xsel.man + @if [ -d $(DESTDIR)$(MANDIR) ]; then set +x; \ + else (set -x; $(MKDIRHIER) $(DESTDIR)$(MANDIR)); fi + $(INSTALL) $(INSTALLFLAGS) $(INSTMANFLAGS) xsel.man $(DESTDIR)$(MANDIR)/xsel.$(MANSUFFIX) + +depend:: + $(DEPEND) $(DEPENDFLAGS) -- $(ALLDEFINES) $(DEPEND_DEFINES) -- $(SRCS) + +lint: + $(LINT) $(LINTFLAGS) $(SRCS) $(LINTLIBS) +lint1: + $(LINT) $(LINTFLAGS) $(FILE) $(LINTLIBS) + +clean:: + $(RM) xsel + +shar: + shar Imakefile xsel.c xsel.h xsel.1x >xsel.shar + +# ---------------------------------------------------------------------- +# common rules for all Makefiles - do not edit + +.c.i: + $(RM) $@ + $(CC) -E $(CFLAGS) $(_NOOP_) $*.c > $@ + +emptyrule:: + +clean:: + $(RM) *.CKP *.ln *.BAK *.bak *.o core errs ,* *~ *.a .emacs_* tags TAGS make.log MakeOut "#"* + +Makefile:: + -@if [ -f Makefile ]; then set -x; \ + $(RM) Makefile.bak; $(MV) Makefile Makefile.bak; \ + else exit 0; fi + $(IMAKE_CMD) -DTOPDIR=$(TOP) -DCURDIR=$(CURRENT_DIR) + +tags:: + $(TAGS) -w *.[ch] + $(TAGS) -xw *.[ch] > TAGS + +man_keywords:: + +# ---------------------------------------------------------------------- +# empty rules for directories that do not have SUBDIRS - do not edit + +install:: + @echo "install in $(CURRENT_DIR) done" + +install.man:: + @echo "install.man in $(CURRENT_DIR) done" + +install.linkkit:: + @echo "install.linkkit in $(CURRENT_DIR) done" + +Makefiles:: + +includes:: + +depend:: + +# ---------------------------------------------------------------------- +# dependencies generated by makedepend + +# DO NOT DELETE +xsel.o: xsel.c /usr/include/stdio.h /usr/include/features.h \ + /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \ + /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stddef.h \ + /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stdarg.h \ + /usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \ + /usr/include/bits/sched.h /usr/include/libio.h \ + /usr/include/_G_config.h /usr/include/bits/stdio_lim.h \ + /usr/include/getopt.h /usr/include/stdlib.h /usr/include/sys/types.h \ + /usr/include/time.h /usr/include/endian.h /usr/include/bits/endian.h \ + /usr/include/sys/select.h /usr/include/bits/select.h \ + /usr/include/bits/sigset.h /usr/include/sys/sysmacros.h \ + /usr/include/alloca.h /usr/include/errno.h /usr/include/bits/errno.h \ + /usr/include/linux/errno.h /usr/include/asm/errno.h \ + /usr/include/unistd.h /usr/include/bits/posix_opt.h \ + /usr/include/bits/environments.h /usr/include/bits/wordsize.h \ + /usr/include/bits/confname.h /usr/include/string.h /usr/include/pwd.h \ + /usr/include/sys/stat.h /usr/include/bits/stat.h \ + /usr/include/sys/time.h /usr/include/bits/time.h /usr/include/fcntl.h \ + /usr/include/bits/fcntl.h /usr/include/setjmp.h \ + /usr/include/bits/setjmp.h /usr/include/signal.h \ + /usr/include/bits/signum.h /usr/include/bits/siginfo.h \ + /usr/include/bits/sigaction.h /usr/include/bits/sigcontext.h \ + /usr/include/asm/sigcontext.h /usr/include/bits/sigstack.h \ + /usr/include/bits/sigthread.h /usr/X11R6/include/X11/StringDefs.h \ + /usr/X11R6/include/X11/Xlib.h /usr/X11R6/include/X11/X.h \ + /usr/X11R6/include/X11/Xfuncproto.h /usr/X11R6/include/X11/Xosdefs.h \ + /usr/X11R6/include/X11/Intrinsic.h /usr/X11R6/include/X11/Xutil.h \ + /usr/X11R6/include/X11/Xresource.h /usr/X11R6/include/X11/Core.h \ + /usr/X11R6/include/X11/Composite.h \ + /usr/X11R6/include/X11/Constraint.h /usr/X11R6/include/X11/Object.h \ + /usr/X11R6/include/X11/RectObj.h /usr/X11R6/include/X11/Xatom.h \ + xsel.h diff --git a/xsel.1x b/xsel.1x @@ -0,0 +1,134 @@ +.TH XSEL 1 "July 2001" +.SH NAME +xsel \- manipulate the X selection. +.SH SYNOPSIS +.B xsel +[\fIOPTION\fR]... +.SH DESCRIPTION +.PP +Retrieve and set the X selection. +.PP +The X server maintains three selections, called \fIPRIMARY\fR, +\fISECONDARY\fR and \fICLIPBOARD\fR. The PRIMARY selection is conventionally +used to implement copying and pasting via the middle mouse button. The +SECONDARY and CLIPBOARD selections are less frequently used by application +programs. This program operates on the PRIMARY selection unless otherwise +specified. +.PP +By default, this program outputs the selection without modification if both +standard input and standard output are terminals (ttys). Otherwise, the +current selection is output if standard output is not a terminal +(tty), and the selection is set from standard input if standard input +is not a terminal (tty). If any input or output options are given then +the program behaves only in the requested mode. +.PP +If both input and output is required then the previous selection is +output before being replaced by the contents of standard input. + +.PP +\fBInput options\fR +.TP +\fB\-a\fR, \fB\-\-append\fR +append standard input to the selection. Implies \fB\-i\fR. +.TP +\fB\-f\fR, \fB\-\-follow\fR +append to selection as standard input grows. Implies \fB\-i\fR. +.TP +\fB\-i\fR, \fB\-\-input\fR +read standard input into the selection. + +.PP +\fBOutput options\fR +.TP +\fB\-o\fR, \fB\-\-output\fR +write the selection to standard output. + +.PP +\fBAction options\fR +.TP +\fB\-c\fR, \fB\-\-clear\fR +clear the selection. Overrides all \fIinput\fR options. +.TP +\fB\-d\fR, \fB\-\-delete\fR +Request that the current selection be deleted. This not only clears the +selection, but also requests to the program in which the selection +resides that the selected contents be deleted. Overrides all \fIinput\fR +options. + +.PP +\fBSelection options\fR +.TP +\fB\-p\fR, \fB\-\-primary\fR +operate on the PRIMARY selection (default). +.TP +\fB\-s\fR, \fB\-\-secondary\fR +operate on the SECONDARY selection. +.TP +\fB\-b\fR, \fB\-\-clipboard\fR +operate on the CLIPBOARD selection. + +.TP +\fB\-k\fR, \fB\-\-keep\fR +Do not modify the selections, but make the PRIMARY and SECONDARY selections +persist even after the programs they were selected in exit. (Conventionally, +the CLIPBOARD selection is persisted by a separate special purpose program +such as \fIxclipboard(1)\fR). Ignores all \fIinput\fR and \fIoutput\fR +options. +.TP +\fB\-x\fR, \fB\-\-exchange\fR +exchange the PRIMARY and SECONDARY selections. Ignores all \fIinput\fR +and \fIoutput\fR options. + +.PP +\fBX options\fR +.TP +\fB\-d\fR \fIdisplayname\fR, \fB\-\-display\fR \fIdisplayname\fR +specify the server to use; see \fIX(1)\fP. +.TP +\fB\-t\fR \fIms\fR, \fB\-\-selectionTimeout\fR \fIms\fR +Specify the timeout in milliseconds within which the selection must be +retrieved. A value of \fB0\fR (zero) specifies no timeout (default). + +.PP +\fBMiscellaneous options\fR +.TP +\fB\-l\fR \fIfilename\fR, \fB\-\-logfile\fR \fIfilename\fR +Specify the file to log errors to when detached. (Default +\fI$HOME/.xsel.log\fR) +.TP +\fB\-n\fR, \fB\-\-nodetach\fR +Do not detach from the controlling terminal. Without this option, xsel will +fork to become a background process in \fIinput\fR, \fIexchange\fR and +\fIkeep\fR modes. +.TP +\fB\-h\fR, \fB\-\-help\fR +display usage information and exit +.TP +\fB\-v\fR, \fB\-\-verbose\fR +Print informative messages. Additional instances of \fI-v\fR raise the +debugging level, ie. print more information. +.TP +\fB\-v\fR, \fB\-\-version\fR +output version information and exit +.PP +.SH NOTES +.PP +\fIThere is no X selection buffer.\fR The selection mechanism in X11 is +an interclient communication mediated by the X server each time any +program wishes to know the selection contents, eg. to perform a middle +mouse button paste. In order to implement modification of the selection(s) +(in \fIinput\fR, \fIkeep\fR and \fIexchange\fR modes) this program detaches +from the terminal, spawning a child process to supply the new selection(s) +on demand. This child exits immediately when any other program takes over +the selection(s), eg. when the user next selects some text in a terminal +window or by running \fBxsel -c\fR. +.PP +.SH STANDARDS +xsel conforms to the Inter-Client Communication Conventions Manual +Version 2.0 (ICCCM2). +.PP +.SH "SEE ALSO" +X(1), Xserver(1), xset(1), xclipboard(1), xpaste(1) +.BR +.SH AUTHOR +Conrad Parker <conrad@vergenet.net>, July 2001 diff --git a/xsel.c b/xsel.c @@ -0,0 +1,1954 @@ +/* + * xsel -- manipulate the X selection + * Copyright (C) 2001 Conrad Parker <conrad@vergenet.net> + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <pwd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <fcntl.h> +#include <sys/time.h> +#include <setjmp.h> +#include <signal.h> +#include <X11/StringDefs.h> +#include <X11/Xlib.h> +#include <X11/Intrinsic.h> +#include <X11/Xatom.h> + +#include "xsel.h" + + +/* The name we were invoked as (argv[0]) */ +static char * progname; + +/* Verbosity level for debugging */ +static int debug_level = DEBUG_LEVEL; + +/* Our X Display and Window */ +static Display * display; +static Window window; + +/* Maxmimum request size supported by this X server */ +static long max_req; + +/* Our timestamp for all operations */ +static Time timestamp; + +static Atom timestamp_atom; /* The TIMESTAMP atom */ +static Atom multiple_atom; /* The MULTIPLE atom */ +static Atom targets_atom; /* The TARGETS atom */ +static Atom delete_atom; /* The DELETE atom */ +static Atom incr_atom; /* The INCR atom */ +static Atom null_atom; /* The NULL atom */ +static Atom text_atom; /* The TEXT atom */ + +/* Number of selection targets served by this. + * (MULTIPLE, INCR, TARGETS, TIMESTAMP, DELETE, TEXT and STRING) */ +#define NUM_TARGETS 7 +static Atom supported_targets[NUM_TARGETS]; + +/* do_follow: Follow mode for output */ +static Boolean do_follow = False; + +/* nodaemon: Disable daemon mode if True. */ +static Boolean no_daemon = False; + +/* logfile: name of file to log error messages to when detached */ +static char logfile[MAXFNAME]; + +/* fstat() on stdin and stdout */ +static struct stat in_statbuf, out_statbuf; + +static int total_input = 0; +static int current_alloc = 0; + +static long timeout = 0; +static struct itimerval timer; + +/* + * usage () + * + * print usage information. + */ +static void +usage (void) +{ + printf ("Usage: xsel [options]\n"); + printf ("Manipulate the X selection.\n\n"); + printf ("By default the current selection is output and not modified if both\n"); + printf ("standard input and standard output are terminals (ttys). Otherwise,\n"); + printf ("the current selection is output if standard output is not a terminal\n"); + printf ("(tty), and the selection is set from standard input if standard input\n"); + printf ("is not a terminal (tty). If any input or output options are given then\n"); + printf ("the program behaves only in the requested mode.\n\n"); + printf ("If both input and output is required then the previous selection is\n"); + printf ("output before being replaced by the contents of standard input.\n\n"); + printf ("Input options\n"); + printf (" -a, --append Append standard input to the selection\n"); + printf (" -f, --follow Append to selection as standard input grows\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 ("Action options\n"); + printf (" -c, --clear Clear the selection\n"); + printf (" -d, --delete Request that the selection be cleared and that\n"); + printf (" the application owning it delete its contents\n\n"); + printf ("Selection options\n"); + printf (" -p, --primary Operate on the PRIMARY selection (default)\n"); + printf (" -s, --secondary Operate on the SECONDARY selection\n"); + printf (" -b, --clipboard Operate on the CLIPBOARD selection\n\n"); + printf (" -k, --keep Do not modify the selections, but make the PRIMARY\n"); + printf (" and SECONDARY selections persist even after the\n"); + printf (" programs they were selected in exit.\n"); + printf (" -x, --exchange Exchange the PRIMARY and SECONDARY selections\n\n"); + printf ("X options\n"); + printf (" --display displayname\n"); + printf (" Specify the connection to the X server\n"); + printf (" -t ms, --selectionTimeout ms\n"); + printf (" Specify the timeout in milliseconds within which the\n"); + printf (" selection must be retrieved. A value of 0 (zero)\n"); + printf (" specifies no timeout (default)\n\n"); + printf ("Miscellaneous options\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"); + printf (" process in input, exchange and keep modes.\n\n"); + printf (" -h, --help Display this help and exit\n"); + printf (" -v, --verbose Print informative messages\n"); + printf (" --version Output version information and exit\n\n"); + printf ("Please report bugs to <conrad@vergenet.net>.\n"); +} + +/* + * exit_err (fmt) + * + * Print a formatted error message and errno information to stderr, + * then exit with return code 1. + */ +static void +exit_err (const char * fmt, ...) +{ + va_list ap; + int errno_save; + char buf[MAXLINE]; + int n; + + errno_save = errno; + + va_start (ap, fmt); + + snprintf (buf, MAXLINE, "%s: ", progname); + n = strlen (buf); + + vsnprintf (buf+n, MAXLINE-n, fmt, ap); + n = strlen (buf); + + snprintf (buf+n, MAXLINE-n, ": %s\n", strerror (errno_save)); + + fflush (stdout); /* in case stdout and stderr are the same */ + fputs (buf, stderr); + fflush (NULL); + + va_end (ap); + exit (1); +} + +/* + * print_err (fmt) + * + * Print a formatted error message to stderr. + */ +static void +print_err (const char * fmt, ...) +{ + va_list ap; + int errno_save; + char buf[MAXLINE]; + int n; + + errno_save = errno; + + va_start (ap, fmt); + + snprintf (buf, MAXLINE, "%s: ", progname); + n = strlen (buf); + + vsnprintf (buf+n, MAXLINE-n, fmt, ap); + n = strlen (buf); + + fflush (stdout); /* in case stdout and stderr are the same */ + fputs (buf, stderr); + fputc ('\n', stderr); + fflush (NULL); + + va_end (ap); +} + +/* + * print_debug (level, fmt) + * + * Print a formatted debugging message of level 'level' to stderr + */ +#define print_debug(x,y...) {if (x <= debug_level) print_err (y);} + +/* + * get_atom_name (atom) + * + * Returns a string with a printable name for the Atom 'atom'. + */ +static unsigned char * +get_atom_name (Atom atom) +{ + if (atom == None) return "None"; + if (atom == XA_STRING) return "STRING"; + if (atom == XA_PRIMARY) return "PRIMARY"; + if (atom == XA_SECONDARY) return "SECONDARY"; + if (atom == timestamp_atom) return "TIMESTAMP"; + if (atom == multiple_atom) return "MULTIPLE"; + if (atom == targets_atom) return "TARGETS"; + if (atom == delete_atom) return "DELETE"; + if (atom == incr_atom) return "INCR"; + if (atom == null_atom) return "NULL"; + if (atom == text_atom) return "TEXT"; + if (atom == XInternAtom (display, "XSEL_DATA", True)) return "XSEL_DATA"; + + return "<unknown atom>"; +} + +/* + * debug_property (level, requestor, property, target, length) + * + * Print debugging information (at level 'level') about a property received. + */ +static void +debug_property (int level, Window requestor, Atom property, Atom target, + int length) +{ + print_debug (level, "Got window property: requestor 0x%x, property 0x%x, target 0x%x %s, length %d bytes", requestor, property, target, get_atom_name (target), length); +} + +/* + * xs_malloc (size) + * + * Malloc wrapper. Always returns a successful allocation. Exits if the + * allocation didn't succeed. + */ +static void * +xs_malloc (size_t size) +{ + void * ret; + + if ((ret = malloc (size)) == NULL) { + exit_err ("malloc error"); + } + + return ret; +} + +/* + * get_homedir () + * + * Get the user's home directory. + */ +static char * +get_homedir (void) +{ + uid_t uid; + char * username, * homedir; + struct passwd * pw; + + if ((homedir = getenv ("HOME")) != NULL) { + return homedir; + } + + /* else ... go hunting for it */ + uid = getuid (); + + username = getenv ("LOGNAME"); + if (!username) username = getenv ("USER"); + + if (username) { + pw = getpwnam (username); + if (pw && pw->pw_uid == uid) goto gotpw; + } + + pw = getpwuid (uid); + +gotpw: + + if (!pw) { + exit_err ("error retrieving passwd entry"); + } + + homedir = strdup (pw->pw_dir); + + return homedir; +} + +/* + * become_daemon () + * + * Perform the required procedure to become a daemon process, as + * outlined in the Unix programming FAQ: + * http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 + * and open a logfile. + */ +static void +become_daemon (void) +{ + pid_t pid; + int null_r_fd, null_w_fd, log_fd; + char * homedir; + + if (no_daemon) return; + + homedir = get_homedir (); + + /* Check that we can open a logfile before continuing */ + + /* If the user has specified a --logfile, use that ... */ + if (logfile[0] == '\0') { + /* ... otherwise use the default logfile */ + snprintf (logfile, MAXFNAME, "%s/.xsel.log", homedir); + } + + /* Make sure to create the logfile with sane permissions */ + log_fd = open (logfile, O_WRONLY|O_APPEND|O_CREAT, 0600); + if (log_fd == -1) { + exit_err ("error opening logfile %s for writing", logfile); + } + print_debug (D_INFO, "opened logfile %s", logfile); + + if ((pid = fork()) == -1) { + exit_err ("error forking"); + } else if (pid > 0) { + _exit (0); + } + + if (setsid () == -1) { + exit_err ("setsid error"); + } + + if ((pid = fork()) == -1) { + exit_err ("error forking"); + } else if (pid > 0) { + _exit (0); + } + + umask (0); + + if (chdir (homedir) == -1) { + print_debug (D_WARN, "Could not chdir to %s\n", homedir); + if (chdir ("/") == -1) { + exit_err ("Error chdir to /"); + } + } + + /* dup2 /dev/null on stdin unless following input */ + if (!do_follow) { + null_r_fd = open ("/dev/null", O_RDONLY); + if (null_r_fd == -1) { + exit_err ("error opening /dev/null for reading"); + } + if (dup2 (null_r_fd, 0) == -1) { + exit_err ("error duplicating /dev/null on stdin"); + } + } + + /* dup2 /dev/null on stdout */ + null_w_fd = open ("/dev/null", O_WRONLY|O_APPEND); + if (null_w_fd == -1) { + exit_err ("error opening /dev/null for writing"); + } + if (dup2 (null_w_fd, 1) == -1) { + exit_err ("error duplicating /dev/null on stdout"); + } + + /* dup2 logfile on stderr */ + if (dup2 (log_fd, 2) == -1) { + exit_err ("error duplicating logfile %s on stderr", logfile); + } +} + +/* + * get_timestamp () + * + * Get the current X server time. + * + * This is done by doing a zero-length append to a random property of the + * window, and checking the time on the subsequent PropertyNotify event. + * + * PRECONDITION: the window must have PropertyChangeMask set. + */ +static Time +get_timestamp (void) +{ + XEvent event; + + XChangeProperty (display, window, XA_WM_NAME, XA_STRING, 8, + PropModeAppend, NULL, 0); + + while (1) { + XNextEvent (display, &event); + + if (event.type == PropertyNotify) + return event.xproperty.time; + } +} + +/* + * SELECTION RETRIEVAL + * =================== + * + * The following functions implement retrieval of an X selection, + * optionally within a user-specified timeout. + * + * + * Selection timeout handling. + * --------------------------- + * + * The selection retrieval can time out if no response is received within + * a user-specified time limit. In order to ensure we time the entire + * selection retrieval, we use an interval timer and catch SIGVTALRM. + * [Calling select() on the XConnectionNumber would only provide a timeout + * to the first XEvent.] + */ + +/* The jmp_buf to longjmp out of the signal handler */ +static jmp_buf env_alrm; + +/* + * alarm_handler (sig) + * + * Signal handler for catching SIGVTALRM. + */ +static void +alarm_handler (int sig) +{ + siglongjmp (env_alrm, 1); +} + +/* + * get_append_property () + * + * Get a window property and append its data to a buffer at a given offset + * pointed to by *offset. 'offset' is modified by this routine to point to + * the end of the data. + * + * Returns True if more data is available for receipt. + * + * If an error is encountered, the buffer is free'd. + */ +static Boolean +get_append_property (XSelectionEvent * xsl, unsigned char ** buffer, + int * offset, int * alloc) +{ + unsigned char * ptr; + Atom target; + int format; + unsigned long bytesafter, length; + unsigned char * value; + + XGetWindowProperty (xsl->display, xsl->requestor, xsl->property, + 0L, 1000000, True, (Atom)AnyPropertyType, + &target, &format, &length, &bytesafter, &value); + + debug_property (D_TRACE, xsl->requestor, xsl->property, target, length); + + if (target != XA_STRING) { + print_debug (D_OBSC, "target %s not XA_STRING in get_append_property()", + get_atom_name (target)); + free (*buffer); + *buffer = NULL; + return False; + } else if (length == 0) { + /* A length of 0 indicates the end of the transfer */ + print_debug (D_TRACE, "Got zero length property; end of INCR transfer"); + return False; + } else if (format == 8) { + if (*offset + length > *alloc) { + *alloc = *offset + length; + if ((*buffer = realloc (*buffer, *alloc)) == NULL) { + exit_err ("realloc error"); + } + } + ptr = *buffer + *offset; + strncpy (ptr, value, length); + *offset += length; + print_debug (D_TRACE, "Appended %d bytes to buffer\n", length); + } else { + print_debug (D_WARN, "Retrieved non-8-bit data\n"); + } + + return True; +} + + +/* + * wait_incr_selection (selection) + * + * Retrieve a property of target type INCR. Perform incremental retrieval + * and return the resulting data. + */ +static unsigned char * +wait_incr_selection (Atom selection, XSelectionEvent * xsl, int init_alloc) +{ + XEvent event; + unsigned char * incr_base = NULL, * incr_ptr = NULL; + int incr_alloc = 0, incr_xfer = 0; + Boolean wait_prop = True; + + print_debug (D_TRACE, "Initialising incremental retrieval of at least %d bytes\n", init_alloc); + + /* Take an interest in the requestor */ + XSelectInput (xsl->display, xsl->requestor, PropertyChangeMask); + + incr_alloc = init_alloc; + incr_base = xs_malloc (incr_alloc); + incr_ptr = incr_base; + + print_debug (D_TRACE, "Deleting property that informed of INCR transfer"); + XDeleteProperty (xsl->display, xsl->requestor, xsl->property); + + print_debug (D_TRACE, "Waiting on PropertyNotify events"); + while (wait_prop) { + XNextEvent (xsl->display, &event); + + switch (event.type) { + case PropertyNotify: + if (event.xproperty.state != PropertyNewValue) break; + + wait_prop = get_append_property (xsl, &incr_base, &incr_xfer, + &incr_alloc); + break; + default: + break; + } + } + + /* when zero length found, finish up & delete last */ + XDeleteProperty (xsl->display, xsl->requestor, xsl->property); + + print_debug (D_TRACE, "Finished INCR retrieval"); + + return incr_base; +} + +/* + * wait_selection (selection, request_target) + * + * Block until we receive a SelectionNotify event, and return its + * contents; or NULL in the case of a deletion or error. This assumes we + * have already called XConvertSelection, requesting a string (explicitly + * XA_STRING) or deletion (delete_atom). + */ +static unsigned char * +wait_selection (Atom selection, Atom request_target) +{ + XEvent event; + Atom target; + int format; + unsigned long bytesafter, length; + unsigned char * value, * retval = NULL; + Boolean keep_waiting = True; + + while (keep_waiting) { + XNextEvent (display, &event); + + switch (event.type) { + case SelectionNotify: + if (event.xselection.selection != selection) break; + + if (event.xselection.property == None) { + print_debug (D_WARN, "Conversion refused"); + value = NULL; + keep_waiting = False; + } else if (event.xselection.property == null_atom && + request_target == delete_atom) { + } else { + XGetWindowProperty (event.xselection.display, + event.xselection.requestor, + event.xselection.property, 0L, 1000000, + False, (Atom)AnyPropertyType, &target, + &format, &length, &bytesafter, &value); + + debug_property (D_TRACE, event.xselection.requestor, + event.xselection.property, target, length); + + if (target == incr_atom) { + /* Handle INCR transfers */ + retval = wait_incr_selection (selection, &event.xselection, + *(int *)value); + keep_waiting = False; + } else if (target != XA_STRING && request_target != delete_atom) { + /* Report non-TEXT atoms */ + print_debug (D_WARN, "Selection (type %s) is not a string.", + get_atom_name (target)); + free (retval); + retval = NULL; + keep_waiting = False; + } else { + retval = strdup (value); + XFree (value); + keep_waiting = False; + } + + XDeleteProperty (event.xselection.display, + event.xselection.requestor, + event.xselection.property); + + } + break; + default: + break; + } + } + + /* Now that we've received the SelectionNotify event, clear any + * remaining timeout. */ + if (timeout > 0) { + setitimer (ITIMER_VIRTUAL, (struct itimerval *)0, (struct itimerval *)0); + } + + return retval; +} + +/* + * get_selection (selection, request_target) + * + * Retrieves the specified selection and returns its value. + * + * If a non-zero timeout is specified then set a virtual interval + * timer. Return NULL and print an error message if the timeout + * expires before the selection has been retrieved. + */ +static unsigned char * +get_selection (Atom selection, Atom request_target) +{ + Atom prop; + unsigned char * retval; + + prop = XInternAtom (display, "XSEL_DATA", False); + XConvertSelection (display, selection, request_target, prop, window, + timestamp); + XSync (display, False); + + if (timeout > 0) { + if (signal (SIGVTALRM, alarm_handler) == SIG_ERR) { + exit_err ("error setting timeout handler"); + } + + timer.it_interval.tv_sec = 0; + timer.it_interval.tv_usec = timeout; + timer.it_value.tv_sec = 0; + timer.it_value.tv_usec = timeout; + + if (sigsetjmp (env_alrm, 0) == 0) { + setitimer (ITIMER_VIRTUAL, &timer, (struct itimerval *)0); + retval = wait_selection (selection, request_target); + } else { + print_debug (D_WARN, "selection timed out"); + retval = NULL; + } + } else { + retval = wait_selection (selection, request_target); + } + + return retval; +} + +/* + * SELECTION SETTING + * ================= + * + * The following functions allow a given selection to be set, appended to + * or cleared, or to exchange the primary and secondary selections. + */ + +/* + * copy_sel (s) + * + * Copy a string into a new selection buffer, and intitialise + * current_alloc and total_input to exactly its length. + */ +static unsigned char * +copy_sel (unsigned char * s) +{ + unsigned char * new_sel = NULL; + + new_sel = strdup (s); + current_alloc = total_input = strlen (s); + + return new_sel; +} + +/* + * read_input (read_buffer, do_select) + * + * Read input from stdin into the specified read_buffer. + * + * read_buffer must have been dynamically allocated before calling this + * function, or be NULL. Input is read until end-of-file is reached, and + * read_buffer will be reallocated to accomodate the entire contents of + * the input. read_buffer, which may have been reallocated, is returned + * upon completion. + * + * If 'do_select' is True, this function will first check if any data + * is available for reading, and return immediately if not. + */ +static unsigned char * +read_input (unsigned char * read_buffer, Boolean do_select) +{ + int insize = in_statbuf.st_blksize; + unsigned char * new_buffer = NULL; + int d, fatal = 0, nfd; + ssize_t n; + fd_set fds; + struct timeval select_timeout; + + if (do_select) { +try_read: + /* Check if data is available for reading -- if not, return immediately */ + FD_ZERO (&fds); + FD_SET (0, &fds); + + select_timeout.tv_sec = (time_t)0; + select_timeout.tv_usec = (time_t)0; + + nfd = select (1, &fds, NULL, NULL, &select_timeout); + if (nfd == -1) { + if (errno == EINTR) goto try_read; + else exit_err ("select error"); + } else if (nfd == 0) { + print_debug (D_TRACE, "No data available for reading"); + return read_buffer; + } + } + + do { + /* check if buffer is full */ + if (current_alloc == total_input) { + if ((d = (current_alloc % insize)) != 0) current_alloc += (insize-d); + current_alloc *= 2; + new_buffer = realloc (read_buffer, current_alloc); + if (new_buffer == NULL) { + exit_err ("realloc error"); + } + read_buffer = new_buffer; + } + + /* read the remaining data, up to the optimal block length */ + n = read (0, &read_buffer[total_input], + MIN(current_alloc - total_input, insize)); + if (n == -1) { + switch (errno) { + case EAGAIN: + case EINTR: + break; + default: + perror ("read error"); + fatal = 1; + break; + } + } + total_input += n; + } while (n != 0 && !fatal); + + read_buffer[total_input] = '\0'; + + print_debug (D_TRACE, "Accumulated %d bytes input", total_input); + + return read_buffer; +} + +/* + * initialise_read (read_buffer) + * + * Initialises the read_buffer and the state variable current_alloc. + * read_buffer is reallocated to accomodate either the entire input + * if stdin is a regular file, or at least one block of input otherwise. + * If the supplied read_buffer is NULL, a new buffer will be allocated. + */ +static unsigned char * +initialise_read (unsigned char * read_buffer) +{ + int insize = in_statbuf.st_blksize; + unsigned char * new_buffer = NULL; + + if (S_ISREG (in_statbuf.st_mode)) { + current_alloc += in_statbuf.st_size; + } else { + current_alloc += insize; + } + + if ((new_buffer = realloc (read_buffer, current_alloc)) == NULL) { + exit_err ("realloc error"); + } + + read_buffer = new_buffer; + + return read_buffer; +} + +/* Forward declaration of refuse_all_incr () */ +static void +refuse_all_incr (void); + +/* + * handle_x_errors () + * + * XError handler. + */ +static int +handle_x_errors (Display * display, XErrorEvent * eev) +{ + char err_buf[MAXLINE]; + + /* Make sure to send a refusal to all waiting INCR requests + * and delete the corresponding properties. */ + if (eev->error_code == BadAlloc) refuse_all_incr (); + + XGetErrorText (display, eev->error_code, err_buf, MAXLINE); + exit_err (err_buf); + + return 0; +} + +/* + * clear_selection (selection) + * + * Clears the specified X selection 'selection'. This requests that no + * process should own 'selection'; thus the X server will respond to + * SelectionRequests with an empty property and we don't need to leave + * a daemon hanging around to service this selection. + */ +static void +clear_selection (Atom selection) +{ + XSetSelectionOwner (display, selection, None, timestamp); + /* Call XSync to ensure this operation completes before program + * termination, especially if this is all we are doing. */ + XSync (display, False); +} + +/* + * own_selection (selection) + * + * Requests ownership of the X selection. Returns True if ownership was + * granted, and False otherwise. + */ +static Boolean +own_selection (Atom selection) +{ + Window owner; + + XSetSelectionOwner (display, selection, window, timestamp); + /* XGetSelectionOwner does a round trip to the X server, so there is + * no need to call XSync here. */ + owner = XGetSelectionOwner (display, selection); + if (owner != window) { + return False; + } else { + XSetErrorHandler (handle_x_errors); + return True; + } +} + + +static IncrTrack * incrtrack_list = NULL; + +/* + * add_incrtrack (it) + * + * Add 'it' to the head of incrtrack_list. + */ +static void +add_incrtrack (IncrTrack * it) +{ + if (incrtrack_list) { + incrtrack_list->prev = it; + } + it->prev = NULL; + it->next = incrtrack_list; + incrtrack_list = it; +} + +/* + * remove_incrtrack (it) + * + * Remove 'it' from incrtrack_list. + */ +static void +remove_incrtrack (IncrTrack * it) +{ + if (it->prev) { + it->prev->next = it->next; + } + if (it->next) { + it->next->prev = it->prev; + } + + if (incrtrack_list == it) { + incrtrack_list = it->next; + } +} + +/* + * fresh_incrtrack () + * + * Create a new incrtrack, and add it to incrtrack_list. + */ +static IncrTrack * +fresh_incrtrack (void) +{ + IncrTrack * it; + + it = xs_malloc (sizeof (IncrTrack)); + add_incrtrack (it); + + return it; +} + +/* + * trash_incrtrack (it) + * + * Remove 'it' from incrtrack_list, and free it. + */ +static void +trash_incrtrack (IncrTrack * it) +{ + remove_incrtrack (it); + free (it); +} + +/* + * find_incrtrack (atom) + * + * Find the IncrTrack structure within incrtrack_list pertaining to 'atom', + * if it exists. + */ +static IncrTrack * +find_incrtrack (Atom atom) +{ + IncrTrack * iti; + + for (iti = incrtrack_list; iti; iti = iti->next) { + if (atom == iti->property) return iti; + } + + return NULL; +} + +/* Forward declaration of handle_multiple() */ +static HandleResult +handle_multiple (Display * display, Window requestor, Atom property, + unsigned char * sel, Atom selection, Time time, + MultTrack * mparent); + +/* Forward declaration of process_multiple() */ +static HandleResult +process_multiple (MultTrack * mt, Boolean do_parent); + +/* + * confirm_incr (it) + * + * Confirm the selection request of ITER tracked by 'it'. + */ +static void +notify_incr (IncrTrack * it, HandleResult hr) +{ + XSelectionEvent ev; + + /* Call XSync here to make sure any BadAlloc errors are caught before + * confirming the conversion. */ + XSync (it->display, False); + + print_debug (D_TRACE, "Confirming conversion"); + + /* Prepare a SelectionNotify event to send, placing the selection in the + * requested property. */ + ev.type = SelectionNotify; + ev.display = it->display; + ev.requestor = it->requestor; + ev.selection = it->selection; + ev.time = it->time; + ev.target = it->target; + + if (hr & HANDLE_ERR) ev.property = None; + else ev.property = it->property; + + XSendEvent (display, ev.requestor, False, + (unsigned long)NULL, (XEvent *)&ev); +} + +/* + * refuse_all_incr () + * + * Refuse all INCR transfers in progress. ASSUMES that this is called in + * response to an error, and that the program is about to bail out; + * ie. incr_track is not cleaned out. + */ +static void +refuse_all_incr (void) +{ + IncrTrack * it; + + for (it = incrtrack_list; it; it = it->next) { + XDeleteProperty (it->display, it->requestor, it->property); + notify_incr (it, HANDLE_ERR); + /* Don't bother trashing and list-removing these; we are about to + * bail out anyway. */ + } +} + +/* + * complete_incr (it) + * + * Finish off an INCR retrieval. If it was part of a multiple, continue + * that; otherwise, send confirmation that this completed. + */ +static void +complete_incr (IncrTrack * it, HandleResult hr) +{ + MultTrack * mparent = it->mparent; + + if (mparent) { + trash_incrtrack (it); + process_multiple (mparent, True); + } else { + notify_incr (it, hr); + trash_incrtrack (it); + } +} + +/* + * notify_multiple (mt, hr) + * + * Confirm the selection request initiated with MULTIPLE tracked by 'mt'. + */ +static void +notify_multiple (MultTrack * mt, HandleResult hr) +{ + XSelectionEvent ev; + + /* Call XSync here to make sure any BadAlloc errors are caught before + * confirming the conversion. */ + XSync (mt->display, False); + + /* Prepare a SelectionNotify event to send, placing the selection in the + * requested property. */ + ev.type = SelectionNotify; + ev.display = mt->display; + ev.requestor = mt->requestor; + ev.selection = mt->selection; + ev.time = mt->time; + ev.target = multiple_atom; + + if (hr & HANDLE_ERR) ev.property = None; + else ev.property = mt->property; + + XSendEvent (display, ev.requestor, False, + (unsigned long)NULL, (XEvent *)&ev); +} + +/* + * complete_multiple (mt, do_parent, hr) + * + * Complete a MULTIPLE transfer. Iterate to its parent MULTIPLE if + * 'do_parent' is true. If there is not parent MULTIPLE, send notification + * of its completion with status 'hr'. + */ +static void +complete_multiple (MultTrack * mt, Boolean do_parent, HandleResult hr) +{ + MultTrack * mparent = mt->mparent; + + if (mparent) { + free (mt); + if (do_parent) process_multiple (mparent, True); + } else { + notify_multiple (mt, hr); + free (mt); + } +} + +/* + * change_property (display, requestor, property, target, format, mode, + * data, nelements) + * + * Wrapper to XChangeProperty that performs INCR transfer if required and + * returns status of entire transfer. + */ +static HandleResult +change_property (Display * display, Window requestor, Atom property, + Atom target, int format, int mode, + unsigned char * data, int nelements, + Atom selection, Time time, MultTrack * mparent) +{ + XSelectionEvent ev; + int nr_bytes; + IncrTrack * it; + + print_debug (D_TRACE, "change_property ()"); + + nr_bytes = nelements * format / 8; + + if (nr_bytes <= max_req) { + print_debug (D_TRACE, "data within maximum request size"); + XChangeProperty (display, requestor, property, target, format, mode, + data, nelements); + + return HANDLE_OK; + } + + /* else */ + print_debug (D_TRACE, "large data transfer"); + + + /* Send a SelectionNotify event of type INCR */ + ev.type = SelectionNotify; + ev.display = display; + ev.requestor = requestor; + ev.selection = selection; + ev.time = time; + ev.target = incr_atom; /* INCR */ + ev.property = property; + + XSelectInput (ev.display, ev.requestor, PropertyChangeMask); + + XChangeProperty (ev.display, ev.requestor, ev.property, incr_atom, 32, + PropModeReplace, (unsigned char *)&nr_bytes, 1); + + XSendEvent (display, requestor, False, + (unsigned long)NULL, (XEvent *)&ev); + + /* Set up the IncrTrack to track this */ + it = fresh_incrtrack (); + + it->mparent = mparent; + it->state = S_INCR_1; + it->display = display; + it->requestor = requestor; + it->property = property; + it->selection = selection; + it->time = time; + it->target = target; + it->format = format; + it->data = data; + it->nelements = nelements; + it->offset = 0; + + /* Maximum nr. of elements that can be transferred in one go */ + it->max_elements = max_req * 8 / format; + + /* Nr. of elements to transfer in this instance */ + it->chunk = MIN (it->max_elements, it->nelements - it->offset); + + /* Wait for that property to get deleted */ + print_debug (D_TRACE, "Waiting on intial property deletion (%s)", + get_atom_name (it->property)); + + return HANDLE_INCOMPLETE; +} + +static HandleResult +incr_stage_1 (IncrTrack * it) +{ + /* First pass: PropModeReplace, from data, size chunk */ + print_debug (D_TRACE, "Writing first chunk (%d bytes) (target 0x%x %s) to property 0x%x of requestor 0x%x", it->chunk, it->target, get_atom_name(it->target), it->property, it->requestor); + XChangeProperty (it->display, it->requestor, it->property, it->target, + it->format, PropModeReplace, it->data, it->chunk); + + it->offset += it->chunk; + + /* wait for PropertyNotify events */ + print_debug (D_TRACE, "Waiting on subsequent deletions ..."); + + it->state = S_INCR_2; + + return HANDLE_INCOMPLETE; +} + +static HandleResult +incr_stage_2 (IncrTrack * it) +{ + it->chunk = MIN (it->max_elements, it->nelements - it->offset); + + if (it->chunk <= 0) { + + /* Now write zero-length data to the property */ + XChangeProperty (it->display, it->requestor, it->property, it->target, + it->format, PropModeAppend, NULL, 0); + it->state = S_NULL; + print_debug (D_TRACE, "Set si to state S_NULL"); + return HANDLE_OK; + } else { + print_debug (D_TRACE, "Writing chunk (%d bytes) to property", + it->chunk); + XChangeProperty (it->display, it->requestor, it->property, it->target, + it->format, PropModeAppend, it->data+it->offset, + it->chunk); + it->offset += it->chunk; + print_debug (D_TRACE, "%d bytes remaining", + it->nelements - it->offset); + return HANDLE_INCOMPLETE; + } +} + + +/* + * handle_timestamp (display, requestor, property) + * + * Handle a TIMESTAMP request. + */ +static HandleResult +handle_timestamp (Display * display, Window requestor, Atom property, + Atom selection, Time time, MultTrack * mparent) +{ + return + change_property (display, requestor, property, XA_INTEGER, 32, + PropModeReplace, (unsigned char *)&timestamp, 1, + selection, time, mparent); +} + +/* + * handle_targets (display, requestor, property) + * + * Handle a TARGETS request. + */ +static HandleResult +handle_targets (Display * display, Window requestor, Atom property, + Atom selection, Time time, MultTrack * mparent) +{ + Atom * targets_cpy; + + targets_cpy = malloc (sizeof (supported_targets)); + memcpy (targets_cpy, supported_targets, sizeof (supported_targets)); + + return + change_property (display, requestor, property, XA_ATOM, 32, + PropModeReplace, (unsigned char *)targets_cpy, + NUM_TARGETS, selection, time, mparent); +} + +/* + * handle_string (display, requestor, property, sel) + * + * Handle a STRING request; setting 'sel' as the data + */ +static HandleResult +handle_string (Display * display, Window requestor, Atom property, + unsigned char * sel, Atom selection, Time time, + MultTrack * mparent) +{ + return + change_property (display, requestor, property, XA_STRING, 8, + PropModeReplace, sel, strlen(sel), + selection, time, mparent); +} + +/* + * handle_delete (display, requestor, property) + * + * Handle a DELETE request. + */ +static HandleResult +handle_delete (Display * display, Window requestor, Atom property) +{ + XChangeProperty (display, requestor, property, null_atom, 0, + PropModeReplace, NULL, 0); + + return DID_DELETE; +} + +/* + * process_multiple (mt, do_parent) + * + * Iterate through a MultTrack until it completes, or until one of its + * entries initiates an interated selection. + * + * If 'do_parent' is true, and the actions proscribed in 'mt' are + * completed during the course of this call, then process_multiple + * is iteratively called on mt->mparent. + */ +static HandleResult +process_multiple (MultTrack * mt, Boolean do_parent) +{ + HandleResult retval = HANDLE_OK; + int i; + + if (!mt) return retval; + + for (; mt->index < mt->length; mt->index += 2) { + i = mt->index; + if (mt->atoms[i] == timestamp_atom) { + retval |= handle_timestamp (mt->display, mt->requestor, mt->atoms[i+1], + mt->selection, mt->time, mt); + } else if (mt->atoms[i] == targets_atom) { + retval |= handle_targets (mt->display, mt->requestor, mt->atoms[i+1], + mt->selection, mt->time, mt); + } 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] == 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] == 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 + * Over-engineering 101 :) This comment is just here to keep the + * logic documented and separate from the 'else' block. */ + } else { + /* for anything we don't know how to handle, we fail the conversion + * by setting this: */ + mt->atoms[i] = None; + } + + /* If any of the conversions failed, signify this by setting that + * atom to None ...*/ + if (retval & HANDLE_ERR) { + mt->atoms[i] = None; + } + /* ... but don't propogate HANDLE_ERR */ + retval &= (~HANDLE_ERR); + + if (retval & HANDLE_INCOMPLETE) break; + } + + if ((retval & HANDLE_INCOMPLETE) == 0) { + complete_multiple (mt, do_parent, retval); + } + + return retval; +} + +/* + * continue_incr (it) + * + * Continue an incremental transfer of IncrTrack * it. + * + * NB. If the incremental transfer was part of a multiple request, this + * function calls process_multiple with do_parent=True because it is + * assumed we are continuing an interrupted ITER, thus we must continue + * the multiple as its original handler did not complete. + */ +static HandleResult +continue_incr (IncrTrack * it) +{ + HandleResult retval = HANDLE_OK; + + if (it->state == S_INCR_1) { + retval = incr_stage_1 (it); + } else if (it->state == S_INCR_2) { + retval = incr_stage_2 (it); + } + + /* If that completed the INCR, deal with completion */ + if ((retval & HANDLE_INCOMPLETE) == 0) { + complete_incr (it, retval); + } + + return retval; +} + +/* + * handle_multiple (display, requestor, property, sel, selection, time) + * + * Handle a MULTIPLE request; possibly setting 'sel' if any STRING + * requests are processed within it. Return value has DID_DELETE bit set + * if any delete requests are processed. + * + * NB. This calls process_multiple with do_parent=False because it is + * assumed we are "handling" the multiple request on behalf of a + * multiple already in progress, or (more likely) directly off a + * SelectionRequest event. + */ +static HandleResult +handle_multiple (Display * display, Window requestor, Atom property, + unsigned char * sel, Atom selection, Time time, + MultTrack * mparent) +{ + MultTrack * mt; + int format; + unsigned long bytesafter; + HandleResult retval = HANDLE_OK; + + mt = xs_malloc (sizeof (MultTrack)); + + XGetWindowProperty (display, requestor, property, 0L, 1000000, + False, (Atom)AnyPropertyType, &mt->property, + &format, &mt->length, &bytesafter, + (unsigned char **)&mt->atoms); + + /* Make sure we got the Atom list we want */ + if (format != 32) return HANDLE_OK; + + + mt->mparent = mparent; + mt->display = display; + mt->requestor = requestor; + mt->sel = sel; + mt->selection = selection; + mt->time = time; + mt->index = 0; + + retval = process_multiple (mt, False); + + return retval; +} + +/* + * handle_selection_request (event, sel) + * + * Processes a SelectionRequest event 'event' and replies to its + * sender appropriately, eg. with the contents of the string 'sel'. + * Returns False if a DELETE request is processed, indicating to + * the calling function to delete the corresponding selection. + * Returns True otherwise. + */ +static Boolean +handle_selection_request (XEvent event, unsigned char * sel) +{ + XSelectionRequestEvent * xsr = &event.xselectionrequest; + XSelectionEvent ev; + HandleResult hr = HANDLE_OK; + Boolean retval = True; + + print_debug (D_TRACE, "handle_selection_request, property=0x%x (%s), target=0x%x (%s)", + xsr->property, get_atom_name (xsr->property), + xsr->target, get_atom_name (xsr->target)); + + /* Prepare a SelectionNotify event to send, either as confirmation of + * placing the selection in the requested property, or as notification + * that this could not be performed. */ + ev.type = SelectionNotify; + ev.display = xsr->display; + ev.requestor = xsr->requestor; + ev.selection = xsr->selection; + ev.time = xsr->time; + ev.target = xsr->target; + + if (xsr->property == None && ev.target != multiple_atom) { + /* Obsolete requestor */ + xsr->property = xsr->target; + } + + if (ev.time != CurrentTime && ev.time < timestamp) { + /* If the time is outside the period we have owned the selection, + * which is any time later than timestamp, or if the requested target + * is not a string, then refuse the SelectionRequest. NB. Some broken + * clients don't set a valid timestamp, so we have to check against + * CurrentTime here. */ + ev.property = None; + } else if (ev.target == timestamp_atom) { + /* Return timestamp used to acquire ownership if target is TIMESTAMP */ + ev.property = xsr->property; + hr = handle_timestamp (ev.display, ev.requestor, ev.property, + ev.selection, ev.time, NULL); + } else if (ev.target == targets_atom) { + /* Return a list of supported targets (TARGETS)*/ + ev.property = xsr->property; + hr = handle_targets (ev.display, ev.requestor, ev.property, + ev.selection, ev.time, NULL); + } else if (ev.target == multiple_atom) { + if (xsr->property == None) { /* Invalid MULTIPLE request */ + ev.property = None; + } else { + /* Handle MULTIPLE request */ + hr = handle_multiple (ev.display, ev.requestor, ev.property, sel, + ev.selection, ev.time, NULL); + } + } else if (ev.target == XA_STRING || ev.target == text_atom) { + /* Received STRING or TEXT request */ + ev.property = xsr->property; + hr = handle_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 */ + ev.property = None; + } + + /* Return False if a DELETE was processed */ + retval = (hr & DID_DELETE) ? False : True; + + /* If there was an error in the transfer, it should be refused */ + if (hr & HANDLE_ERR) { + print_debug (D_TRACE, "Error in transfer"); + ev.property = None; + } + + if ((hr & HANDLE_INCOMPLETE) == 0) { + if (ev.property == None) {print_debug (D_TRACE, "Refusing conversion");} + else { print_debug (D_TRACE, "Confirming conversion");} + + XSendEvent (display, ev.requestor, False, + (unsigned long)NULL, (XEvent *)&ev); + + /* If we return False here, we may quit immediately, so sync out the + * X queue. */ + if (!retval) XSync (display, False); + } + + return retval; +} + +/* + * set_selection (selection, sel) + * + * Takes ownership of the selection 'selection', then loops waiting for + * its SelectionClear or SelectionRequest events. + * + * Handles SelectionRequest events, first checking for additional + * input if the user has specified 'follow' mode. Returns when a + * SelectionClear event is received for the specified selection. + */ +static void +set_selection (Atom selection, unsigned char * sel) +{ + XEvent event; + IncrTrack * it; + + if (own_selection (selection) == False) return; + + for (;;) { + XNextEvent (display, &event); + + switch (event.type) { + case SelectionClear: + if (event.xselectionclear.selection == selection) return; + break; + case SelectionRequest: + if (event.xselectionrequest.selection != selection) break; + + if (do_follow) + sel = read_input (sel, True); + + if (!handle_selection_request (event, sel)) return; + + break; + case PropertyNotify: + if (event.xproperty.state != PropertyDelete) break; + + it = find_incrtrack (event.xproperty.atom); + + if (it != NULL) { + continue_incr (it); + } + + break; + default: + break; + } + } +} + +/* + * set_selection__daemon (selection, sel) + * + * Creates a daemon process to handle selection requests for the + * specified selection 'selection', to respond with selection text 'sel'. + * If 'sel' is an empty string (NULL or "") then no daemon process is + * created and the specified selection is cleared instead. + */ +static void +set_selection__daemon (Atom selection, unsigned char * sel) +{ + if (empty_string (sel)) { + clear_selection (selection); + return; + } + + become_daemon (); + + set_selection (selection, sel); +} + +/* + * set_selection_pair (sel_p, sel_s) + * + * Handles SelectionClear and SelectionRequest events for both the + * primary and secondary selections. Returns once SelectionClear events + * have been received for both selections. Responds to SelectionRequest + * events for the primary selection with text 'sel_p' and for the + * secondary selection with text 'sel_s'. + */ +static void +set_selection_pair (unsigned char * sel_p, unsigned char * sel_s) +{ + XEvent event; + IncrTrack * it; + + if (sel_p) { + if (own_selection (XA_PRIMARY) == False) + free_string (sel_p); + } else { + clear_selection (XA_PRIMARY); + } + + if (sel_s) { + if (own_selection (XA_SECONDARY) == False) + free_string (sel_s); + } else { + clear_selection (XA_SECONDARY); + } + + for (;;) { + XNextEvent (display, &event); + + switch (event.type) { + case SelectionClear: + if (event.xselectionclear.selection == XA_PRIMARY) { + free_string (sel_p); + if (sel_s == NULL) return; + } else if (event.xselectionclear.selection == XA_SECONDARY) { + free_string (sel_s); + if (sel_p == NULL) return; + } + break; + case SelectionRequest: + if (event.xselectionrequest.selection == XA_PRIMARY) { + if (!handle_selection_request (event, sel_p)) { + free_string (sel_p); + if (sel_s == NULL) return; + } + } else if (event.xselectionrequest.selection == XA_SECONDARY) { + if (!handle_selection_request (event, sel_s)) { + free_string (sel_s); + if (sel_p == NULL) return; + } + } + break; + case PropertyNotify: + if (event.xproperty.state != PropertyDelete) break; + + it = find_incrtrack (event.xproperty.atom); + + if (it != NULL) { + continue_incr (it); + } + break; + default: + break; + } + } +} + +/* + * set_selection_pair__daemon (sel_p, sel_s) + * + * Creates a daemon process to handle selection requests for both the + * primary and secondary selections with texts 'sel_p' and 'sel_s' + * respectively. + * + * If both 'sel_p' and 'sel_s' are empty strings (NULL or "") then no + * daemon process is created, and both selections are cleared instead. + */ +static void +set_selection_pair__daemon (unsigned char * sel_p, unsigned char * sel_s) +{ + if (empty_string (sel_p) && empty_string (sel_s)) { + clear_selection (XA_PRIMARY); + clear_selection (XA_SECONDARY); + return; + } + + become_daemon (); + + set_selection_pair (sel_p, sel_s); +} + +/* + * keep_selections () + * + * Takes ownership of both the primary and secondary selections. The current + * selection texts are retrieved and a new daemon process is created to + * handle both selections unmodified. + */ +static void +keep_selections (void) +{ + unsigned char * text1, * text2; + + text1 = get_selection (XA_PRIMARY, XA_STRING); + text2 = get_selection (XA_SECONDARY, XA_STRING); + + set_selection_pair__daemon (text1, text2); +} + +/* + * exchange_selections () + * + * Exchanges the primary and secondary selections. The current selection + * texts are retrieved and a new daemon process is created to handle both + * selections with their texts exchanged. + */ +static void +exchange_selections (void) +{ + unsigned char * text1, * text2; + + text1 = get_selection (XA_PRIMARY, XA_STRING); + text2 = get_selection (XA_SECONDARY, XA_STRING); + + set_selection_pair__daemon (text2, text1); +} + + +/* + * main (argc, argv) + * ================= + * + * Parse user options and set behaviour. + * + * By default the current selection is output and not modified if both + * standard input and standard output are terminals (ttys). Otherwise, + * the current selection is output if standard output is not a terminal + * (tty), and the selection is set from standard input if standard input + * is not a terminal (tty). If any input or output options are given then + * the program behaves only in the requested mode. + * + * If both input and output is required then the previous selection is + * output before being replaced by the contents of standard input. + */ +int +main(int argc, char *argv[]) +{ + Boolean show_version = False; + Boolean show_help = False; + Boolean do_append = False, do_clear = False; + Boolean do_keep = False, do_exchange = False; + Boolean do_input = False, do_output = False; + Boolean dont_input = True, dont_output = False; + Boolean want_clipboard = False, do_delete = False; + Window root; + Atom selection = XA_PRIMARY, test_atom; + int black; + int i, s=0; + unsigned char * old_sel = NULL, * new_sel = NULL; + char * display_name = NULL; + long timeout_ms = 0L; + + progname = argv[0]; + + /* Specify default behaviour based on input and output file types */ + if (isatty(0) && isatty(1)) { + do_input = False; dont_input = True; + do_output = False; dont_output = False; + } else { + do_input = !isatty(0); dont_input = !do_input; + do_output = !isatty(1); dont_output = !do_output; + } + +#define OPT(s) (strcmp (argv[i], (s)) == 0) + + /* Parse options; modify behaviour according to user-specified options */ + for (i=1; i < argc; i++) { + if (OPT("--help") || OPT("-h")) { + show_help = True; + } else if (OPT("--version")) { + show_version = True; + } else if (OPT("--verbose") || OPT("-v")) { + debug_level++; + } else if (OPT("--append") || OPT("-a")) { + do_append = True; + dont_output = True; + } else if (OPT("--input") || OPT("-i")) { + do_input = True; + dont_output = True; + } else if (OPT("--clear") || OPT("-c")) { + do_clear = True; + dont_output = True; + } else if (OPT("--output") || OPT("-o")) { + do_output = True; + dont_input = True; + } else if (OPT("--follow") || OPT("-f")) { + do_follow = True; + dont_output = True; + } else if (OPT("--primary") || OPT("-p")) { + selection = XA_PRIMARY; + } else if (OPT("--secondary") || OPT("-s")) { + selection = XA_SECONDARY; + } else if (OPT("--clipboard") || OPT("-b")) { + want_clipboard = True; + } else if (OPT("--keep") || OPT("-k")) { + do_keep = True; + } else if (OPT("--exchange") || OPT("-x")) { + do_exchange = True; + } else if (OPT("--display")) { + i++; if (i >= argc) goto usage_err; + display_name = argv[i]; + } else if (OPT("--selectionTimeout") || OPT("-t")) { + i++; if (i >= argc) goto usage_err; + timeout_ms = strtol(argv[i], (char **)NULL, 10); + if (timeout_ms < 0) timeout_ms = 0; + } else if (OPT("--nodetach") || OPT("-n")) { + no_daemon = True; + } else if (OPT("--delete") || OPT("-d")) { + do_delete = True; + dont_output = True; + } else if (OPT("--logfile") || OPT("-l")) { + i++; if (i >= argc) goto usage_err; + strncpy (logfile, argv[i], MAXFNAME); + } else { + goto usage_err; + } + } + + if (show_version) { + printf ("xsel version " VERSION " by " AUTHOR "\n"); + } + + if (show_help) { + usage (); + } + + if (show_version || show_help) { + exit (0); + } + + if (fstat (0, &in_statbuf) == -1) { + exit_err ("fstat error on stdin"); + } + if (fstat (1, &out_statbuf) == -1) { + exit_err ("fstat error on stdout"); + } + + if (S_ISDIR(in_statbuf.st_mode)) { + exit_err ("-: Is a directory\n"); + } + if (S_ISDIR(out_statbuf.st_mode)) { + exit_err ("stdout: Is a directory\n"); + } + + timeout = timeout_ms * 1000; + + display = XOpenDisplay (display_name); + if (display==NULL) { + exit_err ("Can't open display: %s\n", display_name); + } + root = XDefaultRootWindow (display); + + /* Create an unmapped window for receiving events */ + black = BlackPixel (display, DefaultScreen (display)); + window = XCreateSimpleWindow (display, root, 0, 0, 1, 1, 0, black, black); + + print_debug (D_INFO, "Window id: 0x%x (unmapped)", window); + + /* Get a timestamp */ + XSelectInput (display, window, PropertyChangeMask); + timestamp = get_timestamp (); + + print_debug (D_OBSC, "Timestamp: %lu", timestamp); + + /* Get the maximum incremental selection size in bytes */ + /*max_req = MAX_SELECTION_INCR (display);*/ + max_req = 4000; + + print_debug (D_OBSC, "Maximum request size: %ld bytes", max_req); + + /* Consistency check */ + test_atom = XInternAtom (display, "PRIMARY", False); + if (test_atom != XA_PRIMARY) + print_debug (D_WARN, "XA_PRIMARY not named \"PRIMARY\"\n"); + test_atom = XInternAtom (display, "SECONDARY", False); + if (test_atom != XA_SECONDARY) + print_debug (D_WARN, "XA_SECONDARY not named \"SECONDARY\"\n"); + + /* Get the TIMESTAMP atom */ + timestamp_atom = XInternAtom (display, "TIMESTAMP", False); + supported_targets[s++] = timestamp_atom; + + /* Get the MULTIPLE atom */ + multiple_atom = XInternAtom (display, "MULTIPLE", False); + supported_targets[s++] = multiple_atom; + + /* Get the TARGETS atom */ + targets_atom = XInternAtom (display, "TARGETS", False); + supported_targets[s++] = targets_atom; + + /* Get the DELETE atom */ + delete_atom = XInternAtom (display, "DELETE", False); + supported_targets[s++] = delete_atom; + + /* Get the INCR atom */ + incr_atom = XInternAtom (display, "INCR", False); + supported_targets[s++] = incr_atom; + + /* Get the NULL atom */ + null_atom = XInternAtom (display, "NULL", False); + + /* Get the TEXT atom */ + text_atom = XInternAtom (display, "TEXT", False); + supported_targets[s++] = text_atom; + + supported_targets[s++] = XA_STRING; + + /* handle selection keeping and exit if so */ + if (do_keep) { + keep_selections (); + _exit (0); + } + + /* handle selection exchange and exit if so */ + if (do_exchange) { + exchange_selections (); + _exit (0); + } + + /* Find the "CLIPBOARD" selection if required */ + if (want_clipboard) { + selection = XInternAtom (display, "CLIPBOARD", False); + } + + /* handle output modes */ + if (do_output || !dont_output) { + /* Get the current selection */ + old_sel = get_selection (selection, XA_STRING); + if (old_sel) printf ("%s", old_sel); + } + + /* handle input and clear modes */ + if (do_delete) { + get_selection (selection, delete_atom); + } else if (do_clear) { + clear_selection (selection); + } + else if (do_input || !dont_input) { + if (do_append) { + if (!old_sel) old_sel = get_selection (selection, XA_STRING); + new_sel = copy_sel (old_sel); + } + new_sel = initialise_read (new_sel); + new_sel = read_input (new_sel, False); + set_selection__daemon (selection, new_sel); + } + + exit (0); + +usage_err: + usage (); + exit (0); +} diff --git a/xsel.h b/xsel.h @@ -0,0 +1,99 @@ +/* + * xsel -- manipulate the X selection + * Copyright (C) 2001 Conrad Parker <conrad@vergenet.net> + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#define VERSION "0.9" +#define AUTHOR "Conrad Parker <conrad@vergenet.net>" + +/* Default debug level (ship at 0) */ +#define DEBUG_LEVEL 0 + +#define MAX(a,b) ((a)>(b)?(a):(b)) +#define MIN(a,b) ((a)<(b)?(a):(b)) + +#define empty_string(s) (s==NULL||s[0]=='\0') +#define free_string(s) { free(s); s=NULL; } + +/* Maximum line length for error messages */ +#define MAXLINE 4096 + +/* Maximum filename length */ +#define MAXFNAME 1024 + +/* Maximum incremental selection size. (Ripped from Xt) */ +#define MAX_SELECTION_INCR(dpy) (((65536 < XMaxRequestSize(dpy)) ? \ + (65536 << 2) : (XMaxRequestSize(dpy) << 2))-100) + +/* + * Debug levels (for print_debug()): + * + * 0 - Fatal errors (default/unmaskable) + * 1 - Non-fatal warning (essential debugging info) + * 2 - Informative (generally useful debugging info) + * 3 - Obscure (more detailed debugging info) + * 4 - Trace (sequential trace of progress) + */ + +#define D_FATAL 0 +#define D_WARN 1 +#define D_INFO 2 +#define D_OBSC 3 +#define D_TRACE 4 + +/* An instance of a MULTIPLE SelectionRequest being served */ +typedef struct _MultTrack MultTrack; + +struct _MultTrack { + MultTrack * mparent; + Display * display; + Window requestor; + Atom property; + Atom selection; + Time time; + Atom * atoms; + unsigned long length; + unsigned long index; + unsigned char * sel; +}; + +/* Selection serving states */ +typedef enum { + S_NULL=0, + S_INCR_1, + S_INCR_2 +} IncrState; + +/* An instance of a selection being served */ +typedef struct _IncrTrack IncrTrack; + +struct _IncrTrack { + MultTrack * mparent; + IncrTrack * prev, * next; + IncrState state; + Display * display; + Window requestor; + Atom property; + Atom selection; + Time time; + Atom target; + int format; + unsigned char * data; + int nelements; /* total */ + int offset, chunk, max_elements; /* all in terms of nelements */ +}; + +/* Status of request handling */ +typedef int HandleResult; +#define HANDLE_OK 0 +#define HANDLE_ERR (1<<0) +#define HANDLE_INCOMPLETE (1<<1) +#define DID_DELETE (1<<2)