So the whole thing is made in Arduino, one Attiny85 board acting as a USB client, a HID keyboard, and another board, a Nano (ATMega 328P) on the lookout for button presses. The two communicate via serial.

And the source code is published on github here (the USB-handling attiny85) and here (and the buttons-handling nano).

Below is the very first, unoptimized implementation, barely fitting onto the flash.

The Attiny85 ino file, the USB client

#include <SoftSerial_INT0.h>
#include "DigiKeyboard.h"

#define P_RX 2                        
#define P_TX 1                        
  
SoftSerial softSerial(P_RX, P_TX);           

void setup() {
  // put your setup code here, to run once:
  softSerial.begin(9600);  
  DigiKeyboard.sendKeyStroke(0);
}


char incoming[32];
byte modifMask;

void modifMask_check(byte m, byte b){  
    if (0 != (b & m)){
      modifMask |= b;
      DigiKeyboard.sendKeyPress(0, modifMask);
      DigiKeyboard.delay(90);
    }
}    

void digestIncoming(){
  if (strlen(incoming) < 2){
    return ;
  }
  if ('e' == incoming[1]){
    char s[2];
    s[0] = incoming[2];
    s[1] = 0;
    DigiKeyboard.print(s);    
    return ;
  }
  if ('S' == incoming[1]){
    //
    char s[3];
    s[0] = incoming[2];
    s[1] = incoming[3];
    s[2] = 0;
    byte k = strtol(s, NULL, 16);
    s[0] = incoming[4];
    s[1] = incoming[5];
    s[2] = 0;
    byte m = strtol(s, NULL, 16);
    DigiKeyboard.sendKeyStroke(k, m);
  }
  if ('s' == incoming[1]){
    char s[3];
    // sequentially press stuff, modifiers first
    s[0] = incoming[4];
    s[1] = incoming[5];
    s[2] = 0;
    byte m = strtol(s, NULL, 16);

    modifMask = 0;
    for (int j=0; j<8; j++){
      modifMask_check(m, (1 << j));
    }
    s[0] = incoming[2];
    s[1] = incoming[3];
    s[2] = 0;
    byte k = strtol(s, NULL, 16);
    DigiKeyboard.sendKeyPress(k, m);
    DigiKeyboard.delay(50);
    DigiKeyboard.sendKeyPress(0, 0);
  }
}





void loop() {
  // put your main code here, to run repeatedly:
  // this is generally not necessary but with some older systems it seems to
  // prevent missing the first character after a delay:
  uint32_t present = millis();
  if (softSerial.available()){
    // #
    // S(trike) (press+release)
    // KK key as hexascii
    // MM modifier as hexascii
    // enter or something

    // #
    // e(cho)
    // C(haracter to be echoed)
    // enter or something
    
    byte c = softSerial.read();
    if ('#' == c){
      incoming[0] = 0;
    }
    int lengo = strlen(incoming);
    if (lengo < sizeof(incoming)-2){
      incoming[lengo++] = c;
      incoming[lengo] = 0;
    }
    if ((13 == c)||(10 == c)){
      digestIncoming();
      incoming[0] = 0;
    }
  }

}

And the Nano checking the physical buttons.

#include <SoftwareSerial.h>
#define PIN_MYSERIAL_RX 2
#define PIN_MYSERIAL_TX A0

SoftwareSerial mySerial(PIN_MYSERIAL_RX, PIN_MYSERIAL_TX);

#define MOD_CONTROL_LEFT    (1<<0)
#define MOD_SHIFT_LEFT      (1<<1)
#define MOD_ALT_LEFT        (1<<2)
#define MOD_GUI_LEFT        (1<<3)
#define MOD_CONTROL_RIGHT   (1<<4)
#define MOD_SHIFT_RIGHT     (1<<5)
#define MOD_ALT_RIGHT       (1<<6)
#define MOD_GUI_RIGHT       (1<<7)

#define KEY_1       30
#define KEY_2       31
#define KEY_3       32
#define KEY_4       33
#define KEY_5       34
#define KEY_6       35
#define KEY_7       36
#define KEY_8       37
#define KEY_9       38
#define KEY_0       39

#define KEY_F1      58
#define KEY_F2      59
#define KEY_F3      60
#define KEY_F4      61
#define KEY_F5      62
#define KEY_F6      63
#define KEY_F7      64
#define KEY_F8      65
#define KEY_F9      66
#define KEY_F10     67
#define KEY_F11     68
#define KEY_F12     69

void setup(){
  Serial.begin(9600);
  mySerial.begin(9600);
  pinMode(A1, INPUT_PULLUP);
  pinMode(A2, INPUT_PULLUP);
  pinMode(A3, INPUT_PULLUP);
  //pullup soldered in
  pinMode(A6, INPUT);
  pinMode(A7, INPUT);
}

uint32_t lastReceivedButtonAtMillis;
int lastReceivedButton;

void receiveButton(int b){
  if ((millis() - lastReceivedButtonAtMillis > 1000) || (b!=lastReceivedButton)){
     lastReceivedButton = b;
     char msg[16];
     byte key = KEY_F12 - (b - 1);     
     strcpy(msg, "#s");
     if (key < 16){
      strcat(msg, "0");
     }
     itoa(key, msg+strlen(msg), 16);
     byte modifier=MOD_CONTROL_LEFT | MOD_SHIFT_LEFT | MOD_ALT_LEFT;
     if (modifier < 16){
      strcat(msg, "0");
     }
     itoa(modifier, msg+strlen(msg), 16);
     mySerial.println(msg);
     Serial.println(msg);
     lastReceivedButtonAtMillis = millis();
  }
}



void loop(){
  while (Serial.available()){
    char c[2];
    c[0] = Serial.read();
    c[1] = 0;
    //echo
    if ((c[0] != 13)&&(c[0] != 10)){
      mySerial.print("#e");
      mySerial.println(c);
    }
  }
  if (LOW == digitalRead(A1)){
    receiveButton(1);
  };
  if (LOW == digitalRead(A2)){
    receiveButton(2);
  };
  if (LOW == digitalRead(A3)){
    receiveButton(3);
  };
  if (analogRead(A6) < 64){
    receiveButton(4);
  }
  if (analogRead(A7) < 64){
    receiveButton(5);
  }
}
Facebooktwitterredditpinterestlinkedinmail