Skip to content

Commit 6a3d659

Browse files
committed
clipmenu: Invert own_selections
own_selections right now is a little inverted from how it worked in clipmenu 6. It only gates when clipserve starts, but clipserve always owns PRIMARY and CLIPBOARD. That makes PRIMARY ownership unavoidable which is not great with terminals that don't handle that well (like urxvt).
1 parent 9acaa75 commit 6a3d659

8 files changed

Lines changed: 152 additions & 25 deletions

File tree

man/clipmenu.conf.5

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,16 @@ together with own_selections. Default: 0.
3333
.TP
3434
.B own_selections
3535
Specifies which X11 selections (e.g., "clipboard" or "primary") clipmenud
36-
should actively own. Default: "clipboard".
36+
should actively own when
37+
.B own_clipboard
38+
is enabled. When a clip from one of these selections is stored, clipmenud uses
39+
clipserve to own those selections. This does not affect which selections are
40+
monitored; use
41+
.B selections
42+
for that. Default: "clipboard".
43+
If this is set to an empty value, it is treated as if
44+
.B own_clipboard
45+
was disabled.
3746
.TP
3847
.B selections
3948
Lists the X11 selections to monitor for changes. Valid values include

man/clipserve.1

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
clipserve \- serve a selected clipboard entry to X11 selections
44
.SH SYNOPSIS
55
.B clipserve
6-
<hash>
6+
[\-s selection]... <hash>
77
.SH DESCRIPTION
88
.B clipserve
99
serves the clipboard content identified by a hash from the clip store on the
10-
X11 selections (PRIMARY and CLIPBOARD).
10+
configured X11 selections. By default, it serves both PRIMARY and CLIPBOARD.
1111

1212
This program is not usually invoked directly, but is instead called from inside
1313
other clipmenu applications.
@@ -16,8 +16,12 @@ other clipmenu applications.
1616
.B \-h, \--help
1717
Display the help message (invokes the manual page).
1818
.TP
19-
clipserve requires exactly one argument, which is the hexadecimal hash of the
20-
clipboard entry to be served.
19+
.B \-s, \--selection
20+
Specify a selection to serve. Valid values are "primary", "clipboard", and
21+
"secondary". This option may be repeated. If not provided, PRIMARY and
22+
CLIPBOARD are used.
23+
.TP
24+
clipserve requires a hexadecimal hash of the clipboard entry to be served.
2125
.SH CONFIGURATION
2226
See
2327
.BR clipmenu.conf (5).

src/clipmenu.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ static int _nonnull_ clipmenu_action(struct config *cfg, uint64_t hash) {
2020
expect(cs_make_newest(&cs, hash) == 0);
2121
}
2222

23-
run_clipserve(hash);
23+
run_clipserve(hash, NULL);
2424
return 0;
2525
}
2626

src/clipmenud.c

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ static struct cm_selections sels[CM_SEL_MAX];
3838
static Time last_disable_time = 0;
3939
static Time last_enable_time = 0;
4040

41+
static bool has_owned_selections(void) {
42+
for (size_t i = 0; i < CM_SEL_MAX; i++) {
43+
if (cfg.owned_selections[i].active) {
44+
return true;
45+
}
46+
}
47+
return false;
48+
}
49+
4150
enum clip_text_source {
4251
CLIP_TEXT_SOURCE_X,
4352
CLIP_TEXT_SOURCE_MALLOC,
@@ -349,8 +358,8 @@ static void incr_receive_finish(struct incr_transfer *it) {
349358
if (is_salient_text(ct.data)) {
350359
uint64_t hash = store_clip(&ct);
351360
maybe_trim();
352-
if (cfg.owned_selections[sel].active && cfg.own_clipboard) {
353-
run_clipserve(hash);
361+
if (cfg.own_clipboard && has_owned_selections()) {
362+
run_clipserve(hash, cfg.owned_selections);
354363
}
355364
} else {
356365
it_dbg(it, "Clipboard text is whitespace only, ignoring\n");
@@ -493,8 +502,8 @@ static int handle_property_notify(const XPropertyEvent *pe) {
493502
* 2. urxvt and some other terminal emulators will unhilight on
494503
* PRIMARY ownership being taken away from them
495504
*/
496-
if (cfg.owned_selections[sel].active && cfg.own_clipboard) {
497-
run_clipserve(hash);
505+
if (cfg.own_clipboard && has_owned_selections()) {
506+
run_clipserve(hash, cfg.owned_selections);
498507
}
499508
} else {
500509
dbg("Clipboard text is whitespace only, ignoring\n");

src/clipserve.c

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ static Atom incr_atom;
1818

1919
static size_t chunk_size;
2020

21+
static enum selection_type selection_name_to_type(const char *name) {
22+
if (streq(name, "clipboard")) {
23+
return CM_SEL_CLIPBOARD;
24+
}
25+
if (streq(name, "primary")) {
26+
return CM_SEL_PRIMARY;
27+
}
28+
if (streq(name, "secondary")) {
29+
return CM_SEL_SECONDARY;
30+
}
31+
return CM_SEL_INVALID;
32+
}
33+
2134
/**
2235
* Start an INCR transfer.
2336
*/
@@ -95,13 +108,15 @@ static void incr_send_chunk(const XPropertyEvent *pe) {
95108
* Serve clipboard content for all X11 selection requests until all selections
96109
* have been claimed by another application.
97110
*/
98-
static void _nonnull_ serve_clipboard(uint64_t hash,
99-
struct cs_content *content) {
111+
static void _nonnull_ serve_clipboard(uint64_t hash, struct cs_content *content,
112+
const bool selection_active[CM_SEL_MAX]) {
100113
bool running = true;
101114
XEvent evt;
102-
Atom targets, utf8_string, selections[2] = {XA_PRIMARY};
115+
Atom targets, utf8_string;
116+
Atom selection_atoms[CM_SEL_MAX];
103117
Window win;
104118
int remaining_selections;
119+
size_t selection_atoms_len = 0;
105120

106121
dpy = XOpenDisplay(NULL);
107122
expect(dpy);
@@ -114,25 +129,37 @@ static void _nonnull_ serve_clipboard(uint64_t hash,
114129
utf8_string = XInternAtom(dpy, "UTF8_STRING", False);
115130
incr_atom = XInternAtom(dpy, "INCR", False);
116131

117-
selections[1] = XInternAtom(dpy, "CLIPBOARD", False);
118-
for (size_t i = 0; i < arrlen(selections); i++) {
132+
if (selection_active[CM_SEL_PRIMARY]) {
133+
selection_atoms[selection_atoms_len++] = XA_PRIMARY;
134+
}
135+
if (selection_active[CM_SEL_CLIPBOARD]) {
136+
selection_atoms[selection_atoms_len++] =
137+
XInternAtom(dpy, "CLIPBOARD", False);
138+
}
139+
if (selection_active[CM_SEL_SECONDARY]) {
140+
selection_atoms[selection_atoms_len++] = XA_SECONDARY;
141+
}
142+
143+
die_on(selection_atoms_len == 0, "No selections configured\n");
144+
145+
for (size_t i = 0; i < selection_atoms_len; i++) {
119146
bool success = false;
120147
for (int attempts = 0; attempts < 5; attempts++) {
121-
XSetSelectionOwner(dpy, selections[i], win, CurrentTime);
148+
XSetSelectionOwner(dpy, selection_atoms[i], win, CurrentTime);
122149

123150
// According to ICCCM 2.1, a client acquiring a selection should
124151
// confirm success by verifying with GetSelectionOwner.
125-
if (XGetSelectionOwner(dpy, selections[i]) == win) {
152+
if (XGetSelectionOwner(dpy, selection_atoms[i]) == win) {
126153
success = true;
127154
break;
128155
}
129156
}
130157
if (!success) {
131158
die("Failed to set selection for %s\n",
132-
XGetAtomName(dpy, selections[i]));
159+
XGetAtomName(dpy, selection_atoms[i]));
133160
}
134161
}
135-
remaining_selections = arrlen(selections);
162+
remaining_selections = (int)selection_atoms_len;
136163

137164
while (running) {
138165
XNextEvent(dpy, &evt);
@@ -202,12 +229,39 @@ static void _nonnull_ serve_clipboard(uint64_t hash,
202229
}
203230

204231
int main(int argc, char *argv[]) {
205-
die_on(argc != 2, "Usage: clipserve [hash]\n");
206232
_drop_(config_free) struct config cfg = setup("clipserve");
207233
exec_man_on_help(argc, argv);
208234

235+
bool selection_active[CM_SEL_MAX] = {0};
236+
bool selection_set = false;
237+
int argi = 1;
238+
for (; argi < argc; argi++) {
239+
if (streq(argv[argi], "--")) {
240+
argi++;
241+
break;
242+
}
243+
if (streq(argv[argi], "-s") || streq(argv[argi], "--selection")) {
244+
die_on(argi + 1 >= argc,
245+
"Usage: clipserve [-s selection]... <hash>\n");
246+
enum selection_type sel = selection_name_to_type(argv[++argi]);
247+
die_on(sel == CM_SEL_INVALID, "Unknown selection: %s\n",
248+
argv[argi]);
249+
selection_active[sel] = true;
250+
selection_set = true;
251+
continue;
252+
}
253+
break;
254+
}
255+
256+
die_on(argi != argc - 1, "Usage: clipserve [-s selection]... <hash>\n");
257+
258+
if (!selection_set) {
259+
selection_active[CM_SEL_PRIMARY] = true;
260+
selection_active[CM_SEL_CLIPBOARD] = true;
261+
}
262+
209263
uint64_t hash;
210-
expect(str_to_hex64(argv[1], &hash) == 0);
264+
expect(str_to_hex64(argv[argi], &hash) == 0);
211265

212266
_drop_(close) int content_dir_fd = open(get_cache_dir(&cfg), O_RDONLY);
213267
_drop_(close) int snip_fd =
@@ -221,7 +275,7 @@ int main(int argc, char *argv[]) {
221275
die_on(cs_content_get(&cs, hash, &content) < 0,
222276
"Hash " PRI_HASH " inaccessible\n", hash);
223277

224-
serve_clipboard(hash, &content);
278+
serve_clipboard(hash, &content, selection_active);
225279

226280
return 0;
227281
}

src/util.c

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <stdbool.h>
44
#include <string.h>
55

6+
#include "config.h"
67
#include "store.h"
78
#include "util.h"
89

@@ -54,11 +55,27 @@ size_t snprintf_safe(char *buf, size_t len, const char *fmt, ...) {
5455
/**
5556
* Runs clipserve to handle selection requests for a hash in the clip store.
5657
*/
57-
void run_clipserve(uint64_t hash) {
58+
#define CLIPSERVE_MAX_ARGS (CM_SEL_MAX * 2 + 3)
59+
60+
void run_clipserve(uint64_t hash, const struct selection *selections) {
5861
char hash_str[CS_HASH_STR_MAX];
5962
snprintf(hash_str, sizeof(hash_str), PRI_HASH, hash);
6063

61-
const char *const cmd[] = {"clipserve", hash_str, NULL};
64+
const char *cmd[CLIPSERVE_MAX_ARGS];
65+
size_t cmd_idx = 0;
66+
cmd[cmd_idx++] = "clipserve";
67+
if (selections) {
68+
for (size_t i = 0; i < CM_SEL_MAX; i++) {
69+
if (!selections[i].active) {
70+
continue;
71+
}
72+
expect(cmd_idx + 2 < CLIPSERVE_MAX_ARGS);
73+
cmd[cmd_idx++] = "--selection";
74+
cmd[cmd_idx++] = selections[i].name;
75+
}
76+
}
77+
cmd[cmd_idx++] = hash_str;
78+
cmd[cmd_idx++] = NULL;
6279
pid_t pid = fork();
6380
expect(pid >= 0);
6481

src/util.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include <stdlib.h>
99
#include <unistd.h>
1010

11+
struct selection;
12+
1113
#define _drop_(x) __attribute__((__cleanup__(drop_##x)))
1214
#define _must_use_ __attribute__((warn_unused_result))
1315
#define _nonnull_ __attribute__((nonnull))
@@ -69,7 +71,7 @@ size_t _nonnull_ read_safe(int fd, char *buf, size_t count);
6971
size_t _printf_(3, 4)
7072
snprintf_safe(char *buf, size_t len, const char *fmt, ...);
7173

72-
void run_clipserve(uint64_t hash);
74+
void run_clipserve(uint64_t hash, const struct selection *selections);
7375

7476
/**
7577
* __attribute__((cleanup)) functions

tests/x_integration_tests

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ xsel -pc
116116
xsel -sc
117117

118118
clipmenud &
119+
clipmenud_pid=$!
119120
settle
120121

121122
# Should be empty
@@ -290,6 +291,37 @@ settle
290291

291292
check_nr_clips 6
292293

294+
# own_selections: default does not own PRIMARY
295+
kill "$clipmenud_pid"
296+
wait "$clipmenud_pid" || true
297+
pkill -x clipserve 2>/dev/null || true
298+
CM_OWN_CLIPBOARD=1 CM_OWN_SELECTIONS=clipboard clipmenud &
299+
clipmenud_pid=$!
300+
settle
301+
xsel -bc
302+
xsel -pc
303+
printf 'primary_stale' | xsel -p
304+
printf 'clip_default' | xsel -b
305+
settle
306+
[[ "$(xsel -bo)" == clip_default ]]
307+
primary_out=$(xsel -po 2>/dev/null || true)
308+
[[ "$primary_out" != clip_default ]]
309+
310+
# own_selections: owns all listed selections
311+
kill "$clipmenud_pid"
312+
wait "$clipmenud_pid" || true
313+
pkill -x clipserve 2>/dev/null || true
314+
CM_OWN_CLIPBOARD=1 CM_OWN_SELECTIONS='clipboard primary' clipmenud &
315+
clipmenud_pid=$!
316+
settle
317+
xsel -bc
318+
xsel -pc
319+
printf 'primary_stale' | xsel -p
320+
printf 'own_both' | xsel -b
321+
settle
322+
[[ "$(xsel -bo)" == own_both ]]
323+
[[ "$(xsel -po)" == own_both ]]
324+
293325
if (( _UNSHARED )); then
294326
umount -l /tmp
295327
fi

0 commit comments

Comments
 (0)