diff --git a/Apps/Explorer/desk_server.vp b/Apps/Explorer/desk_server.vp index 7046839..f7baf3b 100644 --- a/Apps/Explorer/desk_server.vp +++ b/Apps/Explorer/desk_server.vp @@ -786,7 +786,9 @@ class DeskServer: # 点击图标:最小化先恢复,再走既有 focus(置顶并整体置脏) def _activate_from_taskbar(self, win: int): + if win < 0 or win >= desk.DESK_MAX_WIN: return s: desk.WinSlot* = self.slot(win) + if s.used == 0: return if s.minimized != 0: s.minimized = 0 self.mark_win(s.uy, s.h) @@ -1151,6 +1153,8 @@ class DeskServer: self.mark(s.uy, s.h) s.used = 0 if self.focused == win: self.focused = -1 + # 拖拽中的窗口被销毁:必须清掉 drag_win,否则后续移动会继续操作这个死槽位。 + if self.drag_win == win: self.drag_win = -1 self.mark_all_windows() self.mark_titles() elif ty == uint32(desk.DESK_REQ_MOVE): @@ -1183,8 +1187,11 @@ class DeskServer: self.mark(0, self.fb_h) def poll_requests(self): - while desk.ring_pop(self.shm + desk.desk_req_ring_off(), &self.msg) != 0: + # 单轮最多处理一整圈:head 已前进,剩余请求留到下一轮,不会重复也不会自旋。 + n: int = 0 + while n < desk.DESK_RING_SLOTS and desk.ring_pop(self.shm + desk.desk_req_ring_off(), &self.msg) != 0: self.handle_request(self.msg) + n += 1 # ---------------- 输入 ---------------- def hit_test(self, x: int, y: int) -> int: @@ -1200,8 +1207,10 @@ class DeskServer: return best def focus(self, win: int): - if win < 0: return + # 只允许聚焦"活"的槽位:死槽位上的 z_order/focused 会把焦点指向已销毁窗口。 + if win < 0 or win >= desk.DESK_MAX_WIN: return s: desk.WinSlot* = self.slot(win) + if s.used == 0: return self.next_z += 1 s.z_order = self.next_z self.focused = win @@ -1226,6 +1235,7 @@ class DeskServer: kb: [16]char kn: int = fs.read(self.kfd, &kb, uint32(16)) if kn > 0: + # 无目标窗口(target < 0)时本批字节丢弃:这里只读一次、不重试,不会空转。 target: int = self._key_target() if target >= 0: i: int = 0 @@ -1298,6 +1308,7 @@ class DeskServer: self.mark_win(s.uy, s.h) s.used = 0 if self.focused == hit: self.focused = -1 + if self.drag_win == hit: self.drag_win = -1 self.mark_titles() elif self.cy < s.uy + TITLE_H: # 标题栏(非关闭按钮)⇒ 开始拖动 diff --git a/VKernel/Kernel/drivers/devfs/devfs.vp b/VKernel/Kernel/drivers/devfs/devfs.vp index 0a41bc8..9a37a0c 100644 --- a/VKernel/Kernel/drivers/devfs/devfs.vp +++ b/VKernel/Kernel/drivers/devfs/devfs.vp @@ -408,12 +408,14 @@ virtual class keyboard_device(devfs_file): def read(self, fd: int, buffer: rawptr, size: uint32) -> int: count: uint32 = 0 - while count < size and keyboard.has_data(): + # 以 read_scancode() 的返回值为准(0 = 队列空):每轮要么取到一个字节并前进, + # 要么立即退出,不再依赖第二个计数量做循环条件(那次混合计数会自旋)。 + while count < size: sc: uint8 = keyboard.read_scancode() + if sc == uint8(0): break ch: char = keyboard.scancode_to_ascii(sc) - if ch != '\0': - char*(buffer)[count] = ch - count += 1 + char*(buffer)[count] = ch + count += 1 return count def write(self, fd: int, buffer: const rawptr, size: uint32) -> int: diff --git a/VKernel/Kernel/drivers/input/keyboard/keyboard.vp b/VKernel/Kernel/drivers/input/keyboard/keyboard.vp index f321228..1e4c95b 100644 --- a/VKernel/Kernel/drivers/input/keyboard/keyboard.vp +++ b/VKernel/Kernel/drivers/input/keyboard/keyboard.vp @@ -1,65 +1,61 @@ -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 +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_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 从原始缓冲解码后填入) +# 唯一的数据队列:IRQ 里把扫描码解码成 ASCII/ANSI 字节后直接入队。 +# 读侧只做"取一个字节",不再在 read 里解码 —— 这样 has_data() 与 read_scancode() +# 看的是**同一个计数**,不可能出现"有数据但取不到"的自旋。 out_buf: [KBD_OUT_SIZE]uint8 out_head: static uint32 = 0 out_tail: static uint32 = 0 out_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 -_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' -] - +_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 @@ -68,211 +64,203 @@ def _drain_outbuf(): 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") +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 _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): + # 整条序列要么全进、要么全丢:半条 CSI 会让 app 侧状态机吞掉下一个字符。 + if int(out_count) + 3 > KBD_OUT_SIZE: return + _out_push(uint8(0x1B)) + _out_push(uint8(0x5B)) + _out_push(final) + +def _emit_csi_tilde(digit: uint8): + if int(out_count) + 4 > KBD_OUT_SIZE: return + _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 转义字节,追加到输出队列(只在 IRQ 上下文调用) +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 irq_handler() -> int: + global 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) + _decode_scancode(scancode) + return 0 + +# 取一个已解码字节(0 = 当前没有)。每次调用最多消耗 out_buf 里的一个字节。 +def read_scancode() -> uint8: + global out_head, out_count + asm.cli() + 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) + +# 只统计解码后的可读字节:>0 时 read_scancode() 必定返回一个非 0 字节 +def has_data() -> int: + return 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") diff --git a/vpsdk/vpui.vp b/vpsdk/vpui.vp index ab4107e..4d1d6f7 100644 --- a/vpsdk/vpui.vp +++ b/vpsdk/vpui.vp @@ -1802,8 +1802,8 @@ virtual class Tk(Wm, Misc): if ch == 91: self._ansi_state = 2 return 0 + # 孤立 ESC:只丢弃 ESC,当前字节照常分发 —— 否则会连带吞掉下一个键入字符。 self._ansi_state = 0 - evt.x = 27 return 1 if ch >= 48 and ch <= 57: self._ansi_param = self._ansi_param * 10 + (ch - 48)