changeset 26:5bdea77d0c1d

Add pwm-clip utility for setting the X11 CLIPBOARD selection
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Thu, 21 Sep 2017 09:45:59 +0200
parents 616385fa1fd9
children 722a45b4028b
files Makefile compat.h compat/getline.c compat/getline.h pwm-clip.1.xml pwm-clip.c pwm.1.xml
diffstat 7 files changed, 464 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Wed Sep 20 23:57:51 2017 +0200
+++ b/Makefile	Thu Sep 21 09:45:59 2017 +0200
@@ -83,6 +83,7 @@
   HAVE_ASPRINTF ?=	1
   HAVE_CLOSEFROM ?=	0
   HAVE_ERR_H ?=		1
+  HAVE_GETLINE ?=	0
   HAVE_GETRANDOM ?=	0
   HAVE_SYS_QUEUE_H ?=	0
   HAVE_SETPROGNAME ?=	0
@@ -92,6 +93,7 @@
   HAVE_ASPRINTF ?=	1
   HAVE_CLOSEFROM ?=	1
   HAVE_ERR_H ?=		1
+  HAVE_GETLINE ?=	1
   HAVE_GETRANDOM ?=	0
   HAVE_SYS_QUEUE_H ?=	1
   HAVE_SETPROGNAME ?=	1
@@ -101,6 +103,7 @@
   HAVE_ASPRINTF ?=	1
   HAVE_CLOSEFROM ?=	1
   HAVE_ERR_H ?=		1
+  HAVE_GETLINE ?=	1
   HAVE_GETRANDOM ?=	0
   HAVE_SYS_QUEUE_H ?=	1
   HAVE_SYS_TREE_H ?=	1
@@ -110,6 +113,7 @@
   HAVE_ASPRINTF ?=	1
   HAVE_CLOSEFROM ?=	1
   HAVE_ERR_H ?=		1
+  HAVE_GETLINE ?=	1
   HAVE_GETRANDOM ?=	0
   HAVE_SYS_QUEUE_H ?=	1
   HAVE_SYS_TREE_H ?=	1
@@ -119,11 +123,13 @@
     HAVE_ARC4RANDOM ?=	0
     HAVE_ASPRINTF ?=	0
     HAVE_ERR_H ?=	0
+    HAVE_GETLINE ?=	0
     HAVE_GETRANDOM ?=	0
   else
     HAVE_ARC4RANDOM ?=	1
     HAVE_ASPRINTF ?=	1
     HAVE_ERR_H ?=	1
+    HAVE_GETLINE ?=	1
     HAVE_GETRANDOM ?=	1
   endif
   HAVE_CLOSEFROM ?=	1
@@ -135,6 +141,7 @@
   HAVE_ASPRINTF ?=	0
   HAVE_CLOSEFROM ?=	0
   HAVE_ERR_H ?=		0
+  HAVE_GETLINE ?=	0
   HAVE_GETRANDOM ?=	0
   HAVE_SYS_QUEUE_H ?=	0
   HAVE_SETPROGNAME ?=	0
@@ -149,6 +156,10 @@
 LIBPWM_LIB =		libpwm.a
 LIBPWM_LIB_MEMBERS =	$(LIBPWM_OBJS:%.o=$(LIBPWM_LIB)(%.o))
 
+PWM_CLIP_OBJS =		pwm-clip.o
+PWM_CLIP_BIN =		pwm-clip
+PWM_CLIP_MAN1 =		pwm-clip.1
+
 PWM_OBJS =		cmd.o \
 			io.o \
 			pager.o \
@@ -158,20 +169,23 @@
 			pwm.o \
 			tok.o
 PWM_BIN =		$(PACKAGE)
+PWM_MAN1 =		pwm.1
 
-OBJS = $(LIBCOMPAT_OBJS) $(LIBPWM_OBJS) $(PWM_OBJS)
+OBJS = $(LIBCOMPAT_OBJS) $(LIBPWM_OBJS) $(PWM_CLIP_OBJS) $(PWM_OBJS)
 
-MANPAGES = pwm.1
+MANPAGES = $(PWM_MAN1) $(PWM_CLIP_MAN1)
 
 .DEFAULT_TARGET = all
 
 .PHONY: all clean clobber dist install
 
-all: $(PWM_BIN) $(MANPAGES)
+all: $(PWM_BIN) $(PWM_CLIP_BIN) $(MANPAGES)
 
 $(PWM_BIN): XCPPFLAGS += -DPACKAGE=\"$(PACKAGE)\" \
 			-DVERSION=\"$(VERSION)\"
 $(PWM_BIN): LDLIBS =	-ltecla -lcurses -lpws -lnettle
+$(PWM_CLIP_BIN): XCPPFLAGS += -Icompat/include
+$(PWM_CLIP_BIN): LDLIBS = -lnettle
 ifeq ($(HAVE_ASPRINTF),1)
   XCPPFLAGS +=		-DHAVE_ASPRINTF
 else
@@ -196,6 +210,11 @@
 else
   LIBCOMPAT_OBJS +=	compat/err.o
 endif
+ifeq ($(HAVE_GETLINE),1)
+  XCPPFLAGS +=		-DHAVE_GETLINE
+else
+  LIBCOMPAT_OBJS +=	compat/getline.o
+endif
 ifeq ($(HAVE_SETPROGNAME),1)
   XCPPFLAGS +=		-DHAVE_SETPROGNAME
 else
@@ -223,10 +242,15 @@
 
 $(LIBPWM_LIB): $(LIBPWM_LIB_MEMBERS)
 
+$(PWM_CLIP_BIN): $(PWM_CLIP_OBJS) $(LIBPWM_LIB) $(LIBCOMPAT_LIB)
+	$(LINK.o) $^ $(LDLIBS) -o $@
+
+$(eval $(call generate-manpage-rule,$(PWM_CLIP_MAN1)))
+
 $(PWM_BIN): $(PWM_OBJS) $(LIBPWM_LIB) $(LIBCOMPAT_LIB)
 	$(LINK.o) $^ $(LDLIBS) -o $@
 
-$(eval $(call generate-manpage-rule,$(MANPAGES)))
+$(eval $(call generate-manpage-rule,$(PWM_MAN1)))
 
 %.o: %.c
 	$(MAKEDEPEND.c) $< | $(SED) -f deps.sed >$*.d
@@ -238,6 +262,7 @@
 
 install: all
 	$(INSTALL.exec) $(PWM_BIN) "$(DESTDIR)$(bindir)/$(PWM_BIN)"
+	$(INSTALL.exec) $(PWM_CLIP_BIN) "$(DESTDIR)$(bindir)/$(PWM_CLIP_BIN)"
 	for manpage in $(MANPAGES); do \
 	    if [ -L $${manpage} ]; then \
 	        $(INSTALL.link) $${manpage} \
@@ -249,7 +274,8 @@
 	done
 
 clean:
-	rm -f $(LIBCOMPAT_LIB) $(LIBPWM_LIB) $(PWM_BIN) $(OBJS) $(MANPAGES)
+	rm -f $(LIBCOMPAT_LIB) $(LIBPWM_LIB) $(PWM_CLIP_BIN) $(PWM_BIN) \
+	    $(OBJS) $(MANPAGES)
 
 clobber: clean
 	rm -f $(patsubst %.o,%.d,$(OBJS))
--- a/compat.h	Wed Sep 20 23:57:51 2017 +0200
+++ b/compat.h	Thu Sep 21 09:45:59 2017 +0200
@@ -39,6 +39,10 @@
 #include "compat/err.h"
 #endif /* !HAVE_ERR_H */
 
+#ifndef	HAVE_GETLINE
+#include "compat/getline.h"
+#endif /* !HAVE_GETLINE */
+
 #ifndef	HAVE_GETENTROPY
 #include "compat/getentropy.h"
 #endif /* !HAVE_GETENTROPY */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/getline.c	Thu Sep 21 09:45:59 2017 +0200
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 Guido Berhoerster <guido+pwm@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#include "getline.h"
+
+#define	MIN(x, y)	(((x) < (y)) ? (x) : (y))
+
+ssize_t
+getdelim(char **linep, size_t *sizep, int delim, FILE *fp)
+{
+	ssize_t	len;
+	char	*line = *linep;
+	size_t	size = *sizep;
+	size_t	pos = 0;
+	char	*line_new;
+	int	c;
+
+	if (*linep == NULL) {
+		size = 0;
+	}
+
+	flockfile(fp);
+
+	for (;;) {
+		if (size - pos < 2) {
+			size = MIN((size_t)SSIZE_MAX + 1,
+			    (size < BUFSIZ) ? BUFSIZ : size * 1.5);
+			if (size - pos < 2) {
+				len = -1;
+				errno = EOVERFLOW;
+				break;
+			}
+			line_new = realloc(line, size);
+			if (line_new == NULL) {
+				len = -1;
+				break;
+			}
+			*linep = line = line_new;
+			*sizep = size;
+		}
+		c = fgetc(fp);
+		if (c == EOF) {
+			len = -1;
+			break;
+		}
+		line[pos] = c;
+		pos++;
+		len = pos;
+		if (c == delim) {
+			break;
+		}
+	}
+
+	if (*linep != NULL) {
+		(*linep)[pos] = '\0';
+	}
+
+	funlockfile(fp);
+
+	return (len);
+}
+
+ssize_t
+getline(char **linep, size_t *sizep, FILE *fp)
+{
+	return (getdelim(linep, sizep, '\n', fp));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/getline.h	Thu Sep 21 09:45:59 2017 +0200
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 Guido Berhoerster <guido+pwm@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef	GETLINE_H
+#define	GETLINE_H
+
+#include <sys/types.h>
+#include <stdio.h>
+
+ssize_t	getline(char **, size_t *, FILE *);
+ssize_t getdelim(char **, size_t *, int, FILE *);
+
+#endif /* !GETLINE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pwm-clip.1.xml	Thu Sep 21 09:45:59 2017 +0200
@@ -0,0 +1,120 @@
+<?xml version="1.0"?>
+<!--
+
+Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-->
+<refentry xmlns="http://docbook.org/ns/docbook" xml:lang="en">
+  <info>
+    <author>
+      <personname>
+        <firstname>Guido</firstname>
+        <surname>Berhoerster</surname>
+      </personname>
+      <email>guido+pwm@berhoerster.name</email>
+      <personblurb/>
+    </author>
+    <date>21 September, 2017</date>
+  </info>
+  <refmeta>
+    <refentrytitle>pwm-clip</refentrytitle>
+    <manvolnum>1</manvolnum>
+    <refmiscinfo class="source"/>
+    <refmiscinfo class="version"/>
+    <refmiscinfo class="manual">User Commands</refmiscinfo>
+  </refmeta>
+  <refnamediv>
+    <refname>pwm-clip</refname>
+    <refpurpose>set the system clipboard</refpurpose>
+  </refnamediv>
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>pwm-clip</command>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+  <refsect1>
+    <title>Description</title>
+    <para>The <command>pwm-clip</command> utility reads from the standard input
+    stream until it encounters a newline character or the stream is at
+    end-of-file, then removes any newline character at the end and places the
+    data in the system clipboard. On X11 systems it sets the CLIPBOARD
+    selection.</para>
+    <para>The following terminals are currently supported:
+    <simplelist type="horiz" columns="3">
+      <member>xterm</member>
+      <member>tmux</member>
+      <member>screen</member>
+    </simplelist>
+    </para>
+    <para>If <command>pwm-clip</command> is invoked in an unsupported terminal
+    it will print an error message and exit.</para>
+  </refsect1>
+  <refsect1>
+    <title>Environment Variables</title>
+    <variablelist>
+      <varlistentry>
+        <term>
+          <literal>TERM</literal>
+        </term>
+        <listitem>
+          <para>The name of the terminal used by <command>pwm-clip</command> to
+          determine whether setting the system clipboard is supported</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+  <refsect1>
+    <title>Exit Status</title>
+    <para>The following exit values are returned:</para>
+    <variablelist>
+      <varlistentry>
+        <term>0</term>
+        <listitem>
+          <para>Command successfully executed.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>1</term>
+        <listitem>
+          <para>An unspecified error has occured.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>2</term>
+        <listitem>
+          <para>Invalid command line options were specified.</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+  <refsect1>
+    <title>See Also</title>
+    <para><citerefentry><refentrytitle>pwm</refentrytitle>
+      <manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>xterm</refentrytitle>
+      <manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>tmux</refentrytitle>
+      <manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>screen</refentrytitle>
+      <manvolnum>1</manvolnum></citerefentry></para>
+  </refsect1>
+</refentry>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pwm-clip.c	Thu Sep 21 09:45:59 2017 +0200
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "compat.h"
+
+#ifdef	HAVE_ERR_H
+#include <err.h>
+#endif /* HAVE_ERR_H */
+#include <errno.h>
+#include <nettle/base64.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "util.h"
+
+#define	CHUNK_SIZE	128
+
+static char *
+base64_encode(const unsigned char *src, size_t size)
+{
+	char		*dst = NULL;
+	struct base64_encode_ctx b64_ctx;
+	size_t		len = 0;
+
+	dst = xmalloc(BASE64_ENCODE_LENGTH(size) +
+	    BASE64_ENCODE_FINAL_LENGTH);
+
+	base64_encode_init(&b64_ctx);
+	len += base64_encode_update(&b64_ctx, (unsigned char *)dst, size, src);
+	len += base64_encode_final(&b64_ctx, (unsigned char *)dst);
+	dst[len] = '\0';
+
+	dst = xrealloc(dst, len + 1);
+
+	return (dst);
+}
+
+static void
+set_clipboard(const char *s)
+{
+	char	*b64 = NULL;
+
+	b64 = base64_encode((const unsigned char *)s, strlen(s));
+	printf("\033]52;c;%s\a\n", b64);
+	free(b64);
+}
+
+static void
+set_clipboard_dcs_tmux(const char *s)
+{
+	char	*b64 = NULL;
+
+	b64 = base64_encode((const unsigned char *)s, strlen(s));
+	printf("\033Ptmux;\033\033]52;c;%s\a\033\\", b64);
+	free(b64);
+}
+
+static void
+set_clipboard_dcs_chunked(const char *s)
+{
+	char	*b64 = NULL;
+	size_t	i;
+
+	b64 = base64_encode((const unsigned char *)s, strlen(s));
+
+	printf("\033P\033]52;c;");
+	for (i = 0; b64[i] != '\0'; i++) {
+		if ((i > 0) && (i % CHUNK_SIZE == 0)) {
+			printf("\033\\\033P");
+		}
+		putchar(b64[i]);
+	}
+	printf("\a\033\\");
+
+	free(b64);
+}
+
+static void
+usage(void)
+{
+	fprintf(stderr, "usage: %s\n", getprogname());
+}
+
+int
+main(int argc, char *argv[])
+{
+	int		status = EXIT_FAILURE;
+	char		*term;
+	void (*set_clipboard_func)(const char *);
+	int		errflag = 0;
+	int		c;
+	ssize_t		len;
+	char		*s = NULL;
+	size_t		size = 0;
+
+	setprogname(argv[0]);
+
+	term = getenv("TERM");
+	if (term == NULL) {
+		fprintf(stderr, "error: unknown terminal\n");
+		goto out;
+	} else if (strncmp(term, "xterm", strlen("xterm")) == 0) {
+		set_clipboard_func = set_clipboard;
+	} else if (strncmp(term, "tmux", strlen("tmux")) == 0) {
+		set_clipboard_func = set_clipboard_dcs_tmux;
+	} else if (strncmp(term, "screen", strlen("screen")) == 0) {
+		if (getenv("TMUX") != NULL) {
+			set_clipboard_func = set_clipboard_dcs_tmux;
+		} else {
+			set_clipboard_func = set_clipboard_dcs_chunked;
+		}
+	} else {
+		fprintf(stderr, "terminal does not support setting "
+		    "X11 CLIPBOARD selection\n");
+		goto out;
+	}
+
+	while (!errflag && (c = getopt(argc, argv, "h")) != -1) {
+		switch (c) {
+		case 'h':
+			usage();
+			exit(EXIT_SUCCESS);
+		default:
+			errflag = 1;
+		}
+	}
+	if (errflag || (optind + 1 < argc)) {
+		usage();
+		exit(EXIT_USAGE);
+	}
+
+	errno = 0;
+	len = getline(&s, &size, stdin);
+	if (len < 0) {
+		if (errno == 0) {
+			fprintf(stderr, "failed to read line\n");
+			goto out;
+		} else {
+			err(1, "getline");
+		}
+	}
+	if ((len > 0) && (s[len - 1] == '\n')) {
+		s[--len] = '\0';
+	}
+
+	set_clipboard_func(s);
+
+	status = EXIT_SUCCESS;
+
+out:
+	free(s);
+
+	exit(status);
+}
--- a/pwm.1.xml	Wed Sep 20 23:57:51 2017 +0200
+++ b/pwm.1.xml	Thu Sep 21 09:45:59 2017 +0200
@@ -74,8 +74,10 @@
     <command>pwm</command> provides commands to create, modify, delete, and
     display password database entries which may be organized in groups.  The
     contents of a field of a given entry can also be piped to an external
-    command, e.g. in order to copy the content of the username or password
-    field of an entry to the clipboard.</para>
+    command such as the <citerefentry><refentrytitle>pwm-clip</refentrytitle>
+    <manvolnum>1</manvolnum></citerefentry> utility in order to copy the
+    content of the username or password field of an entry to the
+    clipboard.</para>
     <para>If specified, <command>pwm</command> will open or create
     <replaceable>database_file</replaceable> instead of the user's default
     database.</para>
@@ -672,7 +674,9 @@
   </refsect1>
   <refsect1>
     <title>See Also</title>
-    <para><citerefentry><refentrytitle>pwsafe</refentrytitle>
+    <para><citerefentry><refentrytitle>pwm-clip</refentrytitle>
+      <manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>pwsafe</refentrytitle>
       <manvolnum>1</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>sh</refentrytitle>
       <manvolnum>1</manvolnum></citerefentry>,