QMK firmware – Copy, Cut, Paste for Linux and Mac

The problem

I have a problem with the Copy, Cut, Paste and associated commands. Reason for this is that I use both Mac and Linux on a regular basis. On a Mac, the commands are Cmd+C, Cmd+X, Cmd+V, whereas on Linux they are, for the most part Ctrl+C, Ctrl+X, and Ctrl+V. Linux and Windows share the same keystrokes, whereas Mac is its own little snowflake. I am trying to commit keystrokes to muscle memory, so that frequently used commands, are always the same keystrokes, regardless of the OS.

QMK to the rescue

If your keyboard is programmable, and uses QMK, you are in luck, and keystrokes can be unified.

I have created commands for commands I use all the time, and which are have different keystrokes on different operating systems.  Here are the commands:

  • Copy
  • Paste
  • Cut
  • Select All
  • Undo
  • Redo

The infamous Windows Key

The idea is to create a flag, that tells the keyboard which mode it is in. If the isLinux flag is set to true, we will send Ctrl+key, otherwise the keyboard sends Cmd+key.  Note that Cmd is the Windows key. This key has many names:

  • Windows Key
  • Cmd
  • Super
  • Meta
  • GUI key
  • OS key

But all of these refer to the key that most often has a little Windows logo on it. Below I show that key, just to remove any possible confusion.

Issues with Ctrl+C and Ctrl+V in Linux terminals

If you are running Linux, Ctrl+C and Ctrl+V work the same in pretty much all windowed applications. Except in Terminal or terminal emulators. Reasons are historical, but Ctrl+C sends a linux SIGINT, which causes the Operating System to terminate the running program. All terminal emulators change the familiar commands to Ctrl+Shift+C, and Ctrl+Shift+V. Personally, I don’t like this, I want Copy Cut and Paste to use the familiar Ctrl only command, so in any terminal emulator, and in VS Code, I swap these sequences. Ctrl+Shift+C send SIGINT to kill the process, and Ctrl+C will copy to the clipboard.

Mapping a key to flip the isLinux flag

I map a key that will flip between the Linux/Windows (Ctrl+C) and Mac (Cmd+C). I place this key on my adjust layer. Pressing the key flips the flag.  By default, mine is set to isLinux=false meaning that Mac is the default when the system wakes up.  I don’t persist this to the eeprom non-volatile memory by design. I want the flag in a know state when I restart [unplug/replug] the keyboard.

QMK firmware

Here is how I set things up:

enum custom_keycodes {
  COLEMAK = SAFE_RANGE,
  QWERTY,
  LOWER,
  RAISE,
  ADJUST,
  FIND_G, // VSCode Replace in files Shift+Cmd+H
  FIND_L, // VSCode Replace in this file Option+Cmd+F
  SIG,
  KC_OCPRN,
  KC_OCBRC,
  KC_OCCBR,
  KC_OCDQUO,
  KC_OCQUOT,
  LEFT,
  RIGHT,
  TOP,
  BOTTOM,
  PATH,
  ALT_TAB,
  LINUX,
  COPY,
  PASTE,
  SALL,
  CUT,
  UNDO,
  REDO
};

At the bottom of the enum, you can see the definitions for the keys. Below is the actual code for the key defs.

Below is what the keyboard right half of my Keebio Iris keyboard looks like. To access the Copy functionality, I press and hold LOWER, and then tab COPY (U key in Querty mode). The blank key, in case you are wondering is the space key, the keyboard does not have a traditional spacebar. One does get used to this very quickly, by the way.

The handler, process_record_user()

uint8_t mod_state;
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
    static uint16_t my_hash_timer;
    static bool isLinux = false;
    mod_state = get_mods();
    switch (keycode) {
        case LINUX:
            if (record->event.pressed) {
                isLinux = !isLinux;
            }
            return false;
        case COPY:
            if (record->event.pressed) {
                if (isLinux) {
                    tap_code16(LCTL(KC_C));
                } else {
                    tap_code16(LGUI(KC_C));
                }
                return false;
            }
        case PASTE:
            if (record->event.pressed) {
                if (isLinux) {
                    tap_code16(LCTL(KC_V));
                } else {
                    tap_code16(LGUI(KC_V));
                }
            return false;
            }
        case CUT:
            if (record->event.pressed) {
                if (isLinux) {
                    tap_code16(LCTL(KC_X));
                } else {
                    tap_code16(LGUI(KC_X));
                }
                return false;
            }
        case UNDO:
            if (record->event.pressed) {
                if (isLinux) {
                    tap_code16(LCTL(KC_Z));
                } else {
                    tap_code16(LGUI(KC_Z));
                }
                return false;
            }
        case REDO:
            if (record->event.pressed) {
                if (isLinux) {
                    tap_code16(LCTL(KC_Y));
                } else {
                    tap_code16(LSG(KC_Z));
                }
                return false;
            }
        case SALL:
            if (record->event.pressed) {
                if (isLinux) {
                    tap_code16(LCTL(KC_A));
                } else {
                    tap_code16(LGUI(KC_A));
                }
                return false;
            }

I declare the static bool isLinux flag in this method, static just means its available all the time(scoped to the method) and is not stored on the stack. If the Linux Key is pressed and released, the event.pressed is true, and we flip the state. The Copy, Cut, Paste, Select All, Undo, Redo all follow the same pattern – we check the flag, and issue the appropriate keystroke for the currently selected OS.

Conclusion

Having the same keystrokes for commonly used commands helps build muscle memory and speed. Ultimately, this makes my life simpler. I hope this idea is helpful, let me know down in the comments if you have found this useful.

Leave a Reply