universal copy/paste in linux

I’d like to use the same copy/paste keyboard bindings in every application on linux. I spent some time determining if such is possible (spoiler, at best it’s hacky).

Code is available here.

 linux clipboard basics

Linux copy/paste works through an X11 primitive called Selections.

Selections are the primary mechanism X11 defines for clients that want to exchange information with other clients. A selection transfers arbitrary information between two clients. You can think of a selection as a piece of text or graphics that is highlighted in one application and can be pasted into another, though the information transferred can be almost anything. Clients are strongly encouraged to use this mechanism so that there is a uniform procedure in use by all applications.

(Volume One: Xlib Programming Manual/Chapter 12/Selections)

The Inter Client Communications Conventions Manual (ICCCM) defines 3 standard selections: PRIMARY, SECONDAY, CLIPBOARD. There are two interpretations of how the Selections should be used.

use PRIMARY for mouse selection, middle mouse button paste, and explicit cut/copy/paste menu items (Qt 2, GNU Emacs 20)

use CLIPBOARD for the Windows-style cut/copy/paste menu items; use PRIMARY for the currently-selected text, even if it isn’t explicitly copied, and for middle mouse-click (Netscape, Mozilla, XEmacs, some GTK+ apps.

Current consensus is that the second interpretation is correct.

(Clipboards Spec)

 lets build it

The goal is to use the same key combination for copy/paste in every application.

combo effect
ctrl+shift+q copy text to CLIPBOARD
ctrl+shift+a paste text from CLIPBOARD
ctrl+shift+z paste text from PRIMARY

Every application supporting copy/paste must handle three X11 Selection events.

Event Notes
SelectionClear This event is reported to the current owner of a selection and is generated when a new owner is being defined by means of SetSelectionOwner.
SelectionRequest This event is reported to the owner of a selection and is generated when a client issues a ConvertSelection request. The owner argument is the window that was specified in the SetSelectionOwner request.
SelectionNotify …it should be generated by the owner using SendEvent. The owner of a selection should send this event to a requestor either when a selection has been converted and stored as a property or when a selection conversion could not be performed (indicated with property None).

(X Window System Protocol)

 paste from CLIPBOARD

  1. User issues key/button combination which the client interprets as a request to ‘paste’.
  2. The X11 client (process) calls XConvertSelection().
  3. The X11 server places the XConvertSelection() arguments into an XSelectionRequestEvent and sends the event to the selection owner.
  4. The Selection owner (XSetSelectionOwner()) puts the requested data into the property specified by the XSelectionRequestEvent in the initial client’s window.
  5. The Selection owner then uses XSendEvent() to send a SelectionNotify event to the client informing him that his data has arrived.
  6. The client is now free to access the data from the specified property using XGetWindowProperty().

 copy to CLIPBOARD

  1. User issues key/button combination.
  2. Process creates an internal copy of the selected text.
  3. Process calls XSetSelectionOwner() announcing to the world that he will handle SelectionRequests.

X Toolkit Intrinsics (Xt) provide an analogous higher level interface.

action Xlib libXt
request selection value XConvertSelection
XGetWindowProperty
XtGetSelectionValue
provide selection value XSetSelectionOwner
XSendEvent
XtOwnSelection

 what are properties?

Properties are simple {key, value} datums associated with each window. Here’s a simple piece of code to show a Window’s properties.

// snippet from src/util.c

void
print_properties(Display *const dpy, Window w)
{
    int prop_count;
    Atom *const atoms = XListProperties(dpy, w, &prop_count);
    printf("property count: %d\n", prop_count);
    for (int i = 0; i < prop_count; ++i) {
        unsigned long rem;
        int format;
        unsigned char *xdata;
        char *data = NULL;

        if (Success == XGetWindowProperty(dpy, w, atoms[i], 0, 32, False, AnyPropertyType,
                    &(Atom){0}, &format, &(unsigned long){0}, &rem, &xdata)) {
            if (8 == format) {
                data = (NULL == xdata) ? strdup("<null>") : strdup((char *)xdata);
            } else {
                data = strdup("< non-char data >");
            }
            XFree(xdata);
        } else {
            data = strdup("<Failed to read property value>");
        }

        printf("[%d]: %s => %s%s\n", i, XGetAtomName(dpy, atoms[i]), data ? data : "< ??? >", rem ? "..." : "");
        free(data);
    }

    XFree(atoms);
}

Use xprop for a more complete investigation of a Window’s Properties.

 low level universal paste algorithm

  1. Users issues key combo.
  2. Key combo invokes daemon through xbindkeys.
  3. Daemon asks X Server to send SelectionRequest to current owner on behalf of the ‘focused’ window.
  4. X Server sends request.
  5. Selection owner updates the property in the focused window and then sends a SelectionNotify to the same window.
  6. The process running in the focused window uses an internal algorithm to handle the ‘paste event’.

Useful debugging code.

// src/owner.c

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>

#include <X11/Xlib.h>
#include <X11/Xatom.h>

#include "util.h"

const char *Yes     = "YES";
const char *No      = "NO";
const char *Unknown = "UNKNOWN";

typedef unsigned char uchar;

static void selrequest(XEvent *);

static void (*handler[LASTEvent])(XEvent *) = {
    [KeyPress] = noop,
    [ClientMessage] = noop,
    [ConfigureNotify] = noop,
    [VisibilityNotify] = noop,
    [UnmapNotify] = noop,
    [Expose] = noop,
    [FocusIn] = noop,
    [FocusOut] = noop,
    [MotionNotify] = noop,
    [ButtonPress] = noop,
    [ButtonRelease] = noop,
    [SelectionClear] = noop,
    [SelectionNotify] = noop,
    [SelectionRequest] = selrequest,
};


Display *dpy;

int
main()
{
    dpy = XOpenDisplay(NULL);
    if (!dpy) {
        fprintf(stderr, "XOpenDisplay failed!\n");
        abort();
    }

    Window w;
    XGetInputFocus(dpy, &w, &(int){0}); // see man
    if(w == None) {
        fprintf(stderr, "no focus window\n");
        XCloseDisplay(dpy);
        return 1;
    }


    // Own clipboards
    XSetSelectionOwner(dpy, XA_PRIMARY, w, CurrentTime);

    Atom clipboard = XInternAtom(dpy, "CLIPBOARD", 0);
    XSetSelectionOwner(dpy, clipboard, w, CurrentTime);
    // --

    XEvent ev;
    while (true) {
        XNextEvent(dpy, &ev);
        if(XFilterEvent(&ev, None))
            continue;
        if(handler[ev.type]) (handler[ev.type])(&ev);
    }

    return 0;
}

static void
selrequest(XEvent *ev)
{
    printf("selrequest\n");
    XSelectionRequestEvent *xsre;
    XSelectionEvent xev;

    xsre = (XSelectionRequestEvent *)ev;
    xev.type = SelectionNotify;
    xev.requestor = xsre->requestor;
    xev.selection = xsre->selection;
    xev.target = xsre->target;
    xev.time = xsre->time;
    xev.property = None;

    // ripped from xev
    {
        const XAnyEvent *const e = (XAnyEvent *) ev;
        printf ("\tSelectionRequest event, type %d, serial %ld, synthetic %s, window 0x%lx\n",
            e->type, e->serial, e->send_event ? Yes : No, e->window);
    }

    {
        const XSelectionRequestEvent *const e = (XSelectionRequestEvent *) ev;
        char *sname = XGetAtomName (dpy, e->selection);
        char *tname = XGetAtomName (dpy, e->target);
        char *pname = XGetAtomName (dpy, e->property);

        printf ("\t    owner 0x%lx, requestor 0x%lx, selection 0x%lx (%s),\n",
            e->owner, e->requestor, e->selection, sname ? sname : Unknown);
        printf ("\t    target 0x%lx (%s), property 0x%lx (%s), time %lu\n\n",
            e->target, tname ? tname : Unknown, e->property,
            pname ? pname : Unknown, e->time);

        XFree (sname);
        XFree (tname);
        XFree (pname);
    }
    // -- xev

    assert(xsre->target == XInternAtom(dpy, "UTF8_STRING", 0));

    const char msg[] = "hello world!";
    XChangeProperty(xsre->display, xsre->requestor, xsre->property,
            xsre->target, 8, PropModeReplace,
            (uchar *) msg, strlen(msg));
    xev.property = xsre->property;

    /* all done, send a notification to the listener */
    if (!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *) &xev)) {
        fprintf(stderr, "Error sending SelectionNotify event\n");
    }
}
// src/client.c
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>

#include <X11/Xlib.h>
#include <X11/Xatom.h>

#include "util.h"

const char *Yes = "YES";
const char *No = "NO";

typedef unsigned char uchar;
typedef unsigned long ulong;

static void selnotify(XEvent *);

static void (*handler[LASTEvent])(XEvent *) = {
    [KeyPress] = noop,
    [ClientMessage] = noop,
    [ConfigureNotify] = noop,
    [VisibilityNotify] = noop,
    [UnmapNotify] = noop,
    [Expose] = noop,
    [FocusIn] = noop,
    [FocusOut] = noop,
    [MotionNotify] = noop,
    [ButtonPress] = noop,
    [ButtonRelease] = noop,
    [SelectionClear] = noop,
    [SelectionNotify] = selnotify,
    [SelectionRequest] = noop,
};

Display *dpy;
Window w;

int
main()
{
    dpy = XOpenDisplay(NULL);
    if (!dpy) {
        fprintf(stderr, "XOpenDisplay failed!\n");
        abort();
    }
    const int scr = XDefaultScreen(dpy);
    w = XCreateSimpleWindow(dpy, RootWindow(dpy, scr), 10, 10, 100, 100, 1,
                            BlackPixel(dpy, scr), WhitePixel(dpy, scr));
    XMapWindow(dpy, w);

    const Atom xtarget = XInternAtom(dpy, "UTF8_STRING", 0);


    XEvent ev;
    while (true) {
        XConvertSelection(dpy, XA_PRIMARY, xtarget, XA_PRIMARY, w, CurrentTime);
        sleep(1);

        XNextEvent(dpy, &ev);
        if(XFilterEvent(&ev, None))
            continue;
        if(handler[ev.type]) (handler[ev.type])(&ev);
    }

    return 0;
}

static void
selnotify(XEvent *const eventp)
{
    printf("selnotify!\n");

    ulong nitems, rem;
    int format;
    uchar *data;
    Atom type;

    if(XGetWindowProperty(dpy, w, XA_PRIMARY, 0, 32, False, AnyPropertyType,
                &type, &format, &nitems, &rem, &data)) {
        fprintf(stderr, "Clipboard allocation failed\n");
        return;
    }
    assert(0 == rem);

    // ripped from xev
    {
        const XAnyEvent *const ev = (XAnyEvent *)eventp;
        printf ("\tSelectionNotify event, type %d, serial %ld, synthetic %s, window 0x%lx, time %lu\n",
            ev->type, ev->serial, ev->send_event ? Yes : No, ev->window, ((XSelectionEvent *)eventp)->time);
    }
    // -- xev

    printf("\tdata: %s\n\n", data);
}

Start owner and client; output will look something like this.

[burrows@flx clipboard]$ ./obj/owner 
selrequest
        SelectionRequest event, type 30, serial 10, synthetic NO, window 0x1e00022
            owner 0x1e00022, requestor 0x2200001, selection 0x1 (PRIMARY),
            target 0x168 (UTF8_STRING), property 0x1 (PRIMARY), time 0

selrequest
        SelectionRequest event, type 30, serial 14, synthetic NO, window 0x1e00022
            owner 0x1e00022, requestor 0x2200001, selection 0x1 (PRIMARY),
            target 0x168 (UTF8_STRING), property 0x1 (PRIMARY), time 0

selrequest
        SelectionRequest event, type 30, serial 16, synthetic NO, window 0x1e00022
            owner 0x1e00022, requestor 0x2200001, selection 0x1 (PRIMARY),
            target 0x168 (UTF8_STRING), property 0x1 (PRIMARY), time 0
[burrows@flx clipboard]$ ./obj/client 
selnotify!
        SelectionNotify event, type 31, serial 10, synthetic YES, window 0x2200001, time 0
        data: hello world!

selnotify!
        SelectionNotify event, type 31, serial 12, synthetic YES, window 0x2200001, time 0
        data: hello world!

selnotify!
        SelectionNotify event, type 31, serial 14, synthetic YES, window 0x2200001, time 0
        data: hello world!

owner seems to be somewhat reliable; so let’s try injecting a paste with it.

// src/paste.c
#include <stdlib.h>
#include <stdio.h>
#include <locale.h>
#include <stdbool.h>

#include <X11/Xatom.h>
#include <X11/Xlib.h>

#include "util.h"

#define DEFAULT_ATOM_TEXT "PRIMARY"

int
main(int argc, char **argv)
{
    char *atom_text;
    if (argc == 2) {
        atom_text = argv[1];
    } else {
        atom_text = DEFAULT_ATOM_TEXT;
    }

    Display *const dpy = XOpenDisplay(NULL);
    if (!dpy) {
        fprintf(stderr, "XOpenDisplay failed!\n");
        return 1;
    }

    Window w;
    XGetInputFocus(dpy, &w, &(int){0}); // see man
    if(w == None) {
        fprintf(stderr, "no focus window\n");
        XCloseDisplay(dpy);
        return 1;
    }

    print_window_info(dpy, w);

    Atom xtarget = XInternAtom(dpy, "UTF8_STRING", 0);

    // We can't assume what property applications want incoming data to be
    // put into.
    Atom prop = XInternAtom(dpy, atom_text, 0);
    XConvertSelection(dpy, XA_PRIMARY, xtarget, prop, w, CurrentTime);

    XCloseDisplay(dpy);
    return 0;
}

Now run paste+xbindkeys, owner and st (http://st.suckless.org/). xbindkeys needs a config file.

# ~/.xbindkeysrc
"/path/to/clipboard/obj/paste"
  control+shift + a

Run it.

# terminal 0
xbindkeys -n

# terminal 1
# compile/build st
./st

#terminal 2
./owner

The goal is to use the universal paste key binding to get text into st: do ctrl+shift+a in the st window. st should show the pasted text from owner.

Repeat the same process with chromium instead of st. The paste doesn’t work.

Close the running programs and restart only owner and chromium. Then try ctrl+v in chromium. The output from owner should now show us what a legitimate SelectionRequest from chromium looks like.

[burrows@flx clipboard]$ ./obj/owner
selrequest
        SelectionRequest event, type 30, serial 10, synthetic NO, window 0x3400022
            owner 0x3400022, requestor 0xc0004b, selection 0x1c0 (CLIPBOARD),
            target 0x1bf (TARGETS), property 0x1ca (CHROME_SELECTION), time 0

owner: src/owner.c:116: selrequest: Assertion `xsre->target == XInternAtom(dpy, "UTF8_STRING", 0)' failed.
Aborted (core dumped)

chromium violates our assumptions.

  1. It expects the resultant SelectionNotify to have type TARGETS (as opposed to UTF8_STRING).
  2. It wants the selection data put into the CHROME_SELECTION property instead of the PRIMARY property (not to be confused with the PRIMARY selection).

This highlights three fundamental problems with our injection algorithm.

  1. The injector can’t know if the process it’s acting on behalf of uses XA_TARGETS to negotiate data type.
    • “Normally, a requestor would first call XtGetSelectionValue for XA_TARGETS, and then in the callback determine which target it wants to request from the list, and then call XtGetSelectionValue again for the desired target with a separate callback to process the actual data. This is really two separate selection transfers.” (Volume One: Xlib Programming Manual/Chapter 11/Selections: Widget-to-Widget Communication)
  2. It can’t support programs doing stateful stuff with XConvertSelection and the SelectionNotify handler.
  3. The injector doesn’t know what property the focused program wants the selection data to be stuck into. Could be fixed with a hardcoded [application => property] database.
    • We’ve already seen that st uses PRIMARY and. chromium uses CHROME_SELECTION. Also note that XtGetSelectionValue uses _XT_SELECTION_0.

This algorithm isn’t powerful enough to support universal paste.

 Low Level Copy Algorithm

This seems like a total non starter. The application running in the focused window needs to become Selection owner and maintain some internal state so that it can respond to SelectionRequest events. The injection program could issue XSetSelectionOwner on behalf of the focused window, transferring ownership as desired. But how to make the process update it’s internal state?

 notes on debugging selection injection

xev is useful for debugging, but it has limitations. For example, start client and owner. Then monitor client with xev. You’ll see that xev is reporting PropertyNotify events while client shows SelectionNotify events. Why is this?

Unfortunately, xev is not fool proof if you ask it to track events on a window it does not own (the -id mode). It receives these events by requesting them with Xlib’s XSelectInput() function. This technique has three major limitations:



non-maskable events - The X protocol does not allow certain events to be reported to clients other than the one that created the event window. Most of these events deal with selections.

(http://www.rahul.net/kenton/events.html#LimitationsOf)

Non-maskable events: (GraphicsExpose, NoExpose, SelectionClear, SelectionRequest, SelectionNotify, ClientMessage, and MappingNotify)

The above link mentions that xmon and xscope are viable alternatives for monitoring all events intended for some window.

bash> xrdb -query -all

Is useful for examining the current state of xterm.

 high level emulation algorithm

Low level event sending didn’t work, so I wrote a high level input emulator.

// src/emulate.c

// Working
// + st
// + xterm
//   > Must set allowSendEvents to true in ~/.Xresources
// + firefox
//   > the focused window doesnt have _NET_WM_PID, but it's parent does.
// Broken
// + chromium

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <assert.h>
#include <unistd.h>

#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>

#include "util.h"

struct Recipe {
    char pname[100];
    char action[100];
    unsigned long key;
    unsigned long mask;
};

struct Recipe recipes[] = {
    {"xterm",       "paste",    XK_Insert,  ShiftMask},
    {"st",          "paste",    XK_Insert,  ShiftMask},
    {"firefox",     "paste",    XK_Insert,  ShiftMask},
    {"firefox",     "copy",     XK_c,       ControlMask},
    // {"chromium",    "paste",    XK_Insert, ShiftMask}
};

bool get_window(Display *const dpy, Window *const out_w);
bool get_recipe(const char *const p, const char *const a, struct Recipe *r);
bool simulate_key_press(Display *const dpy, Window w, Atom type, int keycode, int mods);

int
main(int argc, char **argv)
{
    if (argc != 2) {
        fprintf(stderr, "usage: %s <action>\n", argv[0]);
        return 1;
    }

    Display *const dpy = XOpenDisplay(NULL);
    if (!dpy) {
        fprintf(stderr, "XOpenDisplay failed!\n");
        return 1;
    }

    Window w;
    if (false == get_window(dpy, &w)) {
        fprintf(stderr, "get_window failed\n");
        XCloseDisplay(dpy);
        return 1;
    }

    unsigned long pid;
    if (false == window_id_to_pid(dpy, w, &pid)) {
        fprintf(stderr, "window_id_to_pid failed\n");
        XCloseDisplay(dpy);
        return 1;
    }

    char *p;
    if (false == get_process_name_from_window(dpy, w, &p)) {
        fprintf(stderr, "get_process_name_from_window failed\n");
        XCloseDisplay(dpy);
        return 1;
    }

    struct Recipe r;
    if (!get_recipe(p, argv[1], &r)) {
        fprintf(stderr, "no recipe for: %s (%s)\n", p, argv[1]);
        free(p);
        XCloseDisplay(dpy);
        return 1;
    }

    if (false == simulate_key_press(dpy, w, KeyPress, r.key, r.mask)) {
        fprintf(stderr, "simulated key press failed\n");
        free(p);
        XCloseDisplay(dpy);
        return 1;
    }
    if (false == simulate_key_press(dpy, w, KeyRelease, r.key, r.mask)) {
        fprintf(stderr, "simulated key release failed\n");
        free(p);
        XCloseDisplay(dpy);
        return 1;
    }

    free(p);
    XCloseDisplay(dpy);
    return 0;
}

bool
get_window(Display *const dpy, Window *const out_w)
{
    assert(out_w);

    Window w;
    XGetInputFocus(dpy, &w, &(int){0});
    if(w == None) {
        fprintf(stderr, "no focus window\n");
        return false;
    }

    // Firefox Hack
    // > We want to send the keyboard events to the parent of the focused
    //   window in firefox; XGetInputFocus() returns an unnamed child window
    //   that every instance of Firefox has.  We want the parent window.
    //
    //   xwininfo -tree -root 
    //
    //   ^ For more info
    {
        unsigned int child_count;
        Window root, parent, *children;
        if (0 == XQueryTree(dpy, w, &root, &parent, &children, &child_count)) {
            perror("XQueryTree failed\n");
            return false;
        }

        if (children) {
            XFree(children);
        }

        char *parent_name;
        if (false == get_process_name_from_window(dpy, parent, &parent_name)) {
            fprintf(stderr, "get_process_name_from_window failed, but it is probably okay to continue\n");
            *out_w = w;
            return true;
        }

        *out_w = !strcasecmp("firefox", parent_name) ? parent : w;
        free(parent_name);
    }

    return true;
}

bool
get_recipe(const char *const p, const char *const a, struct Recipe *r)
{

    for (size_t i = 0; i < sizeof(recipes)/sizeof(struct Recipe); ++i) {
        if (!strcasecmp(recipes[i].pname, p) && !strcasecmp(recipes[i].action, a)) {
            *r = recipes[i];
            return true;
        }
    }

    return false;
}

bool
simulate_key_press(Display *const dpy, Window w, Atom type, int keycode, int mods)
{
    assert(type == KeyPress || type == KeyRelease);
    XKeyEvent event =
    {
        .display     = dpy,
        .window      = w,
        .root        = XDefaultRootWindow(dpy),
        .subwindow   = None,
        .time        = CurrentTime,
        .x           = 1,
        .y           = 1,
        .x_root      = 1,
        .y_root      = 1,
        .same_screen = True,
        .keycode     = XKeysymToKeycode(dpy, keycode),
        .state       = mods,
        .type        = type
    };

    return !!XSendEvent(dpy, w, False, KeyPressMask, (XEvent *)&event);
}

 xterm

By default, xterm doesn’t allow other applications to send it keyboard events using XSendEvent. From the xterm man page,

allowSendEvents: Specifies whether or not synthetic key and button events (generated using the X protocol SendEvent request) should be interpreted or discarded. The default is “false” meaning they are discarded. Note that allowing such events creates a very large security hole, and forcefully disables the allowXXXOps resources. The default is “false.”
In order to make this code work with Xterm you’ll need to.

xev output from an XSendEvent keyboard event

KeyPress event, serial 21, synthetic YES, window 0x1800022,
    root 0xac, subw 0x0, time 0, (1,1), root:(1,1),
    state 0x1, keycode 118 (keysym 0xff63, Insert), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

xev output from physical keyboard event

KeyPress event, serial 21, synthetic NO, window 0x1800022,
    root 0xac, subw 0x0, time 107040360, (1374,823), root:(1375,839),
    state 0x1, keycode 118 (keysym 0xff63, Insert), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

Look closely at the output for each event, in particular the ‘synthetic’ attribute.

Now consider the xterm source. There is a conditional in xterm/misc.c at the end of xevents() that considers the ‘syntheticness’ of the event (XAnyEvent::send_event). A one line change is required in ~/.Xresources to prevent this conditional from dropping the emulated key press.

# ~/.Xresources
xterm*allowSendEvents: true

Depending on the particular linux setup, the following may also be necessary.

bash> xrdb -merge ~/.Xresources

 Firefox

XGetInputFocus() returns a window with no properties when Firefox is focused. This window is the child of the window returned by xwininfo when we click on Firefox. This parent window has all the properties that we’re interested in (_NET_WM_PID, WM_CLASS) and from earlier testing, it supports keyboard emulation.

The get_window function in emulate has a hack to support this Firefox behavior.

 Chromium

Don’t have Chromium working yet.

 Running the Code

Create ~/.xbindkeysrc

# ~/.xbindkeysrc
# copy
"/home/burrows/code/clipboard/obj/emulate copy"
  control+shift + q

# paste
"/home/burrows/code/clipboard/obj/emulate paste"
  control+shift + a

Start xbindkeys

bash> xbindkeys -n
application current support
firefox copy
paste
xterm paste
st paste

Try some supported action. All supported actions currently use the CLIPBOARD selection, but adding support for PRIMARY seems doable. It’s mostly a matter of figuring out how to structure the code so that it can support emulated mouse events (PRIMARY paste uses middle click).

 Summary

How useful is this daemon? What if every program allowed you to choose the keybinding for copy/paste? Would we prefer to configure each program individually?

# ~/.Xresources
# example of xterm configurability

xterm*VT100.Translations: #override \
Shift<Key>Insert: insert-selection(CLIPBOARD) \n\
<Key>Insert: insert-selection(PRIMARY)

How sane is it to do the above for each program we use? Do we care that we can’t update the keybindings for all programs in one place?

On the other hand, if every program that lacks xterm’s configurability requires us to do a firefox-esque hack, will we care enough to create and maintain such work?

 
191
Kudos
 
191
Kudos

Now read this

optimal OOP play against a polarized range

Consider this simplified river spot. Hero is OOP with a condensed range against Villain’s polarized range. Hero’s hands can only beat Villain’s bluffs. Let’s also say that each player can only use an unmixed strategy for their entire... Continue →