Synchronizing Clipboards across two Wayland Compositors
Recently, I started running a Wayland compositor inside another Wayland compositor and needed to copy-and-paste stuff between them, which doesn't work out of the box.
I figured I need something that synchronizes the selections ('clipboards') of the two compositors, but I couldn't find an existing solution.
I thus created wlclipsync to solve this task.
Motivation¶
Running a Wayland compositor nested inside another is great for testing purposes and special setups.
For example, say you want to evaluate alternate compositors, you can start by running one nested for convenient restarts when rapidly tuning configurations, i.e. without having to tear-down and re-open your whole desktop session.
Or - say - your main compositor doesn't support window tabbing (such as KWin), but you want to arrange multiple applications in tabs, you can simply start them inside a nested compositor that supports tabbing (such as Sway).
Some applications (such as Konsole), re-implement tabbing inside their windows, to work-around missing compositor features, but this doesn't help you in general, i.e. when mixing windows of different applications or when using multiple instances of an application that rightly decided to not add their own tabbing (such as the fine terminal foot).
Dead Ends¶
First, I was surprised that copy'n'paste didn't work out of the box between my two Wayland compositors (i.e. Sway nested inside KWin). But after looking into how Wayland compositors support it via additional primary/clipboard selection protocols, it starts to make sense.
So after understanding that programs running in different compositors manage their disjoint sets of selections ('clipboards'), coordinated by each compositor, it suggests itself to look for a way to synchronize the selections.
I found wl-clipboard which can be used to push selections from one compositor into the other, but when trying to do this in both directions it fails spectacularly, because then copy-n-paste events are looped back and forth in an infinite loop.
The Solution¶
I thus created wlclipsync for bidirectionally synchronizing the selections ('clipboards') between two Wayland compositors.
wlclipsync is a single threaded C application which connects to the two Wayland compositors, registers itself in both for primary and clipboard selection offer and request events, and forwards them inside an event loop, while breaking forwarding loops between the compositors.
See also the wlclipsync README for more technical details, especially the following sections:
Addendum: Challenges¶
Arguably, the default Wayland API and C bindings are documented less extensively than - say - the Python standard library or the Postgresql database. There aren't that many examples, some examples follow the old C cliché to leave error handling as an exercise to the reader, i.e. in the name of simplifying the presentation and saving the reader from verbosity, even when the error handling is very tricky and crucial for real programs.
Event Loop¶
For example, since my program wlclipsync connects to two compositors, I cannot use the standard Wayland client high-level loop:
while (wl_display_dispatch(display) != -1) {
/* This space deliberately left blank */
}
The Wayland manual mentions that it's possible to integrate a custom event loop, but is a bit hand-wavy about it:
However, if you have a more sophisticated application, you can build your own event loop in any manner you please, and obtain the Wayland display's file descriptor with
wl_display_get_fd. UponPOLLINevents, callwl_display_dispatchto process incoming events. To flush outgoing requests, callwl_display_flush.
The Wayland API docs contain an incomplete example, but only on a function variant one wouldn't use. And even if you end up finding that snippet, you likely are wasting some time researching how to obtain or possibly create the right queue for that variant. While still having only a rough-edged example to build on.
I thus include here a real-world custom event loop for a normal Wayland client, as a reference implementation:
struct wl_display *dis = wl_display_connect(display_str);
// ...
struct pollfd pls[] = {
{ .fd = wl_display_get_fd(cs[0]->dis) , .events = POLLIN },
// setup other filedescriptors you need to watch ...
//
};
if (pls[0].fd == -1) {
fprintf(stderr, "failed to get wayland file descriptor\n");
return 1;
}
// ...
for (bool keep_on = true; keep_on;) {
// NB: wl_display_prepare_read uses default queue
// cf. wl_display_prepare_read_queue() for event loop integration notes
while (wl_display_prepare_read(dis)) {
if (errno != EAGAIN) {
perror("wl_display_prepare_read");
return 1;
}
int r = wl_display_dispatch_pending(dis);
if (r == -1) {
fprintf(stderr, "wl_display_dispatch_pending failed\n");
return 1;
}
}
pls[i].events &= ~POLLOUT;
int r = wl_display_flush(dis);
if (r == -1) {
if (errno == EAGAIN) {
pls[i].events |= POLLOUT;
} else {
fprintf(stderr, "wl_display_flush failed\n");
return 1;
}
}
bool interrupted = false;
int r = poll(pls, sizeof pls / sizeof pls[0], -1);
if (r == -1) {
if (errno == EINTR) {
interrupted = true;
} else {
wl_display_cancel_read(dis);
perror("poll");
return 1;
}
}
r = wl_display_read_events(dis);
if (r == -1) {
perror("wl_display_read_events");
return 1;
}
r = wl_display_dispatch_pending(dis);
if (r == -1) {
fprintf(stderr, "wl_display_dispatch_pending failed\n");
return 1;
}
if (interrupted)
continue;
for (unsigned i = 0; i < sizeof pls / sizeof pls[0]; ++i) {
if (!pls[i].revents)
continue;
if ((pls[i].revents & (POLLIN | POLLHUP)) != pls[i].revents) {
fprintf(stderr, "Unexpected event on poll element %u: %x\n",
i, (unsigned)pls[i].revents);
return 1;
}
switch (i) {
case 0:
trace("wayland ready for reading ...");
break;
// take care of other filedescriptors ...
// ...
case ...:
trace("received shutdown - terminating ...");
keep_on = false;
break;
}
}
}
See also the wlclipsync source for the multiple connection version and proper tear-down which simplifies working with memory leak detectors.
Callbacks¶
The default Wayland C bindings come with a code generator that generates old-school callback-heavy APIs.
Since Wayland clients have to deal with many events, using some kind of asynchronous programming paradigm is necessary. And callbacks via function pointers is one way to implement such an asynchronous model. However, this often leads to some form a callback hell, to some degree.
While implementing wlclipsync I was looking for alternative Wayland language bindings which would allow a more pull-based approach that works like this:
A client 'just' pulls from a Wayland event source object in its own custom loop and is free to implement its own event dispatch in any way it wants. When there are no more events the client may poll on some relevant file descriptor, as with the callback API.
However, I haven't found such pull-based language bindings or API, so far.