Hello,
I did similar things, just not for Rhino. I’m a big fan of low level approaches, although this has other drawbacks. In Windows, you can always p/invoke user32.dll (winapi) functionality and listen to your keyboard. There must be an equivalent for Mac as well.
So what you do is, you build an lowlevel keylogger and only listen if your Rhino-Instance has focus.
There are tons of examples on Stack overflow for both tasks, and there is a nice website called https://pinvoke.net/.
Unfortunate I don’t have access to the code I’ve written. So you have to find it for yourself 
here is an example of a low level send keys. you can extend ScanKeys enumeration, based on your Keyboard:
<pre>
class Keyboard
{
// Fields
private const uint INPUT_KEYBOARD = 1;
private const int KEY_EXTENDED = 0x0001;
private const uint KEY_UP = 0x0002;
private const uint KEY_SCANCODE = 0x0008;
// Enum
public enum ScanKeys : int
{
Zirk = 1, Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9,
Num0, Szet, Apos, Back, Tab, Q, W, E, R, T, Z, U, I, O, P, Ue, Plus, Ent, Ctrl, A,
S, D, F, G, H, J, K, L, Oe, Ae, Comp, Shift, Eqal, Y, X, C, V, B, N, M,
Comma, Pnt, Minus, ShiftR, Print, Alt, Space, Caps
}
// Native
[DllImport("User32.dll")]
internal static extern uint SendInput(uint numberOfInputs, [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)] KeyboardInput[] input, int structSize);
// Methods
public static void SendKey(ScanKeys key)
{
PressKey((int)key);
System.Threading.Thread.Sleep(20);
ReleaseKey((int)key);
}
public static void PressKey(int scanCode)
{
SendKey(scanCode, true);
}
public static void ReleaseKey(int scanCode)
{
SendKey(scanCode, false);
}
public static void SendString(string s)
{
foreach (char c in s)
{
switch (c)
{
case 'a':
SendKey(ScanKeys.A); break;
case 'b':
SendKey(ScanKeys.B); break;
case 'c':
SendKey(ScanKeys.C); break;
case 'd':
SendKey(ScanKeys.D); break;
case 'e':
SendKey(ScanKeys.E); break;
case 'f':
SendKey(ScanKeys.F); break;
case 'g':
SendKey(ScanKeys.G); break;
case 'h':
SendKey(ScanKeys.H); break;
case 'i':
SendKey(ScanKeys.I); break;
case 'j':
SendKey(ScanKeys.J); break;
case 'k':
SendKey(ScanKeys.K); break;
case 'l':
SendKey(ScanKeys.L); break;
case 'm':
SendKey(ScanKeys.M); break;
case 'n':
SendKey(ScanKeys.N); break;
case 'o':
SendKey(ScanKeys.O); break;
case 'p':
SendKey(ScanKeys.P); break;
case 'q':
SendKey(ScanKeys.Q); break;
case 'r':
SendKey(ScanKeys.R); break;
case 's':
SendKey(ScanKeys.S); break;
case 't':
SendKey(ScanKeys.T); break;
case 'u':
SendKey(ScanKeys.U); break;
case 'v':
SendKey(ScanKeys.V); break;
case 'w':
SendKey(ScanKeys.W); break;
case 'x':
SendKey(ScanKeys.X); break;
case 'y':
SendKey(ScanKeys.Y); break;
case 'z':
SendKey(ScanKeys.Z); break;
case '0':
SendKey(ScanKeys.Num0); break;
case '1':
SendKey(ScanKeys.Num1); break;
case '2':
SendKey(ScanKeys.Num2); break;
case '3':
SendKey(ScanKeys.Num3); break;
case '4':
SendKey(ScanKeys.Num4); break;
case '5':
SendKey(ScanKeys.Num5); break;
case '6':
SendKey(ScanKeys.Num6); break;
case '7':
SendKey(ScanKeys.Num7); break;
case '8':
SendKey(ScanKeys.Num8); break;
case '9':
SendKey(ScanKeys.Num9); break;
case ' ':
SendKey(ScanKeys.Space); break;
default:
break;
}
}
}
private static void SendKey(int scanCode, bool press)
{
KeyboardInput[] input = new KeyboardInput[1];
input[0] = new KeyboardInput();
input[0].type = INPUT_KEYBOARD;
input[0].flags = KEY_SCANCODE;
if ((scanCode & 0xFF00) == 0xE000)
{
input[0].flags |= KEY_EXTENDED;
}
if (press)
{
input[0].scanCode = (ushort)(scanCode & 0xFF);
}
else
{
input[0].scanCode = (ushort)scanCode;
input[0].flags |= KEY_UP;
}
uint result = SendInput(1, input, Marshal.SizeOf(input[0]));
if (result != 1)
{
throw new Exception("Could not send key: " + scanCode);
}
}
}
<pre>