From 266e6fbcfb4f30dc2fa503ca49191fc8a5bba3e0 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 17 Oct 2026 13:01:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(keyboard):=20=E6=81=A2=E5=A4=8D=20Shift/Ca?= =?UTF-8?q?psLock=20=E4=B8=8E=E6=96=B9=E5=90=91=E9=94=AE=E7=AD=89=E7=89=B9?= =?UTF-8?q?=E6=AE=8A=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 旧内核桌面 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 键盘未涉及。 --- .../Kernel/drivers/input/keyboard/keyboard.vp | 165 ++++++++++++++---- vpsdk/vpui.vp | 59 ++++++- 2 files changed, 182 insertions(+), 42 deletions(-) diff --git a/VKernel/Kernel/drivers/input/keyboard/keyboard.vp b/VKernel/Kernel/drivers/input/keyboard/keyboard.vp index 690014d..f321228 100644 --- a/VKernel/Kernel/drivers/input/keyboard/keyboard.vp +++ b/VKernel/Kernel/drivers/input/keyboard/keyboard.vp @@ -10,23 +10,31 @@ define KBD_DATA = 0x60 define KBD_STATUS = 0x64 define KBD_COMMAND = 0x64 -define KBD_BUF_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 +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 - -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 +_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', @@ -89,35 +97,124 @@ def irq_handler() -> int: 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 + global buf_head, buf_count, out_head, out_count asm.cli() - if buf_count == 0: + # 原始扫描码持续解码,直到产出至少一个可读字节 + 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 = buffer[buf_head] - buf_head = buf_head + 1 - if buf_head >= KBD_BUF_SIZE: - buf_head = 0 - buf_count -= 1 + 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: - # 断码(bit7 = 按键释放)不产出字符:剥掉 bit7 再查表会把释放事件也翻译成同一个 - # ASCII,导致每个键输出两个字符(打 ls 得到 llss)。 - if (scancode & uint8(KEY_RELEASED)) != 0: return '\0' - code: uint8 = uint8(scancode & ~uint8(KEY_RELEASED)) - if code >= 128: return '\0' - shifted: bool = (shift_l or shift_r) != 0 - if caps_lock and code >= 0x10 and code <= 0x32: - shifted = not shifted - if shifted: - return scancode_set1_shift[code] - return scancode_set1[code] + # read_scancode() 已解码为 ASCII/转义字节,这里只做透传 + return char(scancode) def has_data() -> int: - return buf_count + return int(buf_count) + int(out_count) def init(): global _kbd_inited diff --git a/vpsdk/vpui.vp b/vpsdk/vpui.vp index e719c6e..ab4107e 100644 --- a/vpsdk/vpui.vp +++ b/vpsdk/vpui.vp @@ -1518,6 +1518,8 @@ virtual class Tk(Wm, Misc): _grid_cols: int _bb: uint32* # 后备缓冲:整帧画完再一次性拷进共享 arena,避免合成器读到半成品帧 _bb_px: int # 后备缓冲容量(像素数),窗口变大时才重新分配 + _ansi_state: int # ESC 序列解析状态:0=普通 1=已收 ESC 2=已收 ESC[ + _ansi_param: int # CSI 数值参数(如 ESC[5~ 的 5) def __init__(self): global _g_layout_init @@ -1551,6 +1553,8 @@ virtual class Tk(Wm, Misc): _g_layout_init = 1 self._bb = None self._bb_px = 0 + self._ansi_state = 0 + self._ansi_param = 0 for j in range(32): self._title[j] = 0 @@ -1785,6 +1789,41 @@ virtual class Tk(Wm, Misc): return w return None + # 逐字节投递的按键流 -> ANSI CSI 状态机;返回 0=本字节被吞掉,1=可分发 + def _fold_ansi_key(self, evt: window.InputEvent*) -> int: + ch: int = evt.x + if self._ansi_state == 0: + if ch == 27: + self._ansi_state = 1 + self._ansi_param = 0 + return 0 + return 1 + if self._ansi_state == 1: + if ch == 91: + self._ansi_state = 2 + return 0 + self._ansi_state = 0 + evt.x = 27 + return 1 + if ch >= 48 and ch <= 57: + self._ansi_param = self._ansi_param * 10 + (ch - 48) + return 0 + self._ansi_state = 0 + vk: int = 0 + if ch == 65: vk = window.VK_UP + elif ch == 66: vk = window.VK_DOWN + elif ch == 67: vk = window.VK_RIGHT + elif ch == 68: vk = window.VK_LEFT + elif ch == 72: vk = window.VK_HOME + elif ch == 70: vk = window.VK_END + elif ch == 126: + if self._ansi_param == 3: vk = window.VK_DELETE + elif self._ansi_param == 5: vk = window.VK_PRIOR + elif self._ansi_param == 6: vk = window.VK_NEXT + if vk == 0: return 0 + evt.x = vk + return 1 + def mainloop(self): self.redraw() evt: window.InputEvent @@ -1803,15 +1842,19 @@ virtual class Tk(Wm, Misc): self.bh = sz & 0xFFFF need_redraw = 1 elif etype == window.EVENT_TYPE_KEY or etype == window.EVENT_TYPE_KEY_RELEASE: - fw: Widget* = self.focused_widget - if fw is None: - fw = self._first_text_widget() + folded: int = 1 + if etype == window.EVENT_TYPE_KEY: + folded = self._fold_ansi_key(&evt) + if folded != 0: + fw: Widget* = self.focused_widget + if fw is None: + fw = self._first_text_widget() + if fw is not None: + fw.focused = 1 + self.focused_widget = fw if fw is not None: - fw.focused = 1 - self.focused_widget = fw - if fw is not None: - handled: int = fw.dispatch_event(&evt) - if handled: need_redraw = 1 + handled: int = fw.dispatch_event(&evt) + if handled: need_redraw = 1 else: if etype == window.EVENT_TYPE_HOVER: i: int = self.widget_count - 1