2
\$\begingroup\$

I'm trying to write a piece of code that will mimic ASCII keyboard output. It uses the keyboard library from pjrc, but the application I'm working on requires outputting string sequences rather than individual keys (the C++ interface exposes Keyboard.print, which does exactly what I want, except that I'm trying to stick to C for learning purposes).

Here's a kick at it;

[snip]
#include <string.h>
#include "usb_keyboard.h" // definitions for the /KEY_\w+/ symbols

int key_from_char(char c){
  if (c >= 'a' && c <= 'z') return c + KEY_A - 'a';
  if (c >= 'A' && c <= 'Z') return c + KEY_A - 'A';
  if (c >= '1' && c <= '9') return c + KEY_A - '1';
  if (c == '0') return 39; // 0 is low in ASCII, high in the USB key definition

  // there's no easy mapping between the other ASCII characters and 
  // USB keyboard keys, so these are all manually represented
  switch (c) {
  case ' ': return KEY_SPACE; break;
  case '!': return KEY_1; break;
  case '"': return KEY_QUOTE; break;
  case '#': return KEY_NUMBER; break;
  case '$': return KEY_4; break;
  case '%': return KEY_5; break;
  case '&': return KEY_7; break;
  case '\'': return KEY_QUOTE; break;
  case '(': return KEY_9; break;
  case ')': return KEY_0; break;
  case '*': return KEYPAD_ASTERIX; break;
  case '+': return KEYPAD_PLUS; break;
  case ',': return KEY_COMMA; break;
  case '-': return KEYPAD_MINUS; break;
  case '.': return KEY_PERIOD; break;
  case '/': return KEYPAD_SLASH; break;

  case ':': return KEY_SEMICOLON; break;
  case ';': return KEY_SEMICOLON; break;
  case '<': return KEY_COMMA; break;
  case '=': return KEY_EQUAL; break;
  case '>': return KEY_PERIOD; break;
  case '?': return KEY_SLASH; break;
  case '@': return KEY_2; break;

  case '[': return KEY_LEFT_BRACE; break;
  case '\\': return KEY_BACKSLASH; break;
  case ']': return KEY_RIGHT_BRACE; break;
  case '^': return KEY_6; break;
  case '_': return KEY_MINUS; break;
  case '`': return KEY_TILDE; break;

  case '{': return KEY_LEFT_BRACE; break;
  case '|': return KEY_BACKSLASH; break;
  case '}': return KEY_RIGHT_BRACE; break;
  case '~': return KEY_TILDE; break;

  default: return 0;
  }
}

int modifier_from_char(char c){
  if ((c >= 'A' && c <= 'Z') ||
      // not sure how useful it is to put chars instead of ints for the rest of these
      c == '!' || c == '"' ||
      (c >= '$' && c <= '&') ||
      c == '(' || c == ')' ||
      c == ':' || c == '<' ||
      (c >= '>' && c <= '@') ||
      c == '^' || c == '_' ||
      (c >= '{' && c <= '~')) return KEY_SHIFT;

  return 0;
}

int8_t usb_keyboard_print(char *s){
  int s_len = strlen(s);
  int i;

  for(i = 0; i < s_len; i++){
    usb_keyboard_press(key_from_char(s[i]), modifier_from_char(s[i]));
  }
}

It seems to work, when I do

int main(void) {
   ...
   usb_keyboard_print("abcdABCD1234!@#$%^&*()_+|~{}:\">?<-=\\`[];',./");
   ...
}

the character sequence

abcdABCD1234!@\$%^&*()_+|~{}:">?<-=\`[];',./

is output as if by keyboard.

All comments welcome, but I also have specific points/questions

  • It seems like there should be an easier way to do this. I mention above that the C++ version of the usb_keyboard library provides a Keyboard.print which does essentially the same thing, but I haven't found anything similar for plain C.
  • Are there any issues I'm missing about USB key codes that would make this code wonk out based on nationality settings? How would you go about internationalizing it?
  • This is basically the first C code I've written since getting out of high-school, so please point out style errors and inefficiencies with extreme prejudice.
\$\endgroup\$
1
\$\begingroup\$

Don't subtract absolute values:

    if (c >= 'a' && c <= 'z') return c - 93;

It makes it hard to understand what you are doing when you subtract integer literals from character codes. I would normally expect some form of value that conveys some meaning. For example lets assume ASCII (You probably want your keyboard thing but I don't know the appropriate macro).

 if (c >= 'a' && c <= 'z') return c + 3 - 'a';  // For some reason you want 'a' to map to 3
                                                // If we assume ASCII 'a' => 96 


 if (c >= 'A' && c <= 'Z') return c + 4 - 'A';  // For some reason you want 'A' to map to 4
                                                // If we assume ASCII 'A' => 65

Either you have some other requirement that is not ASCII (very possible), you want 'a'/'A' to map to some non zero number or you have a bug. But it is hard to tell with the way you have written your code as the literal values 93 and 61 have no real meaning.

\$\endgroup\$
  • \$\begingroup\$ Lowercase a is 97, not 96 in ASCII. The values being returned are part of the header file included with the Teensy C code(zip file). You more or less got it, that header has the "A" key mapped to 4 (which I assume is some sort of keyboard standard; I don't know enough to dispute it). While ASCII has a different code for "a" than "A", the USB keyboard protocol seems to use the same code for both, but expects a modifier key for the uppercase. They're represented as #defines, so I could do return c + KEY_A - 'a'. \$\endgroup\$ – Inaimathi Dec 13 '11 at 20:48
1
\$\begingroup\$

You could take a bit of a different approach if you wanted to get rid of the switch ( as they get a bit inefficient with lots of conditions)

you can make an array of 256 ints, and use the current function to populate the array, then to get the int for the char.

int key_from_char(char c){
    return my_char_mapping_array[(unsigned int)c];
}
\$\endgroup\$
  • \$\begingroup\$ I agree with the usage of the array (that's a good idea). But I not sure I agree with the statement that switch statements get inefficient. I find they are implemented as a jump table. \$\endgroup\$ – Martin York Dec 14 '11 at 3:38
  • \$\begingroup\$ +1: the solution for this should really be data-driven rather than logic-driven \$\endgroup\$ – Paul R Dec 14 '11 at 8:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.