2007年2月2日星期五

Linux键盘驱动详解

Linux键盘驱动详解


从什么开始说比较合理呀?就从硬件开始把:严格来说称不上什么键盘体系,但由于键盘的driver code比较的涩晦,所以就称之为键盘体系了。

后注:什么叫后注?也就是写完后想说点什么的意思呀!这篇文挡太长了(本来想写的更长,真的,还有一些文件都没有写上去呀),大家还是用“文挡结构图”来看把,厉害把,这么多,全部手写呀。
硬件相关
硬件,其实有一些内容,但我实在不想一段段的翻译,大家想要的话,我把english文挡发给大家好了。
Keyboard Key




键盘代码

键盘模式
键盘模式有4种, 在Linux 下你可以用kbd_mode -参数 来设置和显示你的模式:
1) Scancode mode (raw )raw模式:将键盘端口上读出的扫描码放入缓冲区
2) Keycode mode (mediumraw) mediumraw模式:将扫描码过滤为键盘码放入缓冲区
3) ASCII mode (XLATE ) XLATE模式:识别各种键盘码的组合,转换为TTY终端代码放入缓冲区
4) UTF-8 MODE (UNICODE) Unicode 模式:UNICODE模式基本上与XLATE相同,只不过可以通过数字小键盘间接输入UNICODE代码。

键盘模块的上层漫游:
键盘模块的最上层严格的说应该是TTY设备,这在以后描述。对于使用者而言,keyboard.c是我们的最上层。
在keyboard.c中,不涉及底层操作,也不涉及到任何体系结构,他主要负责:键盘初始化、键盘tasklet的挂入、按键盘后的处理、keymap的装入、scancode的转化、与TTY设备的通信。
Keyboard.c是一个大杂烩,包含了大多数keyboard的key的处理,因此代码才变的庞大和复杂,我们可以修改它,让他变的很小(但这样做并不会使空间节省,因为keyboard.c的许多代码是动态加载的)。
Keyboard.c中的int __init kbd_init(void) 函数是键盘代码执行的开始点,但keyboard.c并非是一定 执行的,你必须先定义CONFIG_VT(Character devices ->nbsp;Virtual terminal)才有效,为什 么?因为kbd_init()是在tty_io.c 的 tty_init()中被调用的。
★Kbd_init()在进行一些基本初始化后,执行kbd_init_hw(),此函数可以看做是一个统一的上层界面,对于不同的体系或 相同体系下不同的board,他们的kbd_init_hw()的实现代码是不同的(同过CONFIG_XXX_XXX来实现),他的实现代码就是操作硬 件。 kbd_init_hw()会:检查硬件及有效性、分配资源和中断、操作寄存器的实现函数调用。
初始化一完成,就把keyboard_tasklet放到CPU tasklet中(注意到keyboard_tasklet 是开放出来的,等下可以看到怎么使用它)。
现在就等你按键了,当有按键时,会调用键盘中断处理函数,一般都是体系下的keyboard_interrupt(),此函数会调用到keyboard.c中的handle_scancode() 。
★handle_scancode()就是我们的重点,这个函数最终决定了我们按下的key如何处理,在下面会分析他的流程,他主要做:与TTY的通信、keymap的装入、按键的处理。
handle_scancode()处理的结果就是把你按下的key发到不同的处理函数中,一般的,这些函数离最终的结果已经很近了。这其中,大多数的函数都会调用put_queue() 函数。
★put_queue()的工作原理就是利用TTY, 它将经过转换的键盘功能码用tty_insert_flip_char()放入到当前 TTY的flip buffer中,然后将缓冲区输出任务函数(flush_to_ldisc)添加到控制台任务队列(con_task_queue)并 激活控制台软中断执行该任务函数. flush_to_ldisc()翻转读写缓冲区,将缓冲区接收数据传递给tty终端规程的 n_tty_receive_buf()接收函数,n_tty_receive_buf()处理输入字符,将输出字符缓冲在终端的循环缓冲区 (read_buf)之中.用户通过tty规程的read_chan()读取read_buf中的字符.当多个进程同时读取同一终端时, 使用tty- >tomic_read信号灯来竞争读取权.


Kbd_init()开始点:
int __init kbd_init(void)
{
int i;
struct kbd_struct kbd0; /* 定义一个kbd_struct, 他用于保存当前键盘LED灯状态、缺省keymap表、键盘复合锁定状态、一些功能灯的定义、键盘模式定义、及modeflags模式*/

extern struct tty_driver console_driver; /* console_driver这个全局变量是很重要的,他维护着庞大的TTY/Console对象,承当TTY对外的输入和输出 */

kbd0.ledflagstate = kbd0.default_ledflagstate = KBD_DEFLEDS; /* 缺省不亮灯 */
kbd0.ledmode = LED_SHOW_FLAGS; /* 缺省,用于显示flags */
kbd0.lockstate = KBD_DEFLOCK; /* 缺省,表示用key_map的第一个表,即没有lock键*/
kbd0.slockstate = 0; /* 没有粘键 */
kbd0.modeflags = KBD_DEFMODE; /* modeflags=0 */
kbd0.kbdmode = VC_XLATE; /* ASCII模式 */

for (i = 0 ; i
kbd_table[i] = kbd0;

ttytab = console_driver.table; /* ttytab是一个很重要的指针,他维护着当前各个控制台的tty_struct 表(即相当于一个2维表),tty_struct可看成/dev/tty*的输入设备,只有在/dev/tty*打开时它才接收输入数据*/

/* 以下就是涉及到硬件的操作,由于我们没有KBD硬件,因此我们可能希望屏蔽所产生的error信息*/
#ifdef CONFIG_SA1100_ROSETTA
button_setup(); /* initialize button hardware */
#else
kbd_init_hw(); /*下面会分析他*/
#endif

/* 我们希望把keyboard_tasklet 挂到CPU的运行队列中去,下面就是 */
tasklet_enable(&eyboard_tasklet);
tasklet_schedule(&eyboard_tasklet);

/* 下面register一个电源管理的KBD设备?操作函数为NULL? */
pm_kbd = pm_register(PM_SYS_DEV, PM_SYS_KBC, NULL);

return 0;
}


kbd_init_hw()函数:
我们已经说了,kbd_init_hw() 相当与一个统一的界面,不同硬件操作代码是不同的,因为是涉及硬件的操作。
对arm体系而言,目前的kbd_init_hw() 提供了4种硬件代码,象如果是ADS板,就要提供ADS的相应代码。
SA1100体系的此函数放在文件include/asm-arm/arch-sa1100/keyboard.h 中。大家可以看到在这个 文件中除了kbd_init_hw() 外,还有kbd_setkeycode() / kbd_getkeycode() / kbd_translate()/ ……等函数都是函数名相同,处理代码不同。而这些函数都会在keyboard.c中被调用到。
SA1100定义了 “CONFIG_SA1100_BRUTUS / CONFIG_SA1100_GRAPHICSCLIENT/ CONFIG_SA1111 / other board” 4种已知硬件的操作代码,对于Assabet就会调用other board的代码,也就是无论什么函数都为NULL(因为没有这个硬件呀!), CONFIG_SA1100_GRAPHICSCLIENT也就是ADS板的处理代码(SmartIO芯片决定的),CONFIG_SA1111也有自己 的一块KBD芯片,而且功能居然和PC的差不多。

★那现在我们怎么办呀(我们的板不知道有没有KBD模块芯片)?我们来挑一个最复杂的来分析把。那就是SA1111了。
我们从kbd_init_hw() 进入把:
void __init sa1111_init_hw(void)
{
kbd_request_region(); /* 分配kbd端口,其实根本就不用分配了,对PC来说,0x60~0x68就是KBD的IO口,对sa1100更不用分配了,所以此函数为NULL*/

SKPCR |= (1
/* 以下初始化KBD的4个register,以便开始下面的test工作 */
KBDCLKDIV = 0;
KBDPRECNT = 127;
KBDCR = 0x08;
mdelay(50);
KBDDATA = 0xff;
mdelay(50);

/* Flush any pending input. */
kbd_clear_input();

if (kbd_startup_reset) { /* int kbd_startup_reset =1,所以肯定执行 */
char *msg = initialize_kbd(); /* initialize_kbd() 真正开始init 硬件,出错会return错误message,显示在系统启动的时候 */
if (msg)
printk(KERN_WARNING "nitialize_kbd: %s\n" msg);
}

#if defined CONFIG_PSMOUSE
psaux_init();
#endif

/* allocate the IRQ, keyboard_interrupt 是处理函数 */
kbd_request_irq(keyboard_interrupt);

#if 0 /*@@@*/
for (;;) {
if (KBDSTAT KBD_STAT_RXF)
printk("_%.2x " KBDDATA 0xff);
if (MSESTAT MSE_STAT_RXF)
printk("_%.2x " MSEDATA 0xff);
}
#endif

#if 0 /*@@@*/
timer_table[COMTROL_TIMER].fn = pckbd_timer;
timer_table[COMTROL_TIMER].expires = jiffies + 2*HZ/100;
timer_active |= 1
#endif

}

initialize_kbd()函数:
这个函数如果逐层展开,会比较庞大,我们就分析最上层的把。
首先,要reset键盘,即对键盘的 KBDDATA寄存器写KBD_CMD_RESET数据,然后读状态位,假如超时,那么可能是没有AT 键盘挂上。这里有一个很重要的问题就是你在对register读的时候,先收到KBD_REPLY_ACK数据标志,然后再受到数据,因此你需要读2次。
另,这些硬件代码实在是太多了,我想,如果大家有什么不懂来问我把,我吃不削写了。这里涉及到很多的 键盘命令/键盘标志,你可以参考pc_keyb.h文件,它包括了特殊的键盘命令操作,你可能需要对键盘的原理有所了解才看的懂。

static char * __init initialize_kbd(void)
{
int status;

#if 0 /*@@@*/
/*
* Test the keyboard interface.
* This seems to be the only way to get it going.
* If the test is successful a x55 is placed in the input buffer.
*/
kbd_write_command_w(KBD_CCMD_SELF_TEST);
if (kbd_wait_for_input() != 0x55)
return "eyboard failed self test"

/*
* Perform a keyboard interface test. This causes the controller
* to test the keyboard clock and data lines. The results of the
* test are placed in the input buffer.
*/
kbd_write_command_w(KBD_CCMD_KBD_TEST);
if (kbd_wait_for_input() != 0x00)
return "eyboard interface failed self test"

/*
* Enable the keyboard by allowing the keyboard clock to run.
*/
kbd_write_command_w(KBD_CCMD_KBD_ENABLE);
#endif

/*
* Reset keyboard. If the read times out
* then the assumption is that no keyboard is
* plugged into the machine.
* This defaults the keyboard to scan-code set 2.
*
* Set up to try again if the keyboard asks for RESEND.
*/
do {
kbd_write_output_w(KBD_CMD_RESET);
status = kbd_wait_for_input();
if (status == KBD_REPLY_ACK)
break;
if (status != KBD_REPLY_RESEND)
return "eyboard reset failed, no ACK"
} while (1);

if (kbd_wait_for_input() != KBD_REPLY_POR)
return "eyboard reset failed, no POR"

/*
* Set keyboard controller mode. During this, the keyboard should be
* in the disabled state.
*
* Set up to try again if the keyboard asks for RESEND.
*/
do {
kbd_write_output_w(KBD_CMD_DISABLE);
status = kbd_wait_for_input();
if (status == KBD_REPLY_ACK)
break;
if (status != KBD_REPLY_RESEND)
return "isable keyboard: no ACK"
} while (1);

#if 0 /*@@@*/
kbd_write_command_w(KBD_CCMD_WRITE_MODE);
kbd_write_output_w(KBD_MODE_KBD_INT
| KBD_MODE_SYS
| KBD_MODE_DISABLE_MOUSE
| KBD_MODE_KCC);

/* ibm powerpc portables need this to use scan-code set 1 -- Cort */
kbd_write_command_w(KBD_CCMD_READ_MODE);
if (!(kbd_wait_for_input() KBD_MODE_KCC)) {
/*
* If the controller does not support conversion,
* Set the keyboard to scan-code set 1.
*/
kbd_write_output_w(0xF0);
kbd_wait_for_input();
kbd_write_output_w(0x01);
kbd_wait_for_input();
}
#else
kbd_write_output_w(0xf0);
kbd_wait_for_input();
kbd_write_output_w(0x01);
kbd_wait_for_input();
#endif


kbd_write_output_w(KBD_CMD_ENABLE);
if (kbd_wait_for_input() != KBD_REPLY_ACK)
return "nable keyboard: no ACK"

/*
* Finally, set the typematic rate to maximum.
*/
kbd_write_output_w(KBD_CMD_SET_RATE);
if (kbd_wait_for_input() != KBD_REPLY_ACK)
return "et rate: no ACK"
kbd_write_output_w(0x00);
if (kbd_wait_for_input() != KBD_REPLY_ACK)
return "et rate: no ACK"

return NULL;
}

键盘中断:
在做完init后,接下来就等待key press事件了,如果产生了key press,好,调到keyboard_interrupt()来处理,在sa1111中就是调用handle_kbd_event()函数。
handle_kbd_event()函数:首先去读status port,看是不是有按键事件,然后判断是键盘还是鼠标的,如果是键盘的,读 出scancode,然后判断status register的第8位是否为1,如果是1(表示正确,即key press时状态),那么调用 handle_keyboard_event(scancode)。

static inline void handle_keyboard_event(unsigned char scancode)
{
#ifdef CONFIG_VT
kbd_exists = 1;
if (do_acknowledge(scancode)) /* 我们希望读出的不是键盘的CMD数据,正确,返回1,否则0 */
handle_scancode(scancode, !(scancode 0x80)); /* 这就是我 们要注意的最重要的函数,我们看到scancode 0x80,为什么要这样,因为KBDDATA register最高位是判断键是press还是 release,如果是1就是release。后面的7位才是我们要的数据。 */
#endif
tasklet_schedule(&eyboard_tasklet); /* 一不做,二不修,我们再一次把keyboard_tasklet放到tasklet中,确保bottom half的执行。*/
}


handle_scancode()函数:
进入handle_scancode(), 你准备好了吗?,首先我们要看到此函数是kernel EXPORT出去的函数,也就是说我们可以在user-level程序中调用他,不过,如果把这段代码改写一下,在user-level中调用才有意义的多。
Handle_scancode()主要是:判断key是按下还是释放,然后把当前的tty_driver赋给变量tty,tty_driver可看成/dev/tty*的输出设备,不要和tty_struct混乱起来。
void handle_scancode(unsigned char scancode, int down)
{
unsigned char keycode;
char up_flag = down ? 0 : 0200; /* 判断,如果是press那么up_flag=0,否则up_flag=0200 */
char raw_mode; /* 键盘模式 */

pm_access(pm_kbd);

add_keyboard_randomness(scancode | up_flag);

tty = ttytab? ttytab[fg_console]: NULL; /* 把当前的TTY参数传给变量 tty, 由tty来控制操作 */
if (tty & (!tty->river_data)) { /* 我们如果直接操作tty是危险的,因为我们不知道tty是否被打 开了,所以我们通过tty->river_data来判断,tty打开的时候,tty->river_data是被设置的,tty关闭的时 候,他被clear */
/*
* We touch the tty structure via the ttytab array
* without knowing whether or not tty is open, which
* is inherently dangerous. We currently rely on that
* fact that console_open sets tty->river_data when
* it opens it, and clears it when it closes it.
*/
tty = NULL;
}
kbd = kbd_table + fg_console; /*kbd_table是kbd[]的指针,所以kbd就是当前tty的kbd_struct的指针 */
if ((raw_mode = (kbd->bdmode == VC_RAW))) { /* 我们判断当前的kbd模式是否为VC_RAW,我们知道如果是的话,我们就可以把scancode 直接放到tty_flip_buffer中。 */
put_queue(scancode | up_flag);
/* we do not return yet, because we want to maintain
the key_down array, so that we have the correct
values when finishing RAW mode or when changing VT' */
}

/*
* 把scancode 转化为 keycode。 kbd_translate()函数,
* 我们分析sa1111的。分析在下面
*/
if (!kbd_translate(scancode, &eycode, raw_mode))
return;

if (0) printk(__FUNCTION__ " scancode=%d keycode=%d raw_mode=%d\n" scancode, keycode, raw_mode);

/*
* 变量keycode包含的是键控代码(u-char)
* 注意到keycode 不允许为0 (on m68k 0 是允许的).
* 我们明了了键的 up/down 状态,我们要把状态传进去
* 并且如果是MEDIUMRAW 模式,我们要返回keycode的值
*/

kbd_processkeycode(keycode, up_flag, 0);
}




sa1111_translate()函数:
因为scancode 转化成keycode 是这样的重要,所以我才不的不把他拿出来单独讲解了。我们只有知道scancode和keycode 的区别,我们才可以做下面的事情。
我们知道我们从键盘的 IO口读出的是scancode,那我们怎么知道每个键的scancode 呀?通过:Linux下的showkey ? Cs我们就可以知道每个键的scancode, 如果想知道相应的keycode,你重要用showkey就可以了。(后面的代码我写了一个模仿 showkey的程序)
通过测试,我们知道1~88键按下的scancode是“0x**”,而89~128是以 “0xe0(或0xe1) 0x**”表示的。如果是释放,只要加上个128就可以了。
然后我们来读以下的代码:
int sa1111_translate(unsigned char scancode, unsigned char *keycode,
char raw_mode)
{
static int prev_scancode = 0;

/* special prefix scancodes.. */
if (scancode == 0xe0 || scancode == 0xe1) {
prev_scancode = scancode;
return 0;
}

/* 0xFF is sent by a few keyboards, ignore it. 0x00 is error */
if (scancode == 0x00 || scancode == 0xff) {
prev_scancode = 0;
return 0;
}

scancode & 0x7f;

if (prev_scancode) {
/*
* usually it will be 0xe0, but a Pause key generates
* e1 1d 45 e1 9d c5 when pressed, and nothing when released
*/
if (prev_scancode != 0xe0) {
if (prev_scancode == 0xe1 & scancode == 0x1d) {
prev_scancode = 0x100;
return 0;
} else if (prev_scancode == 0x100 & scancode == 0x45) {
*keycode = E1_PAUSE;
prev_scancode = 0;
} else {
#ifdef KBD_REPORT_UNKN
if (!raw_mode)
printk(KERN_INFO "eyboard: unknown e1 escape sequence\n";
#endif
prev_scancode = 0;
return 0;
}
} else {
prev_scancode = 0;
/*
* The keyboard maintains its own internal caps lock and
* num lock statuses. In caps lock mode E0 AA precedes make
* code and E0 2A follows break code. In num lock mode,
* E0 2A precedes make code and E0 AA follows break code.
* We do our own book-keeping, so we will just ignore these.
*/
/*
* For my keyboard there is no caps lock mode, but there are
* both Shift-L and Shift-R modes. The former mode generates
* E0 2A / E0 AA pairs, the latter E0 B6 / E0 36 pairs.
* So, we should also ignore the latter. - aeb@cwi.nl
*/
if (scancode == 0x2a || scancode == 0x36)
return 0;

if (e0_keys[scancode])
*keycode = e0_keys[scancode];
else {
#ifdef KBD_REPORT_UNKN
if (!raw_mode)
printk(KERN_INFO "eyboard: unknown scancode e0 %02x\n"
scancode);
#endif
return 0;
}
}
} else if (scancode > SC_LIM) {
/* This happens with the FOCUS 9000 keyboard
Its keys PF1..PF12 are reported to generate
55 73 77 78 79 7a 7b 7c 74 7e 6d 6f
Moreover, unless repeated, they do not generate
key-down events, so we have to zero up_flag below */
/* Also, Japanese 86/106 keyboards are reported to
generate 0x73 and 0x7d for \ - and \ | respectively. */
/* Also, some Brazilian keyboard is reported to produce
0x73 and 0x7e for \ ? and KP-dot, respectively. */

*keycode = high_keys[scancode - SC_LIM];

if (!*keycode) {
if (!raw_mode) {
#ifdef KBD_REPORT_UNKN
printk(KERN_INFO "eyboard: unrecognized scancode (%02x)"
"nbsp;- ignored\n" scancode);
#endif
}
return 0;
}
} else
*keycode = scancode;
return 1;
}

因为涉及到high_keys[] 和 e0_keys[],因此我们把代码拿进来读一读:

static unsigned char high_keys[128 - SC_LIM] = {
RGN1, RGN2, RGN3, RGN4, 0, 0, 0, /* 0x59-0x5f */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x60-0x67 */
0, 0, 0, 0, 0, FOCUS_PF11, 0, FOCUS_PF12, /* 0x68-0x6f */
0, 0, 0, FOCUS_PF2, FOCUS_PF9, 0, 0, FOCUS_PF3, /* 0x70-0x77 */
FOCUS_PF4, FOCUS_PF5, FOCUS_PF6, FOCUS_PF7, /* 0x78-0x7b */
FOCUS_PF8, JAP_86, FOCUS_PF10, 0 /* 0x7c-0x7f */
};


static unsigned char e0_keys[128] = {
0, 0, 0, 0, 0, 0, 0, 0, /* 0x00-0x07 */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x08-0x0f */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x10-0x17 */
0, 0, 0, 0, E0_KPENTER, E0_RCTRL, 0, 0, /* 0x18-0x1f */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x20-0x27 */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x28-0x2f */
0, 0, 0, 0, 0, E0_KPSLASH, 0, E0_PRSCR, /* 0x30-0x37 */
E0_RALT, 0, 0, 0, 0, E0_F13, E0_F14, E0_HELP, /* 0x38-0x3f */
E0_DO, E0_F17, 0, 0, 0, 0, E0_BREAK, E0_HOME, /* 0x40-0x47 */
E0_UP, E0_PGUP, 0, E0_LEFT, E0_OK, E0_RIGHT, E0_KPMINPLUS, E0_END,/* 0x48-0x4f */
E0_DOWN, E0_PGDN, E0_INS, E0_DEL, 0, 0, 0, 0, /* 0x50-0x57 */
0, 0, 0, E0_MSLW, E0_MSRW, E0_MSTM, 0, 0, /* 0x58-0x5f */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x60-0x67 */
0, 0, 0, 0, 0, 0, 0, E0_MACRO, /* 0x68-0x6f */
//0, 0, 0, 0, 0, 0, 0, 0, /* 0x70-0x77 */
0, 0, 0, 0, 0, E0_BACKSLASH, 0, 0, /* 0x70-0x77 */
0, 0, 0, E0_YEN, 0, 0, 0, 0 /* 0x78-0x7f */
};

★我们可以知道scancode 到 keycode的转化了。
对于1~88的scancode,就等于keycode, 比如 ’a’ 的scancode 是0x1e和0x9e,对应的keycode 就是30和158。
对于89-128的scancode有*keycode = e0_keys[scancode]来转化,我们可以举列,比如小键盘的回车: scancode = 0xe0 0x1c,(大家别奇怪怎么发送,它会先发送0xe0 然后发送0x1c),根据上面代码,流程应为: prev_scancode = 0xe0 -->nbsp;scancode = 0x1c -->nbsp;prev_scancode = 0 -->nbsp;执行 *keycode = e0_keys[scancode]; -->nbsp;找到e0_keys[]的 0x1c个元素,即第28个元素,即E0_KPENTER,即为96(#define E0_KPENTER 96)。
另对于键是按下还是放开,keycode是相同的。

★kbd_processkeycode()函数:
这才是我们真正要仔细考虑和分析的函数,keyboard.c文件的庞大就是由于它的存在,kbd_processkeycode()最主要的工作就是根据 keycode和keymap 来决定最终的处理函数。
在这个函数里有几个很重要的变量:
1. 一个就是tty,它的值就是当前tty设备的环境;
2. u_short keysym 是我们真正要用的表示一个键的唯一的值,它是一个32位的数,高16位表示type,type的基数是0xf0;低16位 表示键的值;现在你就知道了为什么我们要不费其烦的把scancode转化成keycode,就是因为我们要用keycode 来找到其对应的 key_map[]中的元素。
3. u_char type 就是keysym的高16位值,这个值一定大于0xf0。我们引入他是因为我们需要对不同的见类型做不同的处理代码,比如方向键,小键盘的键,功能键等的处理代码都是不同的。
4. int shift_final key_maps[]是一个2维表,那我们怎么知道我们要用哪张表呀,答案就是这个变量,他对kbd_struct中相应的变量进行读取,得到这个值,然后就用此值来决定使用哪张表,见下面代码。
5. ushort *key_maps[MAX_NR_KEYMAPS] =
{
plain_map, shift_map, altgr_map, 0,
ctrl_map, shift_ctrl_map, 0, 0,
alt_map, 0, 0, 0,
ctrl_alt_map, 0
};这个变量维护着所有的可能出现的表,每个表都有自己的128个元素。
6. ushort *key_map key_map就指向key_maps[]中具体的一张表了。见下面的代码

static void
kbd_processkeycode(unsigned char keycode, char up_flag, int autorepeat)
{
char raw_mode = (kbd->bdmode == VC_RAW); /* 键盘模式 */

if (up_flag) { /* 放开键 */
rep = 0;
if(!test_and_clear_bit(keycode, key_down))
up_flag = kbd_unexpected_up(keycode);
} else { /* 按下键 */
rep = test_and_set_bit(keycode, key_down);
/* If the keyboard autorepeated for us, ignore it.
* We do our own autorepeat processing.
*/
if (rep & !autorepeat)
return;
}
/* 以下是关与重发的处理,我们暂时忽略他 */

if (kbd_repeatkeycode == keycode || !up_flag || raw_mode) {
kbd_repeatkeycode = -1;
del_timer(&ey_autorepeat_timer);
}
do_poke_blanked_console = 1;
tasklet_schedule(&onsole_tasklet);

#ifdef CONFIG_MAGIC_SYSRQ /* Handle the SysRq Hack */
if (keycode == SYSRQ_KEY) {
sysrq_pressed = !up_flag;
return;
} else if (sysrq_pressed) {
if (!up_flag) {
handle_sysrq(kbd_sysrq_xlate[keycode], kbd_pt_regs, kbd, tty);
return;
}
}
#endif

/*
* Calculate the next time when we have to do some autorepeat
* processing. Note that we do not do autorepeat processing
* while in raw mode but we do do autorepeat processing in
* medium raw mode.
*/
if (!up_flag & !raw_mode) {
kbd_repeatkeycode = keycode;
if (vc_kbd_mode(kbd, VC_REPEAT)) {
if (rep)
key_autorepeat_timer.expires = jiffies + kbd_repeatinterval;
else
key_autorepeat_timer.expires = jiffies + kbd_repeattimeout;
add_timer(&ey_autorepeat_timer);
}
}

if (kbd->bdmode == VC_MEDIUMRAW) {
/* soon keycodes will require more than one byte */
put_queue(keycode + up_flag);
raw_mode = 1; /* Most key classes will be ignored */
}


/* 以下就是我们的重要的部分了,下面的代码必须被执行,由于代码展开的话比较琐碎,我把重要的地方用蓝色来表示 */
/*
* Small change in philosophy: earlier we defined repetition by
* rep = keycode == prev_keycode;
* prev_keycode = keycode;
* but now by the fact that the depressed key was down already.
* Does this ever make a difference? Yes.
*/

/*
* Repeat a key only if the input buffers are empty or the
* characters get echoed locally. This makes key repeat usable
* with slow applications and under heavy loads.
*/
if (!rep ||
(vc_kbd_mode(kbd,VC_REPEAT) & tty &
(L_ECHO(tty) || (tty->river.chars_in_buffer(tty) == 0))))
{
u_short keysym;
u_char type;

/* the XOR below used to be an OR */
/* 我们要计算出当前的键盘状态,键盘状态你别告诉我你不知道呀,就是有没有按住“shift / ctrl / alt / shift+alt /……”呀,当然还有正常状态,也就是什么都 没有按住(=0) */
int shift_final = (shift_state | kbd->lockstate) ^
kbd->ockstate;
ushort *key_map = key_maps[shift_final]; /* key_maps[shift_final] 的定义在 defkeymap.c中,他就是大名鼎鼎的key表,下面会详细的介绍,那么这个时候,key_map就指向具体的一张表了。 */

if (key_map != NULL) {
keysym = key_map[keycode]; /* 我们已经把scancode转化成了keycode,很多人问为什么,就是要在这里用呀,根据keycode找到一个相对应的keysym的值,keysym才是我们真正要用的值呀! */
type = KTYP(keysym); /* 分析一下那些key_maps表,你就知道了,type要取出这些32位数的高16位,为什么?因为, 高16位是表示键类型用的,那什么叫“键类型”,键类型就在include/linux/keyboard.h 中定义,如KT_LATIN表示一般键, KT_CUR表示方向键(就4个,你别说不知道哪4个呀!),…………. */
if (0) printk(__FUNCTION__ " keysym=%#x type=%d shift_final=%d\n" keysym, type, shift_final);

if (type > 0xf0) /* 一定会执行了 */
{
type -= 0xf0; /* 我们要减掉一个基数0xf0,当然了,你也可以不减,那就直接判断呀。 */
if (raw_mode & ! (TYPES_ALLOWED_IN_RAW_MODE (1
return;
if (type == KT_LETTER) /* 大写键比较特殊,再次转化以后在执行 */
{
type = KT_LATIN; /* 一般的键的type */
if (vc_kbd_led(kbd, VC_CAPSLOCK))
{
key_map = key_maps[shift_final ^ (1
if (key_map)
keysym = key_map[keycode];
}
}
if (0) printk(__FUNCTION__ " key_handler=%p keysym=%#x up_flag=%d\n" key_handler[type], keysym, up_flag);
(*key_handler[type])(keysym 0xff, up_flag); /* 这就是重点中的重点了,针对不同的键,有不同的操 作。比如’a’和方向键的操作就是不同的,在keyboard.c中我们定义了很多的针对不同的键的不同处理函数,并且放在 static k_hand key_handler[16]中,根据type我们选择相应的处理函数,这些处理函数都带2个参数,第一个就是32位数 keysym的低16位,另一个就是up_flag标志。这句代码会去调用诸如:do_cur()即方向键的处理代码,do_shift()即按住 shift 键时的处理代码。 */
if (type != KT_SLOCK)
kbd->lockstate = 0;
} else {
/* maybe only if (kbd->bdmode == VC_UNICODE) ? */
if (!up_flag & !raw_mode)
to_utf8(keysym); /* */
}
} else {
/* maybe beep? */
/* we have at least to update shift_state */
#if 1 /* how? two almost equivalent choices follow */
compute_shiftstate();
kbd->lockstate = 0; /* play it safe */
#else
keysym = U(plain_map[keycode]);
type = KTYP(keysym);
if (type == KT_SHIFT)
(*key_handler[type])(keysym 0xff, up_flag);
#endif
}
}
rep = 0;
}

各种不同的键类型:
#define KT_LATIN 0 /* we depend on this being zero */
#define KT_LETTER 11 /* symbol that can be acted upon by CapsLock */
#define KT_FN 1 /* 功能键 */
#define KT_SPEC 2 /* 专用键 */
#define KT_PAD 3 /* 小键盘上的键 */
#define KT_DEAD 4 /* 没有使用过 */
#define KT_CONS 5 /* 没有使用过*/
#define KT_CUR 6 /* 方向键 */
#define KT_SHIFT 7 /* shift按下时?*/
#define KT_META 8
#define KT_ASCII 9
#define KT_LOCK 10 /* shift, alt, ctrl 是否被锁住 */
#define KT_SLOCK 12


ushort *key_maps[MAX_NR_KEYMAPS] = {
plain_map, shift_map, altgr_map, 0,
ctrl_map, shift_ctrl_map, 0, 0,
alt_map, 0, 0, 0,
ctrl_alt_map, 0
};


处理函数:
由于处理函数比较多,而且都涉及到tty的操作,因此我把处理函数的代码分析另外写一份文挡。
处理函数 Keysym Keycode 函数值 对应事件
Do_self 0xf01b 1 0x1b(27) 单击Esc
0xf031 2 0x31(49) 单击1


处理函数 Keysym Keycode Fuc_buf的值 对应事件
do_fn 0xf100 59 '033' '' '' '' 0, 单击F1
0xf101 60 '033' '' '' '' 0, 单击F2
处理函数
Do_spec Keysym Keycode 下一处理函数 对应事件
0xf200 0,89-95,120-127 NULL 没有定义
0xf201 28 Enter() 单击CR(回车)
0xf203 70 shift_map中 show_mem() Ctl+scrolllock

处理函数 Keysym Keycode 下一处理函数 对应事件
do_pad 0xf300 82 do_fn(KVAL(K_INSERT), 0) 单击kp_0
numlock 开、关分别处理
pad_chars[0]=0
0xf305 76 applkey('' vc_kbd_mode(kbd, VC_APPLIC)); 单击kp_5
numlock 开、关分别处理
pad_chars[5]=5


一段自己读取键盘按键处理的代码(模块式):
以下这段代码能够在X86上很好的运行,他主要是重新分配键盘的IRQ,读取键盘的数据寄存器,安排队列的运行,最终打印出scancode的值和按键状态。

#include
#include
#include
#include

/* Bottom Half - 一旦内核模块认为它做任何事都是安全的时候这将被内核调用。 */
static void got_char(void *scancode)
{
printk("can Code %x %s.n"
(int) *((char *) scancode) 0x7F,
*((char *) scancode) 0x80 ? "eleased"nbsp;: "ressed";
}

/* 这个函数为键盘中断服务。它读取来自键盘的相关信息然后安排当内核认为bottom half安全的时候让它运行 */
void irq_handler(int irq,
void *dev_id,
struct pt_regs *regs)
{
/* 这些变量是静态的,因为它们需要对 bottom half 可见(通过指针)。 */
static unsigned char scancode;
static struct tq_struct task = {NULL, 0, got_char, &cancode};
unsigned char status;

/* Read keyboard status */
status = inb(0x64);
scancode = inb(0x60);

/* 安排 bottom half 运行 */
#if LINUX_VERSION_CODE >nbsp;KERNEL_VERSION(2,2,0)
queue_task(&ask, &q_immediate);
#else
queue_task_irq(&ask, &q_immediate);
#endif
mark_bh(IMMEDIATE_BH);
}


/* 初始化模块--登记 IRQ 句柄 */
int init_module()
{
/* 既然键盘的句柄不能和我们的共存,在我们做事情前我们不得不关闭它(释放它的 IRQ)。
* 因为我们不知道它在哪儿,所以以后没有办法恢复它--因此当我们做完时计算机将被重新启动。
*/
free_irq(1, NULL);

/* 请求 IRQ 1,键盘的 IRQ,指向我们的 irq_handler。 */
return request_irq(
1, /* PC上的键盘的 IRQ 号 */
irq_handler, /* 我们的句柄 */
SA_SHIRQ,
/* SA_SHIRQ 意味着我们将另一个句柄用于这个 IRQ。
*
* SA_INTERRUPT 能使句柄为一个快速中断。
*/
"est_keyboard_irq_handler" NULL);
}

/* 清除 */
void cleanup_module()
{
/* 它在这儿只是为了完全。它是完全不相关的,因为我们没有办法恢复通常的键盘中断因此计算机完全没用 * 了,需要被重新启动。 */
free_irq(1, NULL);
}


一段功能和showkey同样强大的代码:
这段代码是参考了一个样本的,很有意义的代码,他演示了任何写一个应用程序,如何考虑到X的情况,如何使用信号来处理代码,如何使你的应用程序支持参数,以及如何使用tty参数,当然更重要的是如何实现showkey的功能(甚至实现了键盘模式设置和显示的代码)。
#include
#include
#include
#include
#include
#include
#include
#include
#include

#define VERSION "ia showkey 0.01"

int tmp; /* for debugging */

#define OPTION "edium raw"

extern int getfd();
int fd;
int oldkbmode;
struct termios old;

/*
* restore kbmode unconditially to XLATE,
* thus making the console unusable when it was called under X.
*/
void get_mode(void) {
char *m;
if (ioctl(fd, KDGKBMODE, &ldkbmode)) {
perror("DGKBMODE";
exit(1);
}
switch(oldkbmode)
{
case K_RAW:
m = "AW" break;
case K_XLATE:
m = "LATE" break;
case K_MEDIUMRAW:
m = "EDIUMRAW" break;
default:
m = "UNKNOWN?" break;
}
printf("b mode was %s\n" m);
if (oldkbmode != K_XLATE) {
printf(" if you are trying this under X, it might not work\n";
printf("ince the X server is also reading /dev/console ]\n";
}
printf("n";
}

void clean_up(void)
{
if (ioctl(fd, KDSKBMODE, oldkbmode)) {
perror("DSKBMODE";
exit(1);
}
tmp = tcsetattr(fd, 0, &ld);
if (tmp)
printf("csetattr = %d\n" tmp);
close(fd);
}

void die(int x) {
printf("aught signal %d, cleaning up...\n" x);
clean_up();
exit(1);
}

void watch_dog(int x) {
clean_up();
exit(0);
}

void usage(void) {
fprintf(stderr, "
showkey version "nbsp;VERSION "nbsp;("nbsp;OPTION "

usage: showkey [options...]

valid options are:

-h --help display this help text
-s --scancodes display only the raw scan-codes
-k --keycodes display only the interpreted keycodes (default)
";
exit(1);
}


int
main (int argc, char *argv[]) {
const char *short_opts = "sk"
const struct option long_opts[] = {
{ "elp" no_argument, NULL, ''nbsp;},
{ "cancodes" no_argument, NULL, ''nbsp;},
{ "eycodes" no_argument, NULL, ''nbsp;},
{ NULL, 0, NULL, 0 }
};
int c;
int show_keycodes = 1;

struct termios new;
unsigned char buf[16];
int i, n;

while ((c = getopt_long(argc, argv,
short_opts, long_opts, NULL)) != -1) {
switch (c) {
case ''
show_keycodes = 0;
break;
case ''
show_keycodes = 1;
break;
case ''
case ''
usage();
}
}

if (optind
usage();

fd = getfd();

/* the program terminates when there is no input for 10 secs */
signal(SIGALRM, watch_dog);

/*
if we receive a signal, we want to exit nicely, in
order not to leave the keyboard in an unusable mode
*/
signal(SIGHUP, die);
signal(SIGINT, die);
signal(SIGQUIT, die);
signal(SIGILL, die);
signal(SIGTRAP, die);
signal(SIGABRT, die);
signal(SIGIOT, die);
signal(SIGFPE, die);
signal(SIGKILL, die);
signal(SIGUSR1, die);
signal(SIGSEGV, die);
signal(SIGUSR2, die);
signal(SIGPIPE, die);
signal(SIGTERM, die);
#ifdef SIGSTKFLT
signal(SIGSTKFLT, die);
#endif
signal(SIGCHLD, die);
signal(SIGCONT, die);
signal(SIGSTOP, die);
signal(SIGTSTP, die);
signal(SIGTTIN, die);
signal(SIGTTOU, die);

get_mode();
tmp = tcgetattr(fd, &ld);
if (tmp)
printf("cgetattr = %d\n" tmp);
tmp = tcgetattr(fd, &ew);
if (tmp)
printf("cgetattr = %d\n" tmp);

new.c_lflag & ~ (ICANON | ECHO | ISIG);
new.c_iflag = 0;
new.c_cc[VMIN] = sizeof(buf);
new.c_cc[VTIME] = 1; /* 0.1 sec intercharacter timeout */

tmp = tcsetattr(fd, TCSAFLUSH, &ew);
if (tmp)
printf("csetattr = %d\n" tmp);
if (ioctl(fd, KDSKBMODE,
show_keycodes ? K_MEDIUMRAW : K_RAW)) {
perror("DSKBMODE";
exit(1);
}

printf("ress any key (program terminates after 10s of last keypress)...\n";
while (1) {
alarm(10);
n = read(fd, buf, sizeof(buf));
for (i = 0; i
if (!show_keycodes)
printf("x%02x " buf[i]);
else
printf("eycode %3d %s\n"
buf[i] 0x7f,
buf[i] 0x80 ? "elease"nbsp;: "ress";
}
if (!show_keycodes)
printf("n";
}

clean_up();
exit(0);
}


scancode和keycode一一对应:
以下其实就是defkeymap.map文件,你可以找出相应关系:
# Default kernel keymap. This uses 7 modifier combinations.
keymaps 0-2,4-5,8,12
# Change the above line into
# keymaps 0-2,4-6,8,12
# in case you want the entries
# altgr control keycode 83 = Boot
# altgr control keycode 111 = Boot
# below.
#
# In fact AltGr is used very little, and one more keymap can
# be saved by mapping AltGr to Alt (and adapting a few entries):
# keycode 100 = Alt
#
keycode 1 = Escape Escape
alt keycode 1 = Meta_Escape
keycode 2 = one exclam
alt keycode 2 = Meta_one
keycode 3 = two at at
control keycode 3 = nul
shift control keycode 3 = nul
alt keycode 3 = Meta_two
keycode 4 = three numbersign
control keycode 4 = Escape
alt keycode 4 = Meta_three
keycode 5 = four dollar dollar
control keycode 5 = Control_backslash
alt keycode 5 = Meta_four
keycode 6 = five percent
control keycode 6 = Control_bracketright
alt keycode 6 = Meta_five
keycode 7 = six asciicircum
control keycode 7 = Control_asciicircum
alt keycode 7 = Meta_six
keycode 8 = seven ampersand braceleft
control keycode 8 = Control_underscore
alt keycode 8 = Meta_seven
keycode 9 = eight asterisk bracketleft
control keycode 9 = Delete
alt keycode 9 = Meta_eight
keycode 10 = nine parenleft bracketright
alt keycode 10 = Meta_nine
keycode 11 = zero parenright braceright
alt keycode 11 = Meta_zero
keycode 12 = minus underscore backslash
control keycode 12 = Control_underscore
shift control keycode 12 = Control_underscore
alt keycode 12 = Meta_minus
keycode 13 = equal plus
alt keycode 13 = Meta_equal
keycode 14 = Delete Delete
control keycode 14 = BackSpace
alt keycode 14 = Meta_Delete
keycode 15 = Tab Tab
alt keycode 15 = Meta_Tab
keycode 16 = q
keycode 17 = w
keycode 18 = e
altgr keycode 18 = Hex_E
keycode 19 = r
keycode 20 = t
keycode 21 = y
keycode 22 = u
keycode 23 = i
keycode 24 = o
keycode 25 = p
keycode 26 = bracketleft braceleft
control keycode 26 = Escape
alt keycode 26 = Meta_bracketleft
keycode 27 = bracketright braceright asciitilde
control keycode 27 = Control_bracketright
alt keycode 27 = Meta_bracketright
keycode 28 = Return
alt keycode 28 = Meta_Control_m
keycode 29 = Control
keycode 30 = a
altgr keycode 30 = Hex_A
keycode 31 = s
keycode 32 = d
altgr keycode 32 = Hex_D
keycode 33 = f
altgr keycode 33 = Hex_F
keycode 34 = g
keycode 35 = h
keycode 36 = j
keycode 37 = k
keycode 38 = l
keycode 39 = semicolon colon
alt keycode 39 = Meta_semicolon
keycode 40 = apostrophe quotedbl
control keycode 40 = Control_g
alt keycode 40 = Meta_apostrophe
keycode 41 = grave asciitilde
control keycode 41 = nul
alt keycode 41 = Meta_grave
keycode 42 = Shift
keycode 43 = backslash bar
control keycode 43 = Control_backslash
alt keycode 43 = Meta_backslash
keycode 44 = z
keycode 45 = x
keycode 46 = c
altgr keycode 46 = Hex_C
keycode 47 = v
keycode 48 = b
altgr keycode 48 = Hex_B
keycode 49 = n
keycode 50 = m
keycode 51 = comma less
alt keycode 51 = Meta_comma
keycode 52 = period greater
control keycode 52 = Compose
alt keycode 52 = Meta_period
keycode 53 = slash question
control keycode 53 = Delete
alt keycode 53 = Meta_slash
keycode 54 = Shift
keycode 55 = KP_Multiply
keycode 56 = Alt
keycode 57 = space space
control keycode 57 = nul
alt keycode 57 = Meta_space
keycode 58 = Caps_Lock
keycode 59 = F1 F11 Console_13
control keycode 59 = F1
alt keycode 59 = Console_1
control alt keycode 59 = Console_1
keycode 60 = F2 F12 Console_14
control keycode 60 = F2
alt keycode 60 = Console_2
control alt keycode 60 = Console_2
keycode 61 = F3 F13 Console_15
control keycode 61 = F3
alt keycode 61 = Console_3
control alt keycode 61 = Console_3
keycode 62 = F4 F14 Console_16
control keycode 62 = F4
alt keycode 62 = Console_4
control alt keycode 62 = Console_4
keycode 63 = F5 F15 Console_17
control keycode 63 = F5
alt keycode 63 = Console_5
control alt keycode 63 = Console_5
keycode 64 = F6 F16 Console_18
control keycode 64 = F6
alt keycode 64 = Console_6
control alt keycode 64 = Console_6
keycode 65 = F7 F17 Console_19
control keycode 65 = F7
alt keycode 65 = Console_7
control alt keycode 65 = Console_7
keycode 66 = F8 F18 Console_20
control keycode 66 = F8
alt keycode 66 = Console_8
control alt keycode 66 = Console_8
keycode 67 = F9 F19 Console_21
control keycode 67 = F9
alt keycode 67 = Console_9
control alt keycode 67 = Console_9
keycode 68 = F10 F20 Console_22
control keycode 68 = F10
alt keycode 68 = Console_10
control alt keycode 68 = Console_10
keycode 69 = Num_Lock
shift keycode 69 = Bare_Num_Lock
keycode 70 = Scroll_Lock Show_Memory Show_Registers
control keycode 70 = Show_State
alt keycode 70 = Scroll_Lock
keycode 71 = KP_7
alt keycode 71 = Ascii_7
altgr keycode 71 = Hex_7
keycode 72 = KP_8
alt keycode 72 = Ascii_8
altgr keycode 72 = Hex_8
keycode 73 = KP_9
alt keycode 73 = Ascii_9
altgr keycode 73 = Hex_9
keycode 74 = KP_Subtract
keycode 75 = KP_4
alt keycode 75 = Ascii_4
altgr keycode 75 = Hex_4
keycode 76 = KP_5
alt keycode 76 = Ascii_5
altgr keycode 76 = Hex_5
keycode 77 = KP_6
alt keycode 77 = Ascii_6
altgr keycode 77 = Hex_6
keycode 78 = KP_Add
keycode 79 = KP_1
alt keycode 79 = Ascii_1
altgr keycode 79 = Hex_1
keycode 80 = KP_2
alt keycode 80 = Ascii_2
altgr keycode 80 = Hex_2
keycode 81 = KP_3
alt keycode 81 = Ascii_3
altgr keycode 81 = Hex_3
keycode 82 = KP_0
alt keycode 82 = Ascii_0
altgr keycode 82 = Hex_0
keycode 83 = KP_Period
# altgr control keycode 83 = Boot
control alt keycode 83 = Boot
keycode 84 = Last_Console
keycode 85 =
keycode 86 = less greater bar
alt keycode 86 = Meta_less
keycode 87 = F11 F11 Console_23
control keycode 87 = F11
alt keycode 87 = Console_11
control alt keycode 87 = Console_11
keycode 88 = F12 F12 Console_24
control keycode 88 = F12
alt keycode 88 = Console_12
control alt keycode 88 = Console_12
keycode 89 =
keycode 90 =
keycode 91 =
keycode 92 =
keycode 93 =
keycode 94 =
keycode 95 =
keycode 96 = KP_Enter
keycode 97 = Control
keycode 98 = KP_Divide
keycode 99 = Control_backslash
control keycode 99 = Control_backslash
alt keycode 99 = Control_backslash
keycode 100 = AltGr
keycode 101 = Break
keycode 102 = Find
keycode 103 = Up
keycode 104 = Prior
shift keycode 104 = Scroll_Backward
keycode 105 = Left
alt keycode 105 = Decr_Console
keycode 106 = Right
alt keycode 106 = Incr_Console
keycode 107 = Select
keycode 108 = Down
keycode 109 = Next
shift keycode 109 = Scroll_Forward
keycode 110 = Insert
keycode 111 = Remove
# altgr control keycode 111 = Boot
control alt keycode 111 = Boot
keycode 112 = Macro
keycode 113 = F13
keycode 114 = F14
keycode 115 = Help
keycode 116 = Do
keycode 117 = F17
keycode 118 = KP_MinPlus
keycode 119 = Pause
keycode 120 =
keycode 121 =
keycode 122 =
keycode 123 =
keycode 124 =
keycode 125 =
keycode 126 =
keycode 127 =
string F1 = "033[[A"
string F2 = "033[[B"
string F3 = "033[[C"
string F4 = "033[[D"
string F5 = "033[[E"
string F6 = "033[17~"
string F7 = "033[18~"
string F8 = "033[19~"
string F9 = "033[20~"
string F10 = "033[21~"
string F11 = "033[23~"
string F12 = "033[24~"
string F13 = "033[25~"
string F14 = "033[26~"
string F15 = "033[28~"
string F16 = "033[29~"
string F17 = "033[31~"
string F18 = "033[32~"
string F19 = "033[33~"
string F20 = "033[34~"
string Find = "033[1~"
string Insert = "033[2~"
string Remove = "033[3~"
string Select = "033[4~"
string Prior = "033[5~"
string Next = "033[6~"
string Macro = "033[M"
string Pause = "033[P"
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''#39; ''nbsp;to '
compose ''#39; ''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose 'quot;'nbsp;''nbsp;to '
compose 'quot;'nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''#39; ''nbsp;to '
compose ''#39; ''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose 'quot;'nbsp;''nbsp;to '
compose 'quot;'nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''#39; ''nbsp;to '
compose ''#39; ''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose 'quot;'nbsp;''nbsp;to '
compose 'quot;'nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''#39; ''nbsp;to '
compose ''#39; ''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose 'quot;'nbsp;''nbsp;to '
compose 'quot;'nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''#39; ''nbsp;to '
compose ''#39; ''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose 'quot;'nbsp;''nbsp;to '
compose 'quot;'nbsp;''nbsp;to '
compose ''#39; ''nbsp;to '
compose ''#39; ''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to '
compose 'quot;'nbsp;''nbsp;to ''
compose ''nbsp;''nbsp;to '
compose ''nbsp;''nbsp;to ''

没有评论: