isamert.net
About Feeds

Typing (unicode) characters programmatically on Linux and macOS


I've been using KMonad on my Linux boxes and on my Mac machine which I do use for work. KMonad is currently the best solution to have a consistent keyboard remapping across different operating systems and different keyboard types/layouts. It's not very well explained, but you can check out my KMonad configuration here.

One thing I also utilize KMonad for is typing unicode characters. I use them while coding or even casually messaging with people. They are more expressive and better looking. The usual way of inserting unicode characters is using the compose key on your operating system. KMonad also utilizes it, take a look at here to learn more about it. I don't use this approach because it's janky and limiting:

My solution is to use an external program to type for me. For example on X11 systems, you can use xdotool to write a character or multiple characters for you:

xdotool type "qwe"

When you run this, it literally behaves like you hit q, w and e on your keyboard. But xdotool is also not reliable when it comes to typing unicode characters. Most reliable and extendable solution I found is described in this post. pynput is great and it works! I created this script based on the answer I linked:

#!/usr/bin/env python

import sys
from pynput.keyboard import Controller

Controller().type(' '.join(sys.argv[1:]))

Save it into a file named xtype and you can do the following:

xtype "λ"

…and it will properly type "λ". It also works quite fine on Mac but I found it a bit slower. So here is a faster solution for Mac (which I simply copied from here but simplified the compiling process a bit for you):

#import <Foundation/Foundation.h>
// Following import may or may not be needed. It does not build on M1
// Pro without this but it was building on 2019 Pro.
#import <CoreGraphics/CoreGraphics.h>

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    if (argc > 1) {
      NSString *theString = [NSString stringWithUTF8String:argv[1]];
      NSUInteger len = [theString length];
      NSUInteger n, i = 0;
      CGEventRef keyEvent = CGEventCreateKeyboardEvent(nil, 0, true);
      unichar uChars[20];
      while (i < len) {
        n = i + 20;
        if (n>len){n=len;}
        [theString getCharacters:uChars range:NSMakeRange(i, n-i)];
        CGEventKeyboardSetUnicodeString(keyEvent, n-i, uChars);
        CGEventPost(kCGHIDEventTap, keyEvent); // key down
        CGEventSetType(keyEvent, kCGEventKeyUp);
        CGEventPost(kCGHIDEventTap, keyEvent); // key up (type 20 characters maximum)
        CGEventSetType(keyEvent, kCGEventKeyDown);
        i = n;
        [NSThread sleepForTimeInterval:0.004]; // wait 4/1000 of second, 0.002 it's OK on my computer, I use 0.004 to be safe, increase it If you still have issues
      }
      CFRelease(keyEvent);
    }
  }
  return 0;
}

Save this into a file named xtype.m and compile it with the following command:

clang -framework Foundation -framework ApplicationServices xtype.m -l objc -o xtype

Now, again, you can do:

xtype "λ"

…and it will properly type "λ", but a bit faster this time. This might be a bit overkill, you can still make use of pynput on Mac and call it a day.

Now you can use this xtype command on KMonad (see the tutorial.kbd to learn about running commands with (cmd-button "...")), or using your keybinding manger like sxhkd etc.

Unfortunately, pynput does not work on a pure Wayland session at the moment but there is an issue about it that you can track. Meanwhile, you can try wtype instead. It works with unicode characters, as stated in the README but this tool does not work on GNOME or KDE Wayland sessions. I haven't been able to find a good solution for Wayland but that is the situation for quite a lot of things in Wayland, at least for now.