Files
ViperOS/VKernel/Kernel/drivers/input/keyboard/keyboard.vp
T
TermiNexus 266e6fbcfb feat(keyboard): 恢复 Shift/CapsLock 与方向键等特殊键
旧内核桌面 desktop.vp 里有完整的键盘解码(修饰键状态、特殊键映射、重复),S3e 下沉后丢了,只剩一个简化 scancode_to_ascii(shift/caps 字段从未赋值、方向键返回 0 被丢)。① 内核 keyboard.vp:新增解码层 _decode_scancode + 输出队列,跟踪 Shift(0x2A/0x36)/CapsLock(0x3A)/Ctrl/Alt,Shift 输出大写与上档符号、Caps 只翻转字母区;E0 前缀的独立方向键/Home/End/Delete/PageUp/PageDown/Insert 输出 ANSI 序列(ESC[A/B/C/D、ESC[H/F、ESC[3~/5~/6~/2~);释放码只清状态不产字符。devfs 的 ASCII 字节流契约不变。② vpui:Tk 新增 _ansi_state/_ansi_param 与 _fold_ansi_key(),在 KEY 分发前折叠 ANSI 序列,映射到既有 VK_* 语义(Text/Entry 里那些分支不再是死代码)。Trim:Insert 无 VK_INSERT 定义、被吞掉;typematic 重复未恢复;USB 键盘未涉及。
2026-10-17 13:01:56 +08:00

279 lines
10 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import asm
import intr.idt as idt
import drivers.serial.uart.serial as serial
import platform.pch.pic as pic
import string
import t, c
define KBD_DATA = 0x60
define KBD_STATUS = 0x64
define KBD_COMMAND = 0x64
define KBD_BUF_SIZE = 1024
define KBD_OUT_SIZE = 1024
define KEY_RELEASED = 0x80
buffer: [KBD_BUF_SIZE]uint8
buf_head: static uint32 = 0
buf_tail: static uint32 = 0
buf_count: static uint32 = 0
kbd_irq_cnt: static uint64 = 0
# 硬件初始化只做一次(init 有多个调用点,见 init() 内注释)
_kbd_inited: static int = 0
# 解码后的 ASCII / ANSI 转义字节队列(read_scancode 从原始缓冲解码后填入)
out_buf: [KBD_OUT_SIZE]uint8
out_head: static uint32 = 0
out_tail: static uint32 = 0
out_count: static uint32 = 0
shift_l: static uint8 = 0
shift_r: static uint8 = 0
ctrl_l: static uint8 = 0
alt_l: static uint8 = 0
caps_lock: static uint8 = 0
_e0_pending: static uint8 = 0
scancode_set1: [128]char = [
'\0', '\x1b', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
'\0', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'", '`', '\0',
'\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', '\0', '\0', '\0', ' ',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '-', '8', '6', '2', '\0',
'1', '+', '4', '5', '3', '0', '.', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0'
]
scancode_set1_shift: [128]char = [
'\0', '\x1b', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b',
'\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
'\0', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', '\0',
'|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', '\0', '\0', '\0', ' ',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '-', '8', '6', '2', '\0',
'1', '+', '4', '5', '3', '0', '.', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0'
]
# 清空 8042 输出缓冲(上电自检字节 / 遗留 ACK / 读 CCB 时压入的值)
def _drain_outbuf():
drain: uint32 = 0
while (asm.inb(KBD_STATUS) & 0x01) != 0:
asm.inb(KBD_DATA)
drain += 1
if drain > 1000: break
def _wait_read():
timeout: uint32 = 100000
while timeout > 0:
if (asm.inb(KBD_STATUS) & 0x01) != 0: return
timeout -= 1
def _wait_write():
timeout: uint32 = 100000
while timeout > 0:
if (asm.inb(KBD_STATUS) & 0x02) == 0: return
timeout -= 1
def irq_handler() -> int:
global buf_head, buf_tail, buf_count, kbd_irq_cnt
kbd_irq_cnt += 1
# 0x60 是键盘/鼠标**共享**的输出缓冲:AUX 位(0x64 bit5)置位表示这是鼠标字节,
# 不属于键盘 —— 直接读走会让鼠标驱动永远拿不到(本 ISR 只收键盘字节)。
st: uint8 = asm.inb(KBD_STATUS)
if (st & 0x20) != 0:
return 0
scancode: uint8 = asm.inb(KBD_DATA)
if buf_count < KBD_BUF_SIZE:
buffer[buf_tail] = scancode
buf_tail = buf_tail + 1
if buf_tail >= KBD_BUF_SIZE:
buf_tail = 0
buf_count += 1
return 0
def _out_push(b: uint8):
global out_tail, out_count
if out_count >= KBD_OUT_SIZE: return
out_buf[out_tail] = b
out_tail = out_tail + 1
if out_tail >= KBD_OUT_SIZE:
out_tail = 0
out_count += 1
def _emit_csi(final: uint8):
_out_push(uint8(0x1B))
_out_push(uint8(0x5B))
_out_push(final)
def _emit_csi_tilde(digit: uint8):
_out_push(uint8(0x1B))
_out_push(uint8(0x5B))
_out_push(digit)
_out_push(uint8(0x7E))
# CapsLock 只翻转字母大小写(符号仍由 Shift 决定)
def _is_letter_code(code: uint8) -> bool:
if code >= uint8(0x10) and code <= uint8(0x19): return True
if code >= uint8(0x1E) and code <= uint8(0x26): return True
if code >= uint8(0x2C) and code <= uint8(0x32): return True
return False
# 扫描码 -> ASCII / ANSI 转义字节,追加到输出队列
def _decode_scancode(sc: uint8):
global shift_l, shift_r, ctrl_l, alt_l, caps_lock, _e0_pending
if sc == uint8(0xE0):
_e0_pending = 1
return
if sc == uint8(0xE1):
_e0_pending = 0
return
released: bool = (sc & uint8(KEY_RELEASED)) != 0
code: uint8 = uint8(sc & ~uint8(KEY_RELEASED))
if _e0_pending != 0:
_e0_pending = 0
# E0 1D / 38 = 右 Ctrl / 右 Alt
if code == uint8(0x1D):
ctrl_l = 0
if not released: ctrl_l = 1
return
if code == uint8(0x38):
alt_l = 0
if not released: alt_l = 1
return
if released: return
if code == uint8(0x48): _emit_csi(uint8(0x41)) # Up
elif code == uint8(0x50): _emit_csi(uint8(0x42)) # Down
elif code == uint8(0x4D): _emit_csi(uint8(0x43)) # Right
elif code == uint8(0x4B): _emit_csi(uint8(0x44)) # Left
elif code == uint8(0x47): _emit_csi(uint8(0x48)) # Home
elif code == uint8(0x4F): _emit_csi(uint8(0x46)) # End
elif code == uint8(0x53): _emit_csi_tilde(uint8(0x33)) # Delete
elif code == uint8(0x49): _emit_csi_tilde(uint8(0x35)) # PageUp
elif code == uint8(0x51): _emit_csi_tilde(uint8(0x36)) # PageDown
elif code == uint8(0x52): _emit_csi_tilde(uint8(0x32)) # Insert
return
if code == uint8(0x2A):
shift_l = 0
if not released: shift_l = 1
return
if code == uint8(0x36):
shift_r = 0
if not released: shift_r = 1
return
if code == uint8(0x1D):
ctrl_l = 0
if not released: ctrl_l = 1
return
if code == uint8(0x38):
alt_l = 0
if not released: alt_l = 1
return
if code == uint8(0x3A):
if not released: caps_lock = caps_lock ^ uint8(1)
return
if released: return
shifted: bool = (shift_l or shift_r) != 0
if caps_lock and _is_letter_code(code):
shifted = not shifted
ch: char = scancode_set1[code]
if shifted:
ch = scancode_set1_shift[code]
if ch != '\0':
_out_push(uint8(ch))
def read_scancode() -> uint8:
global buf_head, buf_count, out_head, out_count
asm.cli()
# 原始扫描码持续解码,直到产出至少一个可读字节
while out_count == 0 and buf_count > 0:
raw: uint8 = buffer[buf_head]
buf_head = buf_head + 1
if buf_head >= KBD_BUF_SIZE:
buf_head = 0
buf_count -= 1
_decode_scancode(raw)
if out_count == 0:
asm.sti()
return 0
code: uint8 = out_buf[out_head]
out_head = out_head + 1
if out_head >= KBD_OUT_SIZE:
out_head = 0
out_count -= 1
asm.sti()
return code
def scancode_to_ascii(scancode: uint8) -> char:
# read_scancode() 已解码为 ASCII/转义字节,这里只做透传
return char(scancode)
def has_data() -> int:
return int(buf_count) + int(out_count)
def init():
global _kbd_inited
# 幂等:init() 有两个调用点(devfs 注册 /dev/input/keyboard、kbd_svc.start),
# 硬件初始化只允许做一次。
if _kbd_inited != 0:
return
_kbd_inited = 1
# 清空控制器输出缓冲(上电自检字节 / 上一次遗留的 ACK 等)
_drain_outbuf()
# 读-改-写 CCB(与 mouse.init 同款写法):
# bit0=1 使能键盘 IRQ1;bit6=1 启用 scancode set1 翻译;bit4=0 使能键盘时钟。
# 这里**不再发 0xAD/0xAE**(禁用 / 启用键盘接口):QEMU 下 0xAD 之后那次 CCB 回写
# 经常失败(键盘数据串入 CCB 读取、回读为 0),0xAE 便恢复不了 ⇒ 键盘接口停在
# 禁用态 ⇒ 之后按键完全不产生任何字节/IRQ1(现场:ki 恒为 2/3、无任何键盘字节)。
# 鼠标没有这个序列,所以它一直正常 —— 键盘对齐鼠标的写法即可。
#
# 全程屏蔽 IRQ1:读 CCB 的 0x20 命令会把 CCB 值放进 0x60 输出缓冲并拉高 IRQ1,
# IRQ1 handler 一旦运行就会把这个字节当扫描码读走(现场:handler 装上后读到
# sc=0x47,正是鼠标清 bit5 后的 CCB),这里的 inb(0x60) 便只能拿到残留值 ⇒ 可能把
# 错误的 CCB 写回。屏蔽期 8259 仍会锁存请求,所以解屏蔽前先清空输出缓冲,
# 避免解屏蔽瞬间摄入一个陈旧字节。
pic.setMask(1)
_wait_write()
asm.outb(KBD_COMMAND, 0x20)
_wait_read()
ccb: uint8 = asm.inb(KBD_DATA)
ccb = ccb | 0x01
ccb = ccb | 0x40
ccb = ccb & 0xEF
_wait_write()
asm.outb(KBD_COMMAND, 0x60)
_wait_write()
asm.outb(KBD_DATA, ccb)
# 回读校验:CCB 没写进去 ⇒ 扫描不使能 ⇒ 按键永不产生字节(必须报错,不能静默继续)
_wait_write()
asm.outb(KBD_COMMAND, 0x20)
_wait_read()
rb: uint8 = asm.inb(KBD_DATA)
if rb != ccb:
serial.puts("[kbd] CCB write verify failed\n")
pic.clearMask(1)
return
# 使能键盘扫描(0xF4):非 0xFA ⇒ 命令没送达键盘
_wait_write()
asm.outb(KBD_DATA, 0xF4)
_wait_read()
ack: uint8 = asm.inb(KBD_DATA)
if ack != 0xFA:
serial.puts("[kbd] scan-enable not ACKed\n")
_drain_outbuf()
# 解屏蔽 IRQ1(对齐 mouse.init 的 pic.clearMask(12)),再装 handler
pic.clearMask(1)
idt.irqInstallHandler(1, irq_handler, "keyboard")