/*
 *  pk -- pseudo keyboard via ``kbdsim.o''
 *
 *	0.0: Feb. 12, 2004 by Dai ISHIJIMA (alphabet)
 *	0.1: Feb. 14, 2004
 *
 *  usage:
 *	pk string
 *
 *  see also
 *	kbdsim.c
 *
 *  how to install kbdsim.o
 *	# mknod /dev/kbdsim c 61 0
 *	# insmod -f kbdsim.o
 */

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define YES 1
#define NO  0
#define EOS '\0'

#define DEVKBDSIM "/dev/kbdsim"
#define KEY_PRESS   1
#define KEY_RELEASE 0

#define ASCII	'a'
#define HEX	'X'
#define DIGIT	'D'

#define shift --argc; ++argv
char *prog;


/*
 * keycode definitions
 *	see also "kernel/linux24/drivers/char/corgi_keymap.map"
 *	but something wrong...
 */

/* ALPHABETS (1..26) */
enum {
    KEY_A = 1,	KEY_B,	KEY_C,	KEY_D,	/*  1.. 4 */
    KEY_E,	KEY_F,	KEY_G,	KEY_H,	/*  5.. 8 */
    KEY_I,	KEY_J,	KEY_K,	KEY_L,	/*  9..12 */
    KEY_M,	KEY_N,	KEY_O,	KEY_P,	/* 13..16 */
    KEY_Q,	KEY_R,	KEY_S,	KEY_T,	/* 17..20 */
    KEY_U,	KEY_V,	KEY_W,	KEY_X,	/* 21..24 */
    KEY_Y,	KEY_Z};			/* 25, 26 */

/* MODIFIERS, FUNCTIONS */
enum {
    KEY_SHIFT = 27,	KEY_RET,	KEY_MENU,	KEY_ALT,   /* 27- */
    KEY_BS,		KEY_KIGOU,	KEY_LCDOFF,	KEY_CANCEL,/* 31- */
    KEY_LEFT,		KEY_UP,		KEY_DOWN,	KEY_RIGHT, /* 35- */
/*  ESC [ D		ESC [ A		ESC [ B		ESC [ C		  */
    KEY_OK,		KEY_HOME};				   /* 39- */


/* NUMBERS */
enum {
    KEY_ONE = 41,	KEY_TWO,	KEY_THREE,	KEY_FOUR,  /* 41- */
    KEY_FIVE,		KEY_SIX,	KEY_SEVEN,	KEY_EIGHT, /* 45- */
    KEY_NINE, KEY_ZERO};					   /* 49- */

/* SYMBOLS */
enum {
    KEY_USCORE = 51,	KEY_DOT,	KEY_EQUAL,	KEY_YEN,   /* 51- */
    KEY_SEMI,		KEY_COLON,	KEY_FN_I};		   /* 55- */
					/* 」*/

/* SYMBOLS */
enum {
    KEY_MINUS = 58,	KEY_ASTER,	KEY_LOCK,	KEY_UNDEF, /* 58- */
    KEY_BAR,		KEY_COMMA,	KEY_PERIOD,	KEY_TAB,   /* 62- */
    KEY_X_2,		KEY_C_2,	KEY_V_2,	KEY_PLUS,  /* 66- */
    KEY_LBRACKET,	KEY_LARGE,	KEY_CIRCUM,	KEY_RBRACKET,/* 70- */
    KEY_SMALL,		KEY_FN_U,	KEY_DARK,	KEY_BRITE, /* 74- */
			/* 「 */    
    KEY_UNDEF6,		KEY_UNDEF7,	KEY_UNDEF8,	KEY_UNDEF9,/* 78- */
    KEY_Z_2,		KEY_UNDEFA,	KEY_UNDEFB,	KEY_AT,    /* 82- */
    KEY_LESS,		KEY_GRATER,	KEY_F9,		KEY_SLASH, /* 86- */
                                      /* ESC [ 20 ~ */
    KEY_MAIL,		KEY_SPACE,	KEY_SPACE_2,	KEY_UNDEFC,/* 90- */
    KEY_UNDEFD,		KEY_KANJI};				   /* 94- */

/* FUNCTIONS */
enum {
    KEY_MUSIC = 96,	KEY_MUSIC2,	KEY_MUSIC3,	KEY_MUSIC4,/* 96- */
    KEY_MUSIC5,		KEY_MUSIC6,	KEY_UNDEFE};		   /* 100- */

/* MODIFIERS, CONTROLS */
enum {
    KEY_SHIFT_2 = 103,	KEY_CTRL,	KEY_CTRL_HAT,	KEY_MENUX,  /* 103- */
    KEY_CTRL_YEN,	KEY_LBRACE,	KEY_OFF,	KEY_MAIL2,  /* 107- */
    KEY_RBRACE,		KEY_KIGOU2,	KEY_KRIGHT,	KEY_KLEFT,  /* 111- */
    KEY_PRTSC,		KEY_CTRL_US,	KEY_SYNC,	KEY_YEN2,   /* 115- */
    KEY_UNDEFI,		KEY_LARGE2,	KEY_KANAHIRA,	KEY_KANJI2, /* 119- */
    KEY_OK2,	   	KEY_CANCEL2,	KEY_KUP,	KEY_KDOWN,  /* 123- */
    KEY_UNDEFK};						    /* 127- */


#define CTRL(ch) ((unsigned char)ch & 0x1f)


/* ASCII -> KEYCODE */
int char2key(int ch, unsigned char *keybuf)
{
    if (ch == 0) {
	keybuf[0] = KEY_CTRL;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_SPACE;	keybuf[3] = KEY_PRESS;
	keybuf[4] = KEY_SPACE;	keybuf[5] = KEY_RELEASE;
	keybuf[6] = KEY_CTRL;	keybuf[7] = KEY_RELEASE;
	return(4);
    }
    else if (ch == '\010') {	/* BS */
	keybuf[0] = KEY_BS;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_BS;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '\r') {
	keybuf[0] = KEY_RET;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_RET;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '\033') {
	keybuf[0] = KEY_CANCEL;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_CANCEL;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch <= CTRL('z')) {
	keybuf[0] = KEY_CTRL;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_A + ch - CTRL('a');
	keybuf[3] = KEY_PRESS;
	keybuf[4] = keybuf[2];	keybuf[5] = KEY_RELEASE;
	keybuf[6] = KEY_CTRL;	keybuf[7] = KEY_RELEASE;
	return(4);
    }
    else if (ch == ' ') {
	keybuf[0] = KEY_SPACE;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_SPACE;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (('!' <= ch) && (ch <= ')')) {
	keybuf[0] = KEY_SHIFT;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_ONE + ch - '!';
	keybuf[3] = KEY_PRESS;
	keybuf[4] = keybuf[2];	keybuf[5] = KEY_RELEASE;
	keybuf[6] = KEY_SHIFT;	keybuf[7] = KEY_RELEASE;
	return(4);
    }
    else if (ch == '*') {
	keybuf[0] = KEY_ASTER;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_ASTER;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '+') {
	keybuf[0] = KEY_PLUS;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_PLUS;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == ',') {
	keybuf[0] = KEY_COMMA;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_COMMA;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '-') {
	keybuf[0] = KEY_MINUS;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_MINUS;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '.') {
	keybuf[0] = KEY_PERIOD;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_PERIOD;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '/') {
	keybuf[0] = KEY_SLASH;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_SLASH;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (('1' <= ch) && (ch <= '9')) {
	keybuf[0] = KEY_ONE + ch - '1';
	keybuf[1] = KEY_PRESS;
	keybuf[2] = keybuf[0];	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '0') {
	keybuf[0] = KEY_ZERO;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_ZERO;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == ':') {
	keybuf[0] = KEY_COLON;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_COLON;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == ';') {
	keybuf[0] = KEY_SEMI;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_SEMI;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '<') {
	keybuf[0] = KEY_LESS;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_LESS;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '=') {
	keybuf[0] = KEY_EQUAL;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_EQUAL;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '>') {
	keybuf[0] = KEY_GRATER;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_GRATER;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '?') {
	keybuf[0] = KEY_SHIFT;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_PERIOD;	keybuf[3] = KEY_PRESS;
	keybuf[4] = KEY_PERIOD;	keybuf[5] = KEY_RELEASE;
	keybuf[6] = KEY_SHIFT;	keybuf[7] = KEY_RELEASE;
	return(4);
    }
    else if (ch == '@') {
	keybuf[0] = KEY_AT;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_AT;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (('A' <= ch) && (ch <= 'Z')) {
	keybuf[0] = KEY_SHIFT;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_A + ch - 'A';
	keybuf[3] = KEY_PRESS;
	keybuf[4] = keybuf[2];	keybuf[5] = KEY_RELEASE;
	keybuf[6] = KEY_SHIFT;	keybuf[7] = KEY_RELEASE;
	return(4);
    }
    else if (ch == '[') {
	keybuf[0] = KEY_LBRACKET;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_LBRACKET;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '\134') {
	keybuf[0] = KEY_YEN;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_YEN;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == ']') {
	keybuf[0] = KEY_RBRACKET;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_RBRACKET;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '^') {
	keybuf[0] = KEY_CIRCUM;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_CIRCUM;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '_') {
	keybuf[0] = KEY_USCORE;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_USCORE;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '`') {
	keybuf[0] = KEY_ALT;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_SHIFT;	keybuf[3] = KEY_PRESS;
	keybuf[4] = KEY_MINUS;	keybuf[5] = KEY_PRESS;
	keybuf[6] = KEY_MINUS;	keybuf[7] = KEY_RELEASE;
	keybuf[8] = KEY_SHIFT;	keybuf[9] = KEY_PRESS;
	keybuf[10] = KEY_ALT;	keybuf[11] = KEY_RELEASE;
	return(6);
    }
    else if (('a' <= ch) && (ch <= 'z')) {
	keybuf[0] = KEY_A + ch - 'a';
	keybuf[1] = KEY_PRESS;
	keybuf[2] = keybuf[0];	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '{') {
	keybuf[0] = KEY_LBRACE;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_LBRACE;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '|') {
	keybuf[0] = KEY_BAR;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_BAR;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '}') {
	keybuf[0] = KEY_RBRACE;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_RBRACE;	keybuf[3] = KEY_RELEASE;
	return(2);
    }
    else if (ch == '~') {
	keybuf[0] = KEY_SHIFT;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_ZERO;	keybuf[3] = KEY_PRESS;
	keybuf[4] = KEY_ZERO;	keybuf[5] = KEY_RELEASE;
	keybuf[6] = KEY_SHIFT;	keybuf[7] = KEY_RELEASE;
	return(4);
    }
    else if (ch == '\177') {
	keybuf[0] = KEY_ALT;	keybuf[1] = KEY_PRESS;
	keybuf[2] = KEY_BS;	keybuf[3] = KEY_PRESS;
	keybuf[4] = KEY_BS;	keybuf[5] = KEY_RELEASE;
	keybuf[6] = KEY_ALT;	keybuf[7] = KEY_RELEASE;
	return(4);
    }
    return(0);
}


int hexdigit(char ch)
{
    /* ch に応じて 0〜15までの値を返す */
    if (('0' <= ch) && (ch <= '9')) {
	return(ch - '0');
    }
    else if (('A' <= ch) && (ch <= 'F')) {
	return(ch - 'A' + 10);
    }
    else if (('a' <= ch) && (ch <= 'f')) {
	return(ch - 'a' + 10);
    }
    return(-1);		/* 16進文字じゃないときは -1 を返す */
}


/* 16進文字列をバイナリ化する。「41 54 5a 0d 0a」→「ATZ\r\n」 */
int hexconv(char *to, char *from)
{
    int m, n;
    int x;

    m = n = 0;
    while (from[m] != EOS) {
	while ((from[m] != EOS) && ((x = hexdigit(from[m])) < 0)) {
	    ++m;
	}
	if ((from[m] != EOS) && ((x = hexdigit(from[m])) >= 0)) {
	    to[n] = x;
	    ++m;
	}
	if ((from[m] != EOS) && ((x = hexdigit(from[m])) >= 0)) {
	    to[n] = to[n] << 4 | x;
	    ++m;
	}
	++n;
	if (from[m] == EOS) {
	    break;
	}
    }
    for (m = 0; m < n; m++) {
	from[m] = to[m];
    }
    from[m] = EOS;
    return(m);
}


void sendkey(int fd, unsigned char *s, int verbose)
{
    if (verbose) {
	fprintf(stderr, "%s: code=%d(%x), ", prog, s[0], s[0]);
	if (s[1] == KEY_PRESS) {
	    fprintf(stderr, "PRESS\n");
	}
	else if (s[1] == KEY_RELEASE) {
	    fprintf(stderr, "RELEASE\n");
	}
	else {
	    fprintf(stderr, "UNKNOWN(%d)\n", s[1]);
	}
    }
    write(fd, s, 2);
}


void digitcode(int fd, char sign, unsigned char ch, int verbose)
{
    unsigned char keybuf[2];

    if (sign != '-') {
	keybuf[0] = ch; keybuf[1] = KEY_PRESS;
	sendkey(fd, keybuf, verbose);
    }
    if (sign != '+') {
	keybuf[0] = ch; keybuf[1] = KEY_RELEASE;
	sendkey(fd, keybuf, verbose);
    }
}

void hexcode(int fd, unsigned char *s, int verbose)
{
    unsigned char codebuf[BUFSIZ];
    unsigned char keybuf[2];
    int n;
    int i;
    
    if (verbose) {
	fprintf(stderr, "%s: sending \"%s\"\n", prog, s);
    }
    n = hexconv(codebuf, s);
    for (i = 0; i < n; i++) {
	keybuf[0] = codebuf[i]; keybuf[1] = KEY_PRESS;
	sendkey(fd, keybuf, verbose);
	keybuf[0] = codebuf[i]; keybuf[1] = KEY_RELEASE;
	sendkey(fd, keybuf, verbose);
    }
}


void pseudo_input(int fd, int ch, int verbose)
{
    unsigned char keybuf[BUFSIZ];
    int n;
    int i;

    n = char2key(ch, keybuf);
    for (i = 0; i < n; i++) {
	sendkey(fd, &keybuf[2 * i], verbose);
    }
}


int parse(char *to, char *from)
{
    int i;
    int x;

    i = 0;
    while (*from != EOS) {
	if (*from == '\\') {
	    ++from;
	    if (*from == EOS) {
		break;
	    }
	    else if (*from == 'r') {
		to[i] = '\r';
	    }
	    else if (*from == 'n') {
		to[i] = '\n';
	    }
	    else if (*from == 't') {
		to[i] = '\t';
	    }
	    else if (*from == '^') {
		++from;
		if (*from == EOS) {
		    break;
		}
		else if (*from == '?') {
		    to[i] = 0x7f;
		}
		else if (*from == '^') {
		    to[i] = '^';
		}
		else {
		    to[i] = CTRL(*from);
		}
	    }
	    else if (('0'<= *from) && (*from <= '7')) {
		while (('0'<= *from) && (*from <= '7')) {
		    to[i] = to[i] * 8 + *from - '0';
		    ++from;
		}
		--from;
	    }
	    else if (*from == 'x') {
		++from;
		if ((*from != EOS) && ((x = hexdigit(*from)) >= 0)) {
		    to[i] = x;
		    ++from;
		}
		if ((*from != EOS) && ((x = hexdigit(*from)) >= 0)) {
		    to[i] = to[i] << 4 | x;
		    ++from;
		}
		--from;
	    }
	    else {
		to[i] = *from;
	    }
	}
	else {
	    to[i] = *from;
	}
	++from;
	++i;
    }
    to[i] = EOS;
    return(i);
}


void usage()
{
    fprintf(stderr, "Usage: %s string\n", prog);
}


int main(int argc, char *argv[])
{
    int fd;
    int mode;
    int verbose;
    int delay;
    unsigned char string[BUFSIZ];
    int i, n;

    prog = *argv;
    shift;
    delay = 0;
    mode = ASCII;
    verbose = NO;
    while ((argc > 0) && (argv[0][0] == '-')) {
	if (('0' <= argv[0][1]) && (argv[0][1] <= '9')) {
	    delay = -atoi(*argv);
	}
	else if (argv[0][1] == 'e') {
	    shift;
	    break;
	}
	else if (argv[0][1] == 'v') {
	    verbose = !verbose;
	}
	else if (argv[0][1] == 'x') {
	    mode = HEX;
	}
	else if (argv[0][1] == 'd') {
	    mode = DIGIT;
	    shift;
	    break;
	}
	else {
	    usage();
	}
	shift;
    }
    if (delay > 0) {
	sleep(delay);
    }
    if ((fd = open(DEVKBDSIM, O_WRONLY)) <= 0) {
	fprintf(stderr, "can't open %s\n", DEVKBDSIM);
	exit(1);
    }
    while (argc > 0) {
	if (mode == HEX) {
	    hexcode(fd, *argv, verbose);
	}
	else if (mode == DIGIT) {
	    digitcode(fd, **argv, abs(atoi(*argv)), verbose);
	}
	else {
	    n = parse(string, *argv);
	    for (i = 0; i < n; i++) {
		pseudo_input(fd, string[i], verbose);
	    }
	}
	shift;
    }
    close(fd);
    exit(0);
}
