import vpsdk.desk as desk import vpsdk.devfs as devfs import vpsdk.fs as fs import vpsdk.process as process import vpsdk.syscall as syscall import vpsdk.vpui as vpui # 复用它的 8x16 点阵字体表 from stdint import * import string import viperlib # 【诊断·临时】首个鼠标包已收到的标记(0 = 未收到) _mouse_logged: int = 0 # 首次读到键盘字节时打一次(区分"内核没数据"与"事件没送达 app") _kbd_logged: int = 0 # 【诊断·临时】累计收到的 DESK_REQ_FLUSH 次数(=0 说明 app 从没请求过合成) _flush_cnt: int = 0 # ============================================================================ # explorer 的窗口服务端 + 合成器(S3c):app 把窗口像素写进共享区 arena 后压一条 # DESK_REQ_FLUSH,本服务端按 Z 序逐行合成进 framebuffer;输入来自 /dev/input/*, # 负责光标、命中测试、焦点与事件投递。未实现:任务栏时钟(用户态无时间源)、拖边缩放。 # ============================================================================ define TITLE_H = 32 # 必须与 vpsdk/vpui.vp 的 TITLE_H 一致(app 用 h - TITLE_H - 2 算内容高) define TITLE_GAP = 2 define CURSOR_BMP_W = 16 define CURSOR_BMP_H = 16 # 任务栏与窗口装饰尺寸(对齐内核桌面 desktop.vp) define TASKBAR_H = 50 define TASKBAR_LOGO_SZ = 32 define TASKBAR_ICON_SZ = 36 define TASKBAR_ICON_PAD = 6 # 窗口按钮尺寸(按样式;poll_input 的关闭键热区必须复用同一套取值) define BTN_SIZE = 16 # CLASSIC define BTN_WIN_W = 46 # WIN10/WIN11:宽条,高 = TITLE_H - 2 define BTN_W7_W = 28 # WIN7 define BTN_W7_H = 20 define BTN_XP_SZ = 21 # WINXP define WIN_RADIUS = 8 # WIN11/WIN7/WINXP 的窗口圆角半径 # 键盘投递限流:单轮最多投递 KB_DELIVER_MAX 字节,剩余留在 kb_pend 下一轮继续; # 事件环满时同样停在当前字节,绝不把失败当成功吞掉输入。 define KB_DELIVER_MAX = 64 define KB_PEND_MAX = 64 # 窗口样式(与 vpsdk/window.vp 的 STYLE_* 对齐);当前 CREATE 一律按 CLASSIC 建窗 define STYLE_CLASSIC = 0 define STYLE_WIN10 = 1 define STYLE_WIN11 = 2 define STYLE_WIN7 = 3 define STYLE_WINXP = 4 # 光标形状(与 vpsdk/window.vp 的 CURSOR_* 对齐) define CURSOR_DEFAULT = 0 define CURSOR_IBEAM = 1 define CURSOR_RESIZE_EW = 2 define CURSOR_RESIZE_NS = 3 define CURSOR_RESIZE_NWSE = 4 define CURSOR_HAND = 5 define CURSOR_RESIZE_NESW = 6 # 窗口边缘拖拽缩放的热区宽度 / 最小外框尺寸 define RESIZE_BORDER = 5 define MIN_WIN_W = 160 define MIN_WIN_H = 100 # 窗口阴影(0 = 关闭) define SHADOW_ENABLED = 1 define SHADOW_LEFT = 8 define SHADOW_RIGHT = 12 define SHADOW_TOP = 4 define SHADOW_BOTTOM = 14 define SHADOW_ALPHA = 100 # 光标 16×16 位图模板(取自 desktop.vp 的历史实现):0 = 透明、1 = 黑边、2 = 白心。 # 同一历史文件里的 _CURSOR_IBEAM / _CURSOR_RESIZE_* 待接 WinSlot.cursor 后再启用。 _CURSOR_ARROW: [256]uint8 = [ 1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0, 1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0, 1,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0, 1,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0, 1,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0, 1,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0, 1,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0, 1,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0, 1,2,2,2,1,1,1,1,1,1,0,0,0,0,0,0, 1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0, 1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0, 1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 1,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,0,0,0,0,0,0,0, ] # 其余光标模板同样是 16×16、0=透明 1=黑边 2=白心。 _CURSOR_IBEAM: [256]uint8 = [ 0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0, 0,0,0,0,0,1,2,2,2,2,1,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,1,2,2,2,2,1,0,0,0,0,0, 0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0, ] _CURSOR_HAND: [256]uint8 = [ 0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0, 0,0,0,0,0,0,0,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,0,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,0,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,0,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,0,2,2,1,1,1,0,0,0,0, 0,0,0,0,0,1,2,2,2,2,2,2,1,1,0,0, 0,0,0,0,1,2,1,2,2,1,2,2,2,1,0,0, 0,0,0,1,2,2,1,2,2,1,2,2,2,1,0,0, 0,0,1,2,2,2,1,2,2,1,2,2,2,1,0,0, 0,0,1,2,2,2,1,2,2,1,2,2,1,0,0,0, 0,0,1,2,2,2,1,2,2,1,2,2,1,0,0,0, 0,0,0,1,2,2,2,2,2,2,2,2,1,0,0,0, 0,0,0,1,2,2,2,2,2,2,2,1,0,0,0,0, 0,0,0,0,1,1,2,2,2,2,2,1,0,0,0,0, 0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0, ] _CURSOR_EW: [256]uint8 = [ 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,0,0,0,0,0,0,0,0, 0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0, 0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0, 0,0,1,1,2,0,0,0,0,0,0,2,1,1,0,0, 0,1,1,2,2,1,1,1,1,1,1,2,2,1,1,0, 1,1,2,2,2,2,2,2,2,2,2,2,2,2,1,1, 0,1,1,2,2,2,2,2,2,2,2,2,2,1,1,0, 0,0,1,1,2,1,1,1,1,1,1,2,1,1,0,0, 0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0, 0,0,0,0,1,0,0,0,0,0,0,1,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,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, ] _CURSOR_NS: [256]uint8 = [ 0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0, 0,0,0,0,0,1,1,2,1,1,0,0,0,0,0,0, 0,0,0,0,1,1,2,2,2,1,1,0,0,0,0,0, 0,0,0,1,1,2,2,2,2,2,1,1,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0, 0,0,0,1,1,2,2,2,2,2,1,1,0,0,0,0, 0,0,0,0,1,1,2,2,2,1,1,0,0,0,0,0, 0,0,0,0,0,1,1,2,1,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, ] _CURSOR_NWSE: [256]uint8 = [ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0, 0,1,1,2,2,2,0,0,0,0,0,0,0,0,0,0, 0,0,1,2,2,1,1,0,0,0,0,0,0,0,0,0, 0,0,1,2,1,2,1,1,0,0,0,0,0,0,0,0, 0,0,1,0,1,1,2,1,1,0,0,0,0,0,0,0, 0,0,0,0,0,1,1,2,1,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,1,2,1,1,0,0,0,0,0, 0,0,0,0,0,0,0,1,1,2,1,1,0,1,0,0, 0,0,0,0,0,0,0,0,1,1,2,1,2,1,0,0, 0,0,0,0,0,0,0,0,0,1,1,2,2,1,0,0, 0,0,0,0,0,0,0,0,0,0,2,2,2,1,1,0, 0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ] _CURSOR_NESW: [256]uint8 = [ 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,1,0, 0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0, 0,0,0,0,0,0,0,0,0,0,2,2,2,1,1,0, 0,0,0,0,0,0,0,0,0,1,1,2,2,1,0,0, 0,0,0,0,0,0,0,0,1,1,2,1,2,1,0,0, 0,0,0,0,0,0,0,1,1,2,1,1,0,1,0,0, 0,0,0,0,0,0,1,1,2,1,1,0,0,0,0,0, 0,0,0,0,0,1,1,2,1,1,0,0,0,0,0,0, 0,0,1,0,1,1,2,1,1,0,0,0,0,0,0,0, 0,0,1,2,1,2,1,1,0,0,0,0,0,0,0,0, 0,0,1,2,2,1,1,0,0,0,0,0,0,0,0,0, 0,1,1,2,2,2,0,0,0,0,0,0,0,0,0,0, 0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0, 0,1,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, ] # 圆角窗口的角点/边框判定(移植自内核桌面 desktop.vp) def _is_in_corner(px: int, py: int, cx: int, cy: int, r: int) -> int: dx: int = px - cx dy: int = py - cy if dx < 0: dx = 0 - dx if dy < 0: dy = 0 - dy if dx <= r and dy <= r: if dx * dx + dy * dy > r * r: return 0 return 1 def _is_on_border(rx: int, ry: int, ww: int, wh: int, r: int) -> int: if rx == 0 or rx == ww - 1 or ry == 0 or ry == wh - 1: return 1 if ry < r: if rx < r: dx: int = rx - r dy: int = ry - r if dx * dx + dy * dy >= (r - 1) * (r - 1): return 1 elif rx >= ww - r: dx = rx - (ww - r - 1) dy = ry - r if dx * dx + dy * dy >= (r - 1) * (r - 1): return 1 elif ry >= wh - r: if rx < r: dx = rx - r dy = ry - (wh - r - 1) if dx * dx + dy * dy >= (r - 1) * (r - 1): return 1 elif rx >= ww - r: dx = rx - (ww - r - 1) dy = ry - (wh - r - 1) if dx * dx + dy * dy >= (r - 1) * (r - 1): return 1 return 0 def _corner_alpha(px: int, py: int, cx: int, cy: int, r: int) -> int: dx: int = px - cx dy: int = py - cy if dx < 0: dx = 0 - dx if dy < 0: dy = 0 - dy if dx <= r and dy <= r: d2: int = dx * dx + dy * dy if d2 > r * r: return 0 if d2 >= (r + 1) * (r + 1): return 160 return 255 return 255 class DeskServer: # 屏幕 fb: uint32* # /dev/fb0 映射后的线性地址 fb_fd: int # /dev/fb0 的 fd(等 vblank 用) fb_w: int fb_h: int pitch_px: int fmt: uint32 # 0 = RGBA, 1 = BGRA bg_row: uint32* # 一行背景的预构造副本 back: uint32* # 后备缓冲:绘制先落这里,整帧后逐行拷到 fb # 共享区 shm: uint64 # 输入 kfd: int mfd: int # 键盘待投递字节(设备已读出但事件环暂时投不进去的部分) kb_pend: [KB_PEND_MAX]char kb_pend_off: int kb_pend_len: int # 脏行 dirty: uint8* # 每行 1 字节 dirty_any: int # 光标 / 焦点 / 拖拽 cx: int cy: int pcx: int pcy: int focused: int next_z: int btn_down: int # 标题栏按钮的捕获态:btn_win = 窗口下标(-1 = 无),btn_kind = 0/1关闭/2最小化/3最大化 btn_win: int btn_kind: int drag_win: int drag_dx: int drag_dy: int # 按住鼠标时的捕获窗口:内容区按下后,移动/松开事件始终投给它(滚动条等 # 控件靠按住期间的 HOVER 更新位置;移出窗口也不能丢)。 capture_win: int # 边缘拖拽缩放:窗口下标、边掩码(1左 2右 4上 8下)、起始几何/指针 resize_win: int resize_edge: int resize_ox: int resize_oy: int resize_ow: int resize_oh: int resize_sx: int resize_sy: int # 标题栏按钮的 hover 状态(用于变色和刷新) hover_btn_win: int hover_btn_kind: int # 复用缓冲 msg: desk.DeskMsg evt: desk.DeskMsg # ---- 颜色:按 framebuffer 格式打包(与内核 gfx.COLOR_RGB 同口径)---- def rgb(self, r: uint32, g: uint32, b: uint32) -> uint32: if self.fmt == uint32(1): return (uint32(255) << 24) | (r << 16) | (g << 8) | b return (uint32(255) << 24) | (b << 16) | (g << 8) | r # ---- 按 alpha/255 把 fg 混到 bg 上(RGBA/BGRA 同为 R/B@0,16、G@8,可共用 SWAR)---- def _blend(self, bg: uint32, fg: uint32, a: int) -> uint32: ia: int = 255 - a v: int = ((int(fg & uint32(0x00FF00FF)) * a) + (int(bg & uint32(0x00FF00FF)) * ia)) >> 8 g: int = ((int((fg >> 8) & uint32(0xFF)) * a) + (int((bg >> 8) & uint32(0xFF)) * ia)) >> 8 return (uint32(255) << 24) | (uint32(v) & uint32(0x00FF00FF)) | (uint32(g & 0xFF) << 8) def init(self, fb: uint32*, w: int, h: int, pitch: int, fmt: uint32, shm: uint64, kfd: int, mfd: int, fb_fd: int): self.fb = fb self.fb_fd = fb_fd self.fb_w = w self.fb_h = h self.pitch_px = pitch / 4 self.fmt = fmt self.shm = shm self.kfd = kfd self.mfd = mfd self.kb_pend_off = 0 self.kb_pend_len = 0 # dirty 与 bg_row 必须真正分配(匿名 mmap),不能把整数当地址用 self.dirty = uint8*(syscall.mmap(0, uint64(h) + uint64(64), uint64(syscall.PROT_READ | syscall.PROT_WRITE), uint64(syscall.MAP_PRIVATE | syscall.MAP_ANONYMOUS), syscall.MMAP_FD_ANON, 0)) self.dirty_any = 0 self.cx = w / 2 self.cy = h / 2 self.pcx = self.cx self.pcy = self.cy self.focused = -1 self.next_z = 1 self.btn_down = 0 self.btn_win = -1 self.btn_kind = 0 self.drag_win = -1 self.drag_dx = 0 self.drag_dy = 0 self.capture_win = -1 self.resize_win = -1 self.resize_edge = 0 self.resize_ox = 0 self.resize_oy = 0 self.resize_ow = 0 self.resize_oh = 0 self.resize_sx = 0 self.resize_sy = 0 self.hover_btn_win = -1 self.hover_btn_kind = 0 # 预构造一行背景,渲染时整行 memcpy self.bg_row = uint32*(syscall.mmap(0, uint64(w) * uint64(4), uint64(syscall.PROT_READ | syscall.PROT_WRITE), uint64(syscall.MAP_PRIVATE | syscall.MAP_ANONYMOUS), syscall.MMAP_FD_ANON, 0)) col: uint32 = self.rgb(uint32(25), uint32(25), uint32(35)) for i in range(w): self.bg_row[i] = col # 后备缓冲:整屏像素,行距 = fb_w(与 framebuffer 的 pitch 无关) back_va: uint64 = syscall.mmap(0, uint64(w) * uint64(h) * uint64(4), uint64(syscall.PROT_READ | syscall.PROT_WRITE), uint64(syscall.MAP_PRIVATE | syscall.MAP_ANONYMOUS), syscall.MMAP_FD_ANON, 0) if back_va == 0: devfs.serial_puts("[explorer] back buffer alloc FAILED\n") self.back = self.fb # 退化:直写 framebuffer,保留原行为 else: self.back = uint32*(back_va) self.mark_all() # ---------------- 脏行 ---------------- def mark(self, y0: int, h: int): y: int = y0 while y < y0 + h: if y >= 0 and y < self.fb_h: if self.dirty[y] == 0: self.dirty[y] = 1 self.dirty_any = 1 y += 1 def mark_all(self): for y in range(self.fb_h): self.dirty[y] = 1 self.dirty_any = 1 def clear_dirty(self): for y in range(self.fb_h): self.dirty[y] = 0 self.dirty_any = 0 # 窗口移动/增删时连同阴影一起置脏(阴影超出窗口外框) def mark_win(self, y: int, h: int): self.mark(y - SHADOW_TOP, h + SHADOW_TOP + SHADOW_BOTTOM) # 窗口画在任务栏之上,覆盖到底部条带时必须把任务栏一并置脏,否则残留旧内容。 tb: int = self.fb_h - TASKBAR_H if y + h + SHADOW_BOTTOM > tb: self.mark(tb, TASKBAR_H) # 窗口集合/层级变化:所有可见窗口整体置脏(含阴影带) def mark_all_windows(self): for i in range(desk.DESK_MAX_WIN): w: desk.WinSlot* = self.slot(i) if w.used != 0 and w.active != 0 and w.minimized == 0: self.mark_win(w.uy, w.h) # 活动窗口切换会改变标题栏配色:把所有窗口的标题栏行置脏 def mark_titles(self): for i in range(desk.DESK_MAX_WIN): s: desk.WinSlot* = self.slot(i) if s.used != 0: self.mark(s.uy, TITLE_H) # 任务栏图标随焦点/窗口集合变化,一起刷新 self.mark(self.fb_h - TASKBAR_H, TASKBAR_H) # ---------------- 基本绘制 ---------------- def row_fill(self, x0: int, y: int, w: int, col: uint32): if y < 0 or y >= self.fb_h or w <= 0: return x: int = x0 dw: int = w if x < 0: dw = dw + x x = 0 if x + dw > self.fb_w: dw = self.fb_w - x if dw <= 0: return base: int = y * self.fb_w string.memset32(rawptr(uint64(self.back) + uint64((base + x) * 4)), col, uint64(dw)) # 把一行中 [x0, x0+w) 与黑色按 alpha 混合,用于按钮 hover 的“稍微变深”。 def _dim_row(self, x0: int, y: int, w: int, alpha: int): if y < 0 or y >= self.fb_h or w <= 0: return x: int = x0 if x < 0: x = 0 xe: int = x0 + w if xe > self.fb_w: xe = self.fb_w base: int = y * self.fb_w while x < xe: self.back[base + x] = self._blend(self.back[base + x], uint32(0), alpha) x += 1 def row_blit(self, x0: int, y: int, src: uint32*, src_off: int, w: int): """把 src[src_off .. src_off+w) 拷到屏幕 (x0, y),裁剪到屏幕内。""" if y < 0 or y >= self.fb_h or w <= 0: return sx: int = 0 dw: int = w dx: int = x0 if dx < 0: sx = 0 - dx dw = dw + dx dx = 0 if dx + dw > self.fb_w: dw = self.fb_w - dx if dw <= 0: return dst: uint64 = uint64(self.back) + uint64(y * self.fb_w + dx) * uint64(4) srcp: uint64 = uint64(src) + uint64(src_off + sx) * uint64(4) string.memcpy(rawptr(dst), rawptr(srcp), uint64(dw) * uint64(4)) # ---------------- 窗口槽 ---------------- def slot(self, win: int) -> desk.WinSlot*: return desk.WinSlot*(self.shm + desk.desk_slot_off(win)) def _content_h(self, h: int) -> int: ch: int = h - TITLE_H - TITLE_GAP if ch < 1: ch = 1 return ch # 指针落在窗口哪条边(1左 2右 4上 8下,可组合成角);0 = 不在边缘/最大化 def _resize_edge_at(self, s: desk.WinSlot*, x: int, y: int) -> int: if s.maximized != 0: return 0 if x < s.ux or x >= s.ux + s.w or y < s.uy or y >= s.uy + s.h: return 0 e: int = 0 if x < s.ux + RESIZE_BORDER: e |= 1 if x >= s.ux + s.w - RESIZE_BORDER: e |= 2 if y < s.uy + RESIZE_BORDER: e |= 4 if y >= s.uy + s.h - RESIZE_BORDER: e |= 8 # 标题栏按钮区域不参与缩放(否则按钮上沿会被当成顶边) if e != 0 and self._title_btn_hit(s, x, y) != 0: return 0 return e def _edge_cursor(self, e: int) -> int: if e == 0: return CURSOR_DEFAULT if (e & 1) != 0 and (e & 4) != 0: return CURSOR_RESIZE_NWSE if (e & 2) != 0 and (e & 8) != 0: return CURSOR_RESIZE_NWSE if (e & 2) != 0 and (e & 4) != 0: return CURSOR_RESIZE_NESW if (e & 1) != 0 and (e & 8) != 0: return CURSOR_RESIZE_NESW if (e & 3) != 0: return CURSOR_RESIZE_EW if (e & 12) != 0: return CURSOR_RESIZE_NS return CURSOR_DEFAULT def _hover_resize_edge(self) -> int: hw: int = self.hit_test(self.cx, self.cy) if hw < 0: return 0 return self._resize_edge_at(self.slot(hw), self.cx, self.cy) # 调整窗口外框几何。像素缓冲只在容量不足时扩容:arena 是 bump 分配, # 容量内复用可避免反复最大化/还原把 arena 耗尽。返回 1 成功 / 0 失败。 def _set_window_rect(self, s: desk.WinSlot*, x: int, y: int, w: int, h: int) -> int: ch: int = self._content_h(h) need: int = w * ch if w <= 0 or need <= 0: return 0 if need > int(s.fb_cap): # 交互缩放时按 1.5 倍增长,避免拖拽过程中每加几像素就切一块新 arena; # 最大化的一次性大跨度仍会直接按 need 分配。 alloc_px: int = need grow: int = int(s.fb_cap) + int(s.fb_cap) / 2 if grow > alloc_px: alloc_px = grow off: uint32 = desk.desk_arena_alloc(self.shm, uint32(alloc_px * 4)) if off == uint32(0): return 0 s.fb_off = off s.fb_cap = uint32(alloc_px) b: uint32* = uint32*(self.shm + uint64(off)) col: uint32 = self.rgb(uint32(30), uint32(30), uint32(50)) for k in range(alloc_px): b[k] = col s.ux = x s.uy = y s.w = w s.h = h s.fb_w = uint32(w) s.fb_h = uint32(ch) return 1 # 边缘拖拽的实时几何更新:只在按下期间调用,内容缓冲随尺寸同步扩容。 def _drag_resize(self): s: desk.WinSlot* = self.slot(self.resize_win) if s.used == 0: self.resize_win = -1 return dx: int = self.cx - self.resize_sx dy: int = self.cy - self.resize_sy nx: int = self.resize_ox ny: int = self.resize_oy nw: int = self.resize_ow nh: int = self.resize_oh if (self.resize_edge & 1) != 0: nx = self.resize_ox + dx nw = self.resize_ow - dx if (self.resize_edge & 2) != 0: nw = self.resize_ow + dx if (self.resize_edge & 4) != 0: ny = self.resize_oy + dy nh = self.resize_oh - dy if (self.resize_edge & 8) != 0: nh = self.resize_oh + dy if nw < MIN_WIN_W: if (self.resize_edge & 1) != 0: nx = self.resize_ox + self.resize_ow - MIN_WIN_W nw = MIN_WIN_W if nh < MIN_WIN_H: if (self.resize_edge & 4) != 0: ny = self.resize_oy + self.resize_oh - MIN_WIN_H nh = MIN_WIN_H if nx < 0: nx = 0 if ny < 0: ny = 0 if nx + nw > self.fb_w: nw = self.fb_w - nx if ny + nh > self.fb_h: nh = self.fb_h - ny if nx == s.ux and ny == s.uy and nw == s.w and nh == s.h: return self.mark_win(s.uy, s.h) if self._set_window_rect(s, nx, ny, nw, nh) != 0: self.push_event(self.resize_win, uint32(desk.DESK_EVT_RESIZE), int(s.fb_w), int(s.fb_h), 0, 0) self.mark_win(s.uy, s.h) def _focus_top_visible(self): best: int = -1 bz: int = -1 for i in range(desk.DESK_MAX_WIN): s: desk.WinSlot* = self.slot(i) if s.used != 0 and s.active != 0 and s.minimized == 0: if s.z_order > bz: bz = s.z_order best = i self.focused = best def _close_window(self, win: int): s: desk.WinSlot* = self.slot(win) if s.used == 0: return # 关闭:通知一次 CLOSE 后把槽位置空回收;window.vp 的 is_active() 见到 used==0 # 即返回 0,app 的 while 循环自然退出。像素缓冲不回收(arena 是 bump 分配)。 self.push_event(win, uint32(desk.DESK_EVT_CLOSE), 0, 0, 0, 0) self.mark_win(s.uy, s.h) s.used = 0 if self.focused == win: self.focused = -1 if self.drag_win == win: self.drag_win = -1 if self.capture_win == win: self.capture_win = -1 if self.resize_win == win: self.resize_win = -1 if self.btn_win == win: self.btn_win = -1 self.btn_kind = 0 self._focus_top_visible() self.mark_all_windows() self.mark_titles() def _minimize_window(self, win: int): s: desk.WinSlot* = self.slot(win) if s.used == 0 or s.minimized != 0: return self.mark_win(s.uy, s.h) s.minimized = 1 if self.focused == win: self.focused = -1 if self.drag_win == win: self.drag_win = -1 if self.capture_win == win: self.capture_win = -1 if self.resize_win == win: self.resize_win = -1 self._focus_top_visible() self.mark_all_windows() self.mark_titles() def _maximize_window(self, win: int): s: desk.WinSlot* = self.slot(win) if s.used == 0 or s.maximized != 0: return ox: int = s.ux oy: int = s.uy ow: int = s.w oh: int = s.h mw: int = self.fb_w mh: int = self.fb_h - TASKBAR_H if mh < TITLE_H + TITLE_GAP + 1: return if self._set_window_rect(s, 0, 0, mw, mh) == 0: return s.restore_x = ox s.restore_y = oy s.restore_w = ow s.restore_h = oh s.maximized = 1 self.mark_win(oy, oh) self.mark_win(s.uy, s.h) self.push_event(win, uint32(desk.DESK_EVT_RESIZE), int(s.fb_w), int(s.fb_h), 0, 0) self.mark_all_windows() self.mark_titles() def _restore_window(self, win: int) -> int: s: desk.WinSlot* = self.slot(win) if s.used == 0 or s.maximized == 0: return 0 oy: int = s.uy oh: int = s.h if self._set_window_rect(s, s.restore_x, s.restore_y, s.restore_w, s.restore_h) == 0: return 0 s.maximized = 0 self.mark_win(oy, oh) self.mark_win(s.uy, s.h) self.push_event(win, uint32(desk.DESK_EVT_RESIZE), int(s.fb_w), int(s.fb_h), 0, 0) self.mark_all_windows() self.mark_titles() return 1 def _toggle_maximize(self, win: int): s: desk.WinSlot* = self.slot(win) if s.used == 0: return if s.maximized != 0: self._restore_window(win) else: self._maximize_window(win) # 最高 z 序(用于判定活动窗口;无可见窗口返回 -1) def _top_z(self) -> int: m: int = -1 for i in range(desk.DESK_MAX_WIN): s: desk.WinSlot* = self.slot(i) if s.used != 0 and s.active != 0 and s.minimized == 0: if s.z_order > m: m = s.z_order return m # 键盘目标:焦点窗口有效就用它,否则取 z 序最高的可见窗口(无则 -1) def _key_target(self) -> int: if self.focused >= 0: f: desk.WinSlot* = self.slot(self.focused) if f.used != 0 and f.active != 0 and f.minimized == 0: return self.focused best: int = -1 best_z: int = -1 for i in range(desk.DESK_MAX_WIN): s: desk.WinSlot* = self.slot(i) if s.used != 0 and s.active != 0 and s.minimized == 0: if s.z_order > best_z: best_z = s.z_order best = i return best def alloc_window(self, pid: int, x: int, y: int, w: int, h: int, style: int) -> int: """找空槽并分配像素缓冲,返回 win_id(失败 -1)。入参 w/h 是含标题栏的外框; 内容缓冲 w × (h - TITLE_H - TITLE_GAP),与 vpui 的 `bh = h - TITLE_H - 2` 一致。""" content_h: int = h - TITLE_H - TITLE_GAP if content_h < 1: content_h = 1 for i in range(desk.DESK_MAX_WIN): s: desk.WinSlot* = self.slot(i) if s.used == 0: # 槽位复用前整块清零:避免上一任窗口的 pid/几何/缓冲区偏移残留。 string.memset(s, 0, desk.desk_slot_size()) cap_px: int = w * content_h buf_off: uint32 = desk.desk_arena_alloc(self.shm, uint32(cap_px) * uint32(4)) if buf_off == uint32(0): return -1 s.used = 1 s.pid = pid s.active = 1 s.minimized = 0 s.maximized = 0 s.ux = x s.uy = y s.w = w s.h = h s.restore_x = x s.restore_y = y s.restore_w = w s.restore_h = h s.style = style s.cursor = 0 s.fb_off = buf_off s.fb_cap = uint32(cap_px) s.fb_w = uint32(w) s.fb_h = uint32(content_h) string.memset(&s.title, 0, desk.DESK_TITLE_MAX) # 像素缓冲先清成窗口底色,避免 app 还没画就露出垃圾 buf: uint32* = uint32*(self.shm + uint64(buf_off)) col: uint32 = self.rgb(uint32(30), uint32(30), uint32(50)) for k in range(cap_px): buf[k] = col s.z_order = self.next_z self.next_z += 1 self.focused = i self.mark_win(y, h) self.mark_titles() return i return -1 # ---------------- 文本(8x16 点阵,字体表复用 vpui.font)---------------- def draw_char(self, x: int, y: int, ch: char, col: uint32): ci: int = int(uint8(ch)) if ci >= 128: ci = ci & 127 fy: int = 0 while fy < vpui.CHAR_H: ty: int = y + fy if ty >= 0 and ty < self.fb_h: row: uint8 = vpui.font[ci][fy] if row != uint8(0): base: int = ty * self.fb_w fx: int = 0 while fx < vpui.CHAR_W: if row & (0x80 >> fx): px: int = x + fx if px >= 0 and px < self.fb_w: self.back[base + px] = col fx += 1 fy += 1 def draw_str(self, x: int, y: int, s: const str, col: uint32): cx: int = x i: int = 0 while s[i] != 0: self.draw_char(cx, y, s[i], col) cx += vpui.CHAR_W i += 1 # ---------------- 窗口阴影 ---------------- # 单个阴影像素的 alpha(与原逐像素算法完全相同) def _shadow_alpha(self, adx: int, ady: int, me_x: int, me_y: int) -> int: dist: int = adx if ady > dist: dist = ady dist = dist + ((adx + ady - dist) * 3) / 8 me: int = me_x if adx + ady > 0: me = (me_x * adx + me_y * ady) / (adx + ady) if me > 0 and dist > 0 and dist < me: t_val: int = (256 * dist) / me t_inv: int = 256 - t_val return (SHADOW_ALPHA * t_inv * t_inv) / 65536 return 0 def _draw_shadow(self, s: desk.WinSlot*, y0: int, y1: int): wx: int = s.ux wy: int = s.uy ww: int = s.w wh: int = s.h sl: int = SHADOW_LEFT sr: int = SHADOW_RIGHT st: int = SHADOW_TOP sb: int = SHADOW_BOTTOM # 左右窄带(dy=0)的 alpha 只随 adx 变化,预建表 lt: [16]int rt: [16]int tl: [16]int tr: [16]int i: int = 0 while i <= sl: lt[i] = self._shadow_alpha(i, 0, sl, st) i += 1 i = 0 while i <= sr: rt[i] = self._shadow_alpha(i, 0, sr, st) i += 1 yy: int = y0 if yy < wy - st: yy = wy - st yend: int = y1 if yend > wy + wh + sb: yend = wy + wh + sb while yy < yend: if yy >= 0 and yy < self.fb_h: side: int = 0 ady: int = 0 me_y: int = st if yy < wy: ady = wy - yy elif yy >= wy + wh: ady = yy - (wy + wh - 1) me_y = sb else: side = 1 tm: int = 0 if side == 0: i = 0 while i <= sl: tl[i] = self._shadow_alpha(i, ady, sl, me_y) i += 1 i = 0 while i <= sr: tr[i] = self._shadow_alpha(i, ady, sr, me_y) i += 1 tm = self._shadow_alpha(0, ady, sl, me_y) px: int = wx - sl if px < 0: px = 0 xend: int = wx + ww + sr if xend > self.fb_w: xend = self.fb_w base: int = yy * self.fb_w while px < xend: a: int = 0 adx: int = 0 if px < wx: adx = wx - px if side != 0: a = lt[adx] else: a = tl[adx] elif px >= wx + ww: adx = px - (wx + ww - 1) if side != 0: a = rt[adx] else: a = tr[adx] elif side == 0: a = tm if a > 0: idx: int = base + px bg: uint32 = self.back[idx] ia: int = 255 - a rb: int = (int(bg & uint32(0x00FF00FF)) * ia) >> 8 g: int = (int((bg >> 8) & uint32(0xFF)) * ia) >> 8 self.back[idx] = (uint32(255) << 24) | (uint32(rb) & uint32(0x00FF00FF)) | (uint32(g & 0xFF) << 8) px += 1 yy += 1 # ---------------- 窗口逐行绘制(按样式)---------------- # 把 col 以 alpha 混到后备缓冲已有像素上(WIN7 半透明标题栏用);逐像素内联 SWAR def _blend_row(self, x0: int, y: int, w: int, col: uint32, a: int): if y < 0 or y >= self.fb_h or w <= 0 or a <= 0: return x: int = x0 dw: int = w if x < 0: dw = dw + x x = 0 if x + dw > self.fb_w: dw = self.fb_w - x if dw <= 0: return ia: int = 255 - a frb: int = int(col & uint32(0x00FF00FF)) * a fg: int = int((col >> 8) & uint32(0xFF)) * a base: int = y * self.fb_w i: int = 0 while i < dw: idx: int = base + x + i bg: uint32 = self.back[idx] v: int = (frb + int(bg & uint32(0x00FF00FF)) * ia) >> 8 g: int = (fg + int((bg >> 8) & uint32(0xFF)) * ia) >> 8 self.back[idx] = (uint32(255) << 24) | (uint32(v) & uint32(0x00FF00FF)) | (uint32(g & 0xFF) << 8) i += 1 # 标题栏每行颜色:WIN7/WINXP 竖直渐变 + 首两行高光 + 末行暗边,其余样式纯色 def _title_row_color(self, style: int, rel: int, th: int, active: int, title_col: uint32) -> uint32: if style != STYLE_WIN7 and style != STYLE_WINXP: return title_col ratio: int = (rel * 100) / th if ratio > 100: ratio = 100 r: int = 0 g: int = 0 b: int = 0 if style == STYLE_WIN7: if active != 0: r = 156 + ((186 - 156) * ratio) / 100 g = 181 + ((209 - 181) * ratio) / 100 b = 209 + ((233 - 209) * ratio) / 100 else: r = 180 + ((200 - 180) * ratio) / 100 g = 195 + ((210 - 195) * ratio) / 100 b = 210 + ((225 - 210) * ratio) / 100 else: if active != 0: r = 107 + ((58 - 107) * ratio) / 100 g = 158 + ((124 - 158) * ratio) / 100 b = 212 + ((195 - 212) * ratio) / 100 else: r = 150 + ((115 - 150) * ratio) / 100 g = 170 + ((135 - 170) * ratio) / 100 b = 205 + ((175 - 205) * ratio) / 100 col: uint32 = self.rgb(uint32(r), uint32(g), uint32(b)) if rel == 0: if style == STYLE_WIN7: col = self._blend(col, self.rgb(uint32(220), uint32(235), uint32(255)), 50) else: col = self._blend(col, self.rgb(uint32(200), uint32(225), uint32(255)), 50) elif rel == 1: if style == STYLE_WIN7: col = self._blend(col, self.rgb(uint32(210), uint32(225), uint32(250)), 20) else: col = self._blend(col, self.rgb(uint32(200), uint32(225), uint32(255)), 25) if rel == th - 1: if style == STYLE_WIN7: col = self._blend(col, self.rgb(uint32(130), uint32(160), uint32(200)), 25) else: col = self._blend(col, self.rgb(uint32(100), uint32(160), uint32(230)), 40) return col # 直边行:整行底色/内容 blit + 左右/上下 1px 边框(内容行不再先铺整行底色) def _draw_flat_row(self, s: desk.WinSlot*, yy: int, rel: int, row_col: uint32, row_is_title: int, border_col: uint32, title_alpha: int, buf: uint32*, fw: int, fh: int, content_y0: int): wx: int = s.ux ww: int = s.w wh: int = s.h content_row: int = yy - content_y0 if row_is_title != 0 and title_alpha < 255: self._blend_row(wx + 1, yy, ww - 2, row_col, title_alpha) elif rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh: self.row_blit(wx + 1, yy, buf, content_row * fw + 1, ww - 2) else: self.row_fill(wx, yy, ww, row_col) if rel == 0 or rel == wh - 1: self.row_fill(wx, yy, ww, border_col) else: self.row_fill(wx, yy, 1, border_col) self.row_fill(wx + ww - 1, yy, 1, border_col) # 圆角带的行:只有左右各 r 列需要逐像素,中段按直行整段处理 def _draw_round_row(self, s: desk.WinSlot*, yy: int, rel: int, row_col: uint32, row_is_title: int, outline_col: uint32, r: int, title_alpha: int, buf: uint32*, fw: int, fh: int, content_y0: int): wx: int = s.ux ww: int = s.w wh: int = s.h content_row: int = yy - content_y0 do_blit: int = 0 if rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh: do_blit = 1 cyc: int = r if rel >= r: cyc = wh - r - 1 if ww > r + r: seg: int = 0 while seg < 2: xs: int = wx xe: int = wx + r cxc: int = r if seg == 1: xs = wx + ww - r xe = wx + ww cxc = ww - r - 1 px: int = xs while px < xe: if px >= 0 and px < self.fb_w: rx: int = px - wx if _is_in_corner(rx, rel, cxc, cyc, r) != 0: on_border: int = _is_on_border(rx, rel, ww, wh, r) idx: int = yy * self.fb_w + px if on_border == 0 and do_blit != 0: self.back[idx] = buf[content_row * fw + rx] px += 1 continue col: uint32 = row_col if on_border != 0: col = outline_col ca: int = _corner_alpha(rx, rel, cxc, cyc, r) if on_border != 0 and ca > 0 and ca < 255: ca = 255 if row_is_title != 0 and title_alpha < 255 and rel >= 1 and rx >= 1 and rx < ww - 1 and ca == 255 and on_border == 0: ca = title_alpha if ca > 0: if ca < 255: self.back[idx] = self._blend(self.back[idx], col, ca) else: self.back[idx] = col px += 1 seg += 1 mid0: int = wx + r mid1: int = wx + ww - r midw: int = mid1 - mid0 if midw > 0: if rel == 0 or rel == wh - 1: self.row_fill(mid0, yy, midw, outline_col) elif do_blit != 0: self.row_blit(mid0, yy, buf, content_row * fw + r, midw) elif row_is_title != 0 and title_alpha < 255: self._blend_row(mid0, yy, midw, row_col, title_alpha) else: self.row_fill(mid0, yy, midw, row_col) return # 窄窗回退:整行逐像素 px: int = wx xend: int = wx + ww while px < xend: if px >= 0 and px < self.fb_w: rx: int = px - wx cxr: int = r if rx >= ww - r: cxr = ww - r - 1 if _is_in_corner(rx, rel, cxr, cyc, r) != 0: on_border: int = _is_on_border(rx, rel, ww, wh, r) idx: int = yy * self.fb_w + px if on_border == 0 and do_blit != 0: self.back[idx] = buf[content_row * fw + rx] px += 1 continue col: uint32 = row_col if on_border != 0: col = outline_col ca: int = _corner_alpha(rx, rel, cxr, cyc, r) if on_border != 0 and ca > 0 and ca < 255: ca = 255 if row_is_title != 0 and title_alpha < 255 and rel >= 1 and rx >= 1 and rx < ww - 1 and ca == 255 and on_border == 0: ca = title_alpha if ca > 0: if ca < 255: self.back[idx] = self._blend(self.back[idx], col, ca) else: self.back[idx] = col px += 1 # ---------------- 窗口按钮(按样式逐行绘制)---------------- # 圆角块的一行是否保留该列 def _round_keep(self, lx: int, lry: int, bw: int, bh: int, r: int) -> int: if lry < r: if lx < r: return _is_in_corner(lx, lry, r, r, r) if lx >= bw - r: return _is_in_corner(lx, lry, bw - r - 1, r, r) elif lry >= bh - r: if lx < r: return _is_in_corner(lx, lry, r, bh - r - 1, r) if lx >= bw - r: return _is_in_corner(lx, lry, bw - r - 1, bh - r - 1, r) return 1 # 窗口本身是否允许画在该像素(圆角样式会裁掉四角) def _window_round_keep(self, s: desk.WinSlot*, x: int, y: int) -> int: if s.style != STYLE_WIN11 and s.style != STYLE_WIN7 and s.style != STYLE_WINXP: return 1 rx: int = x - s.ux ry: int = y - s.uy if rx < 0 or rx >= s.w or ry < 0 or ry >= s.h: return 0 return self._round_keep(rx, ry, s.w, s.h, WIN_RADIUS) # 标题栏按钮底色:按需做按钮圆角(r=0 即方角)+ 窗口圆角裁剪, # 保证按钮本身是方的,只有与窗口圆角重合的角被丢弃。 def _close_fill_row(self, s: desk.WinSlot*, bx: int, by: int, bw: int, bh: int, yy: int, col: uint32, r: int): lry: int = yy - by if lry < 0 or lry >= bh: return lo: int = 0 while lo < bw: if self._round_keep(lo, lry, bw, bh, r) != 0 and self._window_round_keep(s, bx + lo, yy) != 0: break lo += 1 hi: int = bw while hi > lo: if self._round_keep(hi - 1, lry, bw, bh, r) != 0 and self._window_round_keep(s, bx + hi - 1, yy) != 0: break hi -= 1 if hi > lo: self.row_fill(bx + lo, yy, hi - lo, col) def _draw_close_row(self, s: desk.WinSlot*, bx: int, by: int, bw: int, bh: int, yy: int, style: int, hovered: int, pressed: int): lry: int = yy - by if lry < 0 or lry >= bh: return cx: int = bx + bw / 2 cy: int = by + bh / 2 d: int = 3 if bw >= 30: d = 4 x_col: uint32 = self.rgb(uint32(200), uint32(200), uint32(200)) active: int = 1 if (hovered != 0 or pressed != 0) else 0 if style == STYLE_CLASSIC: base_col: uint32 = self.rgb(uint32(40), uint32(40), uint32(70)) if active != 0: base_col = self.rgb(uint32(200), uint32(50), uint32(50)) if pressed != 0: base_col = self.rgb(uint32(140), uint32(25), uint32(25)) self.row_fill(bx, yy, bw, base_col) if active != 0: x_col = self.rgb(uint32(255), uint32(255), uint32(255)) elif style == STYLE_WIN7 or style == STYLE_WINXP: base_col2: uint32 = self.rgb(uint32(200), uint32(50), uint32(50)) if hovered != 0: base_col2 = self.rgb(uint32(220), uint32(60), uint32(60)) if pressed != 0: base_col2 = self.rgb(uint32(140), uint32(25), uint32(25)) self._close_fill_row(s, bx, by, bw, bh, yy, base_col2, 0) x_col = self.rgb(uint32(255), uint32(255), uint32(255)) else: x_col = self.rgb(uint32(150), uint32(150), uint32(150)) # WIN10/WIN11 平时无底色;hover 变红、按下更深,图标保持白色 if active != 0: bg: uint32 = self.rgb(uint32(200), uint32(50), uint32(50)) if pressed != 0: bg = self.rgb(uint32(140), uint32(25), uint32(25)) if style == STYLE_WIN11: # 按钮本身保持方角;只丢弃窗口圆角轮廓之外的部分 self._close_fill_row(s, bx, by, bw, bh, yy, bg, 0) else: self.row_fill(bx, yy, bw, bg) x_col = self.rgb(uint32(255), uint32(255), uint32(255)) i: int = yy - cy if i >= 0 - d and i <= d: self.row_fill(cx + i, yy, 1, x_col) self.row_fill(cx - i, yy, 1, x_col) # 方框描边的一行(x0..x0+size,size 为半宽/边宽) def _draw_square_row(self, x0: int, y0: int, size: int, yy: int, col: uint32): if yy < y0 or yy > y0 + size: return if yy == y0 or yy == y0 + size: self.row_fill(x0, yy, size + 1, col) else: self.row_fill(x0, yy, 1, col) self.row_fill(x0 + size, yy, 1, col) def _draw_min_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int, hovered: int, pressed: int): if pressed != 0: self._dim_row(bx, yy, bw, 90) elif hovered != 0: self._dim_row(bx, yy, bw, 50) cx: int = bx + bw / 2 cy: int = by + bh / 2 lw: int = 5 if bw >= 30: lw = 8 col: uint32 = self.rgb(uint32(180), uint32(180), uint32(180)) if hovered != 0 or pressed != 0: col = self.rgb(uint32(255), uint32(255), uint32(255)) if yy == cy + 3: self.row_fill(cx - lw, yy, lw * 2 + 1, col) def _draw_max_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int, hovered: int, pressed: int, maximized: int): if pressed != 0: self._dim_row(bx, yy, bw, 90) elif hovered != 0: self._dim_row(bx, yy, bw, 50) cx: int = bx + bw / 2 cy: int = by + bh / 2 sz: int = 4 if bw >= 30: sz = 6 col: uint32 = self.rgb(uint32(180), uint32(180), uint32(180)) if hovered != 0 or pressed != 0: col = self.rgb(uint32(255), uint32(255), uint32(255)) if maximized != 0: # 还原图标:两个错位方框(右上在后,左下在前) self._draw_square_row(cx - 1, cy - sz, sz, yy, col) self._draw_square_row(cx - sz, cy - 1, sz, yy, col) else: # 单方框;上边框只画一行(不再加厚) self._draw_square_row(cx - sz, cy - sz, sz * 2, yy, col) # 关闭键宽/高/右内边距(按样式;poll_input 热区复用这三者) def _close_btn_w(self, style: int) -> int: if style == STYLE_WIN10 or style == STYLE_WIN11: return BTN_WIN_W if style == STYLE_WIN7: return BTN_W7_W if style == STYLE_WINXP: return BTN_XP_SZ return BTN_SIZE def _close_btn_h(self, style: int) -> int: if style == STYLE_WIN10 or style == STYLE_WIN11: return TITLE_H - 2 if style == STYLE_WIN7: return BTN_W7_H if style == STYLE_WINXP: return BTN_XP_SZ return BTN_SIZE def _close_btn_pad(self, style: int) -> int: if style == STYLE_WIN10 or style == STYLE_WIN11: return 0 if style == STYLE_WIN7 or style == STYLE_WINXP: return 5 return 4 # 关闭键命中:与 _draw_window_btns 共用同一套几何 def _close_btn_hit(self, s: desk.WinSlot*, x: int, y: int) -> int: cw: int = self._close_btn_w(s.style) ch: int = self._close_btn_h(s.style) bx: int = s.ux + s.w - cw - self._close_btn_pad(s.style) by: int = s.uy + (TITLE_H - ch) / 2 if x >= bx and x < bx + cw and y >= by and y < by + ch: return 1 return 0 # 标题栏按钮命中:1=关闭 2=最小化 3=最大化/还原(与绘制同一套几何) def _title_btn_hit(self, s: desk.WinSlot*, x: int, y: int) -> int: bw: int = self._close_btn_w(s.style) bh: int = self._close_btn_h(s.style) pad: int = self._close_btn_pad(s.style) gap: int = 0 if s.style == STYLE_WIN7 or s.style == STYLE_WINXP: gap = 1 by: int = s.uy + (TITLE_H - bh) / 2 if y < by or y >= by + bh: return 0 close_x: int = s.ux + s.w - bw - pad max_x: int = close_x - bw - gap min_x: int = max_x - bw - gap if x >= close_x and x < close_x + bw: return 1 if x >= max_x and x < max_x + bw: return 3 if x >= min_x and x < min_x + bw: return 2 return 0 def _draw_window_btns(self, s: desk.WinSlot*, y0: int, y1: int, th: int, hover_kind: int, pressed_kind: int): wx: int = s.ux wy: int = s.uy ww: int = s.w style: int = s.style bw: int = self._close_btn_w(style) bh: int = self._close_btn_h(style) by: int = wy + (th - bh) / 2 close_x: int = wx + ww - bw - self._close_btn_pad(style) gap: int = 0 if style == STYLE_WIN7 or style == STYLE_WINXP: gap = 1 max_x: int = close_x - bw - gap min_x: int = max_x - bw - gap yy: int = y0 if yy < by: yy = by yend: int = y1 if yend > by + bh: yend = by + bh while yy < yend: self._draw_close_row(s, close_x, by, bw, bh, yy, style, 1 if hover_kind == 1 else 0, 1 if pressed_kind == 1 else 0) self._draw_min_row(min_x, by, bw, bh, yy, style, 1 if hover_kind == 2 else 0, 1 if pressed_kind == 2 else 0) self._draw_max_row(max_x, by, bw, bh, yy, style, 1 if hover_kind == 3 else 0, 1 if pressed_kind == 3 else 0, 1 if s.maximized != 0 else 0) yy += 1 # ---------------- 任务栏 ---------------- # 第 ordinal 个图标的左边界;绘制与命中测试共用 def _taskbar_icon_x(self, ordinal: int) -> int: return 4 + TASKBAR_LOGO_SZ + 80 + ordinal * (TASKBAR_ICON_SZ + TASKBAR_ICON_PAD) # 图标行的顶边界;绘制与命中测试共用 def _taskbar_icon_y(self) -> int: return self.fb_h - TASKBAR_H + (TASKBAR_H - TASKBAR_ICON_SZ) / 2 # 任务栏图标命中:返回窗口下标(-1 = 未命中);筛选与 ordinal 顺序同绘制 def _taskbar_icon_hit(self, x: int, y: int) -> int: iy: int = self._taskbar_icon_y() if y < iy or y >= iy + TASKBAR_ICON_SZ: return -1 cnt: int = 0 for i in range(desk.DESK_MAX_WIN): s: desk.WinSlot* = self.slot(i) if s.used == 0 or s.active == 0: continue ix: int = self._taskbar_icon_x(cnt) if x >= ix and x < ix + TASKBAR_ICON_SZ: return i cnt += 1 return -1 # 点击图标:最小化先恢复;已聚焦则最小化(任务栏切换);否则聚焦 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) self.focus(win) self.mark_all_windows() self.mark_titles() elif self.focused == win: self._minimize_window(win) else: self.focus(win) def _draw_taskbar_rows(self, y0: int, y1: int): bar_y: int = self.fb_h - TASKBAR_H if y1 <= bar_y or y0 >= self.fb_h: return bar_col: uint32 = self.rgb(uint32(32), uint32(32), uint32(32)) yy: int = y0 if yy < bar_y: yy = bar_y while yy < y1 and yy < self.fb_h: self.row_fill(0, yy, self.fb_w, bar_col) yy += 1 # 顶部分隔线 if bar_y >= y0 and bar_y < y1: self.row_fill(0, bar_y, self.fb_w, self.rgb(uint32(60), uint32(60), uint32(60))) # logo:内亮外环的蓝色圆盘 logo_x: int = 4 logo_y: int = bar_y + (TASKBAR_H - TASKBAR_LOGO_SZ) / 2 lr: int = TASKBAR_LOGO_SZ / 2 lc_ring: uint32 = self.rgb(uint32(80), uint32(120), uint32(220)) lc_core: uint32 = self.rgb(uint32(100), uint32(160), uint32(255)) yy = y0 if yy < logo_y: yy = logo_y while yy < y1 and yy < logo_y + TASKBAR_LOGO_SZ: ly2: int = yy - logo_y - lr lx: int = 0 while lx < TASKBAR_LOGO_SZ: lx2: int = lx - lr d2: int = lx2 * lx2 + ly2 * ly2 if d2 <= lr * lr: px: int = logo_x + lx if px >= 0 and px < self.fb_w: if d2 <= (lr * lr) / 2: self.back[yy * self.fb_w + px] = lc_core else: self.back[yy * self.fb_w + px] = lc_ring lx += 1 yy += 1 # 品牌文字 text_y: int = bar_y + (TASKBAR_H - vpui.CHAR_H) / 2 if text_y + vpui.CHAR_H > y0 and text_y < y1: self.draw_str(logo_x + TASKBAR_LOGO_SZ + 8, text_y, "ViperOS", self.rgb(uint32(255), uint32(255), uint32(255))) # 窗口图标:有边框方块 + 标题首字符(焦点/最小化配色不同) icon_y: int = self._taskbar_icon_y() cnt: int = 0 for i in range(desk.DESK_MAX_WIN): s: desk.WinSlot* = self.slot(i) if s.used == 0 or s.active == 0: continue ix: int = self._taskbar_icon_x(cnt) ic_bg: uint32 = self.rgb(uint32(40), uint32(40), uint32(70)) ic_bd: uint32 = self.rgb(uint32(80), uint32(80), uint32(140)) if i == self.focused: ic_bg = self.rgb(uint32(60), uint32(60), uint32(120)) ic_bd = self.rgb(uint32(120), uint32(120), uint32(220)) elif s.minimized != 0: ic_bg = self.rgb(uint32(30), uint32(30), uint32(50)) yy = y0 if yy < icon_y: yy = icon_y while yy < y1 and yy < icon_y + TASKBAR_ICON_SZ: rel: int = yy - icon_y self.row_fill(ix, yy, TASKBAR_ICON_SZ, ic_bg) if rel == 0 or rel == TASKBAR_ICON_SZ - 1: self.row_fill(ix, yy, TASKBAR_ICON_SZ, ic_bd) else: self.row_fill(ix, yy, 1, ic_bd) self.row_fill(ix + TASKBAR_ICON_SZ - 1, yy, 1, ic_bd) yy += 1 if s.title[0] != 0: ch_col: uint32 = self.rgb(uint32(255), uint32(255), uint32(255)) if i != self.focused: ch_col = self.rgb(uint32(180), uint32(180), uint32(200)) ch_x: int = ix + (TASKBAR_ICON_SZ - vpui.CHAR_W) / 2 ch_y: int = icon_y + (TASKBAR_ICON_SZ - vpui.CHAR_H) / 2 if ch_y + vpui.CHAR_H > y0 and ch_y < y1: self.draw_char(ch_x, ch_y, s.title[0], ch_col) cnt += 1 # ---------------- 渲染 ---------------- def render_range(self, y0: int, y1: int): # 窗口按 Z 序升序(选择排序,窗口数 ≤ 8);顺带缓存几何/样式,避免逐行查槽 order: [desk.DESK_MAX_WIN]int sx: [desk.DESK_MAX_WIN]int sy: [desk.DESK_MAX_WIN]int sw: [desk.DESK_MAX_WIN]int sh: [desk.DESK_MAX_WIN]int smode: [desk.DESK_MAX_WIN]int cnt: int = 0 top_z: int = -1 for i in range(desk.DESK_MAX_WIN): s: desk.WinSlot* = self.slot(i) if s.used != 0 and s.active != 0 and s.minimized == 0: order[cnt] = i sx[i] = s.ux sy[i] = s.uy sw[i] = s.w sh[i] = s.h if s.style == STYLE_WIN7: smode[i] = 2 elif s.style == STYLE_WIN11 or s.style == STYLE_WINXP: smode[i] = 1 else: smode[i] = 0 if s.z_order > top_z: top_z = s.z_order cnt += 1 for a in range(cnt - 1): for b in range(a + 1, cnt): za: int = self.slot(order[a]).z_order zb: int = self.slot(order[b]).z_order if za > zb: t: int = order[a] order[a] = order[b] order[b] = t # 1) 背景:只铺"没有被窗口覆盖"的 x 区间;混合样式的可见行再单独铺一次底 yy: int = y0 while yy < y1: xx: int = 0 for i in range(cnt): w: int = order[i] if yy < sy[w] or yy >= sy[w] + sh[w]: continue if sx[w] > xx: self.row_blit(xx, yy, self.bg_row, xx, sx[w] - xx) ex: int = sx[w] + sw[w] if ex > xx: xx = ex if smode[w] != 0: rel: int = yy - sy[w] need: int = 0 if smode[w] == 2: if rel < TITLE_H or rel >= sh[w] - WIN_RADIUS: need = 1 elif rel < WIN_RADIUS or rel >= sh[w] - WIN_RADIUS: need = 1 if need != 0: self.row_blit(sx[w], yy, self.bg_row, sx[w], sw[w]) if xx < self.fb_w: self.row_blit(xx, yy, self.bg_row, xx, self.fb_w - xx) yy += 1 # 2) 窗口 for i in range(cnt): self.render_window_rows(order[i], y0, y1, top_z) # 3) 任务栏:最后画,始终盖在窗口之上(视觉层级高于所有窗口) self._draw_taskbar_rows(y0, y1) # 4) 光标 self.render_cursor_rows(y0, y1) def render_window_rows(self, win: int, y0: int, y1: int, top_z: int): s: desk.WinSlot* = self.slot(win) wx: int = s.ux wy: int = s.uy wh: int = s.h th: int = TITLE_H style: int = s.style active: int = 0 if s.z_order == top_z: active = 1 # 每样式调色板(对齐内核桌面 desktop.vp 的 _draw_app_window) title_col: uint32 = self.rgb(uint32(50), uint32(50), uint32(90)) border_col: uint32 = self.rgb(uint32(100), uint32(100), uint32(180)) outline_col: uint32 = self.rgb(uint32(100), uint32(100), uint32(180)) content_col: uint32 = self.rgb(uint32(30), uint32(30), uint32(50)) has_separator: int = 1 rounded: int = 0 title_alpha: int = 255 if style == STYLE_WIN10: has_separator = 0 if active != 0: title_col = self.rgb(uint32(0), uint32(0), uint32(0)) content_col = self.rgb(uint32(32), uint32(32), uint32(32)) outline_col = self.rgb(uint32(80), uint32(80), uint32(80)) else: title_col = self.rgb(uint32(32), uint32(32), uint32(32)) content_col = self.rgb(uint32(24), uint32(24), uint32(24)) outline_col = self.rgb(uint32(60), uint32(60), uint32(60)) border_col = title_col elif style == STYLE_WIN11: has_separator = 0 rounded = 1 if active != 0: title_col = self.rgb(uint32(32), uint32(32), uint32(32)) content_col = self.rgb(uint32(32), uint32(32), uint32(32)) outline_col = self.rgb(uint32(70), uint32(70), uint32(70)) else: title_col = self.rgb(uint32(40), uint32(40), uint32(40)) content_col = self.rgb(uint32(40), uint32(40), uint32(40)) outline_col = self.rgb(uint32(60), uint32(60), uint32(60)) border_col = title_col elif style == STYLE_WIN7: has_separator = 0 rounded = 1 title_alpha = 102 content_col = self.rgb(uint32(240), uint32(240), uint32(240)) if active != 0: title_col = self.rgb(uint32(156), uint32(181), uint32(209)) outline_col = self.rgb(uint32(100), uint32(130), uint32(170)) else: title_col = self.rgb(uint32(180), uint32(195), uint32(210)) outline_col = self.rgb(uint32(140), uint32(155), uint32(175)) border_col = title_col elif style == STYLE_WINXP: has_separator = 0 rounded = 1 content_col = self.rgb(uint32(235), uint32(235), uint32(235)) if active != 0: title_col = self.rgb(uint32(107), uint32(158), uint32(212)) outline_col = self.rgb(uint32(43), uint32(87), uint32(151)) else: title_col = self.rgb(uint32(140), uint32(160), uint32(195)) outline_col = self.rgb(uint32(100), uint32(115), uint32(145)) border_col = title_col else: if active != 0: title_col = self.rgb(uint32(70), uint32(70), uint32(130)) border_col = self.rgb(uint32(130), uint32(130), uint32(220)) outline_col = border_col # 阴影画在窗口本体之前(混到已合成的下层像素上),其行范围可能超出窗口外框 # 最大化窗口铺满工作区,阴影会压到任务栏上,直接跳过。 if SHADOW_ENABLED != 0 and s.maximized == 0: self._draw_shadow(s, y0, y1) if wy + wh <= y0 or wy >= y1: return buf: uint32* = uint32*(self.shm + uint64(s.fb_off)) fw: int = int(s.fb_w) fh: int = int(s.fb_h) # 槽表被写坏时宁可跳过该窗口,也不要按越界 fb_off 读像素把整个合成器带崩。 need_bytes: uint64 = uint64(fw) * uint64(fh) * uint64(4) if s.fb_off < uint32(desk.DESK_OFF_ARENA) or uint64(s.fb_off) + need_bytes > uint64(desk.DESK_SHM_SIZE): return content_y0: int = wy + th + TITLE_GAP r: int = WIN_RADIUS yy: int = y0 if yy < wy: yy = wy yend: int = y1 if yend > wy + wh: yend = wy + wh while yy < yend: rel: int = yy - wy row_is_title: int = 0 row_col: uint32 = content_col if rel < th: row_is_title = 1 row_col = self._title_row_color(style, rel, th, active, title_col) elif rel == th and has_separator != 0: row_col = border_col # 标题/内容分隔线(CLASSIC) if rounded != 0 and (rel < r or rel >= wh - r): self._draw_round_row(s, yy, rel, row_col, row_is_title, outline_col, r, title_alpha, buf, fw, fh, content_y0) else: self._draw_flat_row(s, yy, rel, row_col, row_is_title, border_col, title_alpha, buf, fw, fh, content_y0) yy += 1 pressed_kind: int = 0 if self.btn_win == win: pressed_kind = self.btn_kind hover_kind: int = 0 if self.hover_btn_win == win: hover_kind = self.hover_btn_kind self._draw_window_btns(s, y0, y1, th, hover_kind, pressed_kind) if s.title[0] != 0: text_y: int = wy + (th - vpui.CHAR_H) / 2 if text_y + vpui.CHAR_H > y0 and text_y < y1: tcol: uint32 = self.rgb(uint32(255), uint32(255), uint32(255)) if active == 0: tcol = self.rgb(uint32(170), uint32(170), uint32(170)) self.draw_str(wx + 8, text_y, &s.title[0], tcol) # 光标带置脏:光标模板可能有 -8 的居中偏移,统一按 26 行覆盖旧/新位置 def _mark_cursor_at(self, py: int): self.mark(py - 9, CURSOR_BMP_H + 10) def _mark_cursor_band(self): self._mark_cursor_at(self.cy) def _cursor_kind(self) -> int: # 缩放拖动期间锁定边缘光标 if self.resize_win >= 0: return self._edge_cursor(self.resize_edge) e: int = self._hover_resize_edge() if e != 0: return self._edge_cursor(e) hw: int = self.hit_test(self.cx, self.cy) if hw >= 0: s: desk.WinSlot* = self.slot(hw) if self.cy < s.uy + TITLE_H: return CURSOR_DEFAULT c: int = s.cursor if c < 0 or c > CURSOR_RESIZE_NESW: c = CURSOR_DEFAULT return c if self._taskbar_icon_hit(self.cx, self.cy) >= 0: return CURSOR_HAND return CURSOR_DEFAULT def _cursor_value(self, kind: int, idx: int) -> uint8: if kind == CURSOR_IBEAM: return _CURSOR_IBEAM[idx] if kind == CURSOR_HAND: return _CURSOR_HAND[idx] if kind == CURSOR_RESIZE_EW: return _CURSOR_EW[idx] if kind == CURSOR_RESIZE_NS: return _CURSOR_NS[idx] if kind == CURSOR_RESIZE_NWSE: return _CURSOR_NWSE[idx] if kind == CURSOR_RESIZE_NESW: return _CURSOR_NESW[idx] return _CURSOR_ARROW[idx] def render_cursor_rows(self, y0: int, y1: int): # 0 透明(不写像素,靠重画背景恢复),1 黑边,2 白心;同行连续同色合并成一次 row_fill。 kind: int = self._cursor_kind() dx: int = 0 dy: int = 0 if kind == CURSOR_HAND: dx = -7 elif kind != CURSOR_DEFAULT: # I 形与缩放光标的热点在形状中心 dx = -8 dy = -8 cy0: int = self.cy + dy cy1: int = cy0 + CURSOR_BMP_H if cy1 <= y0 or cy0 >= y1: return y: int = cy0 if y < y0: y = y0 while y < cy1 and y < y1: row: int = (y - cy0) * CURSOR_BMP_W col: int = 0 while col < CURSOR_BMP_W: v: uint8 = self._cursor_value(kind, row + col) if v == 0: col += 1 continue start: int = col col += 1 while col < CURSOR_BMP_W and self._cursor_value(kind, row + col) == v: col += 1 px: int = self.cx + dx + start seg_w: int = col - start if px + seg_w > 0 and px < self.fb_w: if px < 0: seg_w = seg_w + px px = 0 if px + seg_w > self.fb_w: seg_w = self.fb_w - px if seg_w > 0: if v == 1: self.row_fill(px, y, seg_w, self.rgb(uint32(0), uint32(0), uint32(0))) else: self.row_fill(px, y, seg_w, self.rgb(uint32(255), uint32(255), uint32(255))) y += 1 def render(self): if self.dirty_any == 0: return devfs.fb_wait_vblank(self.fb_fd) # 逐脏段渲染并立刻提交。**不要**合并成"最小脏行到最大脏行"的一整段:窗口在顶部、 # 任务栏在底部时那会退化成每帧全屏渲染 + 全屏提交(4MB memcpy),帧率直接崩。 # 段内先全画进 back 再逐行提交,保证每一行提交出去的都是完整一帧的内容。 y: int = 0 while y < self.fb_h: if self.dirty[y] == 0: y += 1 continue y0: int = y while y < self.fb_h and self.dirty[y] != 0: y += 1 y1: int = y self.render_range(y0, y1) yy: int = y0 while yy < y1: if self.dirty[yy] != 0: # fb 行距 = pitch_px,back 行距 = fb_w string.memcpy(rawptr(uint64(self.fb) + uint64(yy * self.pitch_px) * uint64(4)), rawptr(uint64(self.back) + uint64(yy * self.fb_w) * uint64(4)), uint64(self.fb_w) * uint64(4)) yy += 1 self.clear_dirty() # ---------------- 事件投递 ---------------- def push_event(self, win: int, etype: uint32, a: int, b: int, c: int, d: int) -> int: """按收件窗口的 pid 投进该进程自己的事件环。a..d 是消息的 4 个载荷位。""" s: desk.WinSlot* = self.slot(win) self.evt.type = etype self.evt.win = win self.evt.pid = s.pid self.evt.a = a self.evt.b = b self.evt.c = c self.evt.d = d return desk.ring_push(self.shm + desk.desk_evt_ring_off(s.pid), self.evt) def push_event_pid(self, pid: int, win: int, etype: uint32, a: int, b: int, c: int, d: int) -> int: """建窗答复专用:此时窗口槽还没有 pid,直接按请求里带的 pid 投递。""" self.evt.type = etype self.evt.win = win self.evt.pid = pid self.evt.a = a self.evt.b = b self.evt.c = c self.evt.d = d return desk.ring_push(self.shm + desk.desk_evt_ring_off(pid), self.evt) # ---------------- 请求处理 ---------------- def handle_request(self, m: desk.DeskMsg): global _flush_cnt if m.type == uint32(desk.DESK_REQ_CREATE): # pid 随请求携带(窗口还没诞生),标题内联在消息里 _sb: [96]char viperlib.snprintf(&_sb, 96, "[explorer] create style=%d w=%d h=%d\n", int(m.style), m.c, m.d) devfs.serial_puts(&_sb) nw: int = self.alloc_window(m.pid, m.a, m.b, m.c, m.d, int(m.style)) if nw >= 0: ns: desk.WinSlot* = self.slot(nw) string.memcpy(&ns.title, &m.title, uint64(desk.DESK_TITLE_MAX)) # 【临时诊断】分配成功后立刻回读槽位:坐标/尺寸/缓冲偏移是否真的落进去了。 _sb2: [160]char viperlib.snprintf(&_sb2, 160, "[explorer] slot%d x=%d y=%d w=%d h=%d off=%u\n", nw, ns.ux, ns.uy, ns.w, ns.h, ns.fb_off) devfs.serial_puts(&_sb2) else: # 【临时诊断】建窗失败(多半是 arena 分配不到)会让 app 拿到 win_id=-1 devfs.serial_puts("[explorer] alloc_window FAILED\n") self.mark_all_windows() self.mark_titles() self.push_event_pid(m.pid, nw, uint32(desk.DESK_EVT_CREATED), nw, 0, 0, 0) return win: int = m.win if win < 0 or win >= desk.DESK_MAX_WIN: return s: desk.WinSlot* = self.slot(win) if s.used == 0: return ty: uint32 = m.type if ty == uint32(desk.DESK_REQ_DESTROY): self._close_window(win) return elif ty == uint32(desk.DESK_REQ_MOVE): self.mark_win(s.uy, s.h) s.ux = m.a s.uy = m.b self.mark_win(s.uy, s.h) elif ty == uint32(desk.DESK_REQ_RESIZE): self.mark_win(s.uy, s.h) if self._set_window_rect(s, s.ux, s.uy, m.a, m.b) != 0: s.maximized = 0 self.mark_win(s.uy, s.h) elif ty == uint32(desk.DESK_REQ_TITLE): string.memcpy(&s.title, &m.title, uint64(desk.DESK_TITLE_MAX)) self.mark(s.uy, TITLE_H) elif ty == uint32(desk.DESK_REQ_CURSOR): if s.cursor != m.a: s.cursor = m.a # 光标正压在这个窗口上:立即按新形状重画光标带 if self.hit_test(self.cx, self.cy) == win: self._mark_cursor_band() elif ty == uint32(desk.DESK_REQ_FLUSH): # 【临时诊断】首条 FLUSH:证明 app 的"请合成"真的到达了合成器 if _flush_cnt == 0: _fb1: [128]char viperlib.snprintf(&_fb1, 128, "[explorer] first FLUSH win=%d uy=%d h=%d\n", win, s.uy, s.h) devfs.serial_puts(&_fb1) _flush_cnt += 1 # 最小化窗口仍在后台绘制/FLUSH:不要置脏,否则合成器每帧都要重画背景。 if s.minimized == 0: self.mark(s.uy, s.h) elif ty == uint32(desk.DESK_REQ_ACTIVATE): self.next_z += 1 s.z_order = self.next_z self.focused = win self.mark(0, self.fb_h) def poll_requests(self): # 单轮最多处理一整圈: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: # 任务栏层级最高:覆盖区域内不接受窗口命中(图标命中由 _taskbar_icon_hit 单独处理) if y >= self.fb_h - TASKBAR_H: return -1 best: int = -1 best_z: int = -1 for i in range(desk.DESK_MAX_WIN): s: desk.WinSlot* = self.slot(i) if s.used != 0 and s.active != 0 and s.minimized == 0: if x >= s.ux and x < s.ux + s.w and y >= s.uy and y < s.uy + s.h: if s.z_order > best_z: best_z = s.z_order best = i return best def focus(self, win: int): # 只允许聚焦"活"的槽位:死槽位上的 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 # 层级变化会改变互相覆盖的关系:所有可见窗口整体置脏 self.mark_all_windows() self.mark_titles() def _move_cursor(self, dx: int, dy: int) -> int: """把相对位移累加到光标并钳制到屏幕内;返回是否发生移动。""" if dx == 0 and dy == 0: return 0 self.cx = self.cx + dx self.cy = self.cy + dy if self.cx < 0: self.cx = 0 if self.cy < 0: self.cy = 0 if self.cx >= self.fb_w: self.cx = self.fb_w - 1 if self.cy >= self.fb_h: self.cy = self.fb_h - 1 return 1 # 把 kb_pend 里的字节投给焦点窗口,返回本轮实际投递数。 # ring_push 失败 = 目标事件环满:停在当前字节,下一轮再投,字节绝不丢。 def _deliver_kb(self, limit: int) -> int: target: int = self._key_target() if target < 0: # 没有收件窗口:无处投递,只能丢弃(保持旧行为,不让 pending 无限增长)。 self.kb_pend_off = 0 self.kb_pend_len = 0 return 0 sent: int = 0 while self.kb_pend_len > 0 and sent < limit: b: char = self.kb_pend[self.kb_pend_off] if self.push_event(target, uint32(desk.DESK_EVT_KEY), int(b), 0, 0, 0) != 0: break self.kb_pend_off += 1 self.kb_pend_len -= 1 sent += 1 if self.kb_pend_len == 0: self.kb_pend_off = 0 return sent def poll_input(self): global _mouse_logged, _kbd_logged # --- 键盘:设备侧已给出 ASCII,逐字符投给焦点窗口(无焦点/失效则顶层窗口)--- # 单轮预算内的投递 + 一次补读:洪流时一轮只推进最多 KB_DELIVER_MAX 字节, # 其余留在 kb_pend,保证请求/输入/渲染仍能每轮推进,不会被一次洪流拖死。 sent: int = self._deliver_kb(KB_DELIVER_MAX) if sent < KB_DELIVER_MAX and self.kb_pend_len == 0: kb: [16]char kn: int = fs.read(self.kfd, &kb, uint32(16)) if kn > 0: # 【诊断·临时】首个键盘字节:证明 内核 → devfs → explorer 这段是通的 if _kbd_logged == 0: _kbd_logged = 1 _kb: [96]char viperlib.snprintf(&_kb, 96, "[explorer] kbd n=%d ch=%d\n", kn, int(kb[0])) devfs.serial_puts(&_kb) if kn > 16: kn = 16 for i in range(kn): self.kb_pend[i] = kb[i] self.kb_pend_off = 0 self.kb_pend_len = kn self._deliver_kb(KB_DELIVER_MAX - sent) # --- 鼠标:内核每次返回一个包。serve 一轮耗时毫秒级,若每轮只取一个包, # PS/2 100Hz 的连续输入会排队累积 ⇒ 光标滞后;故把已到的包一次取完, # 位移累加、按键以最后一个包为准。--- mp: [16]uint8 mn: int = fs.read(self.mfd, &mp, uint32(16)) if mn > 0: # 【诊断·临时】首个鼠标包:证明 中断→devfs→本进程 链路已通 if _mouse_logged == 0: _mouse_logged = 1 _mb: [128]char viperlib.snprintf(&_mb, 128, "[explorer] mouse pkt n=%d btn=%d\n", mn, int(mp[0])) devfs.serial_puts(&_mb) moved: int = 0 if mn >= 9: moved = self._move_cursor(int(*(int32*(uint64(&mp) + uint64(1)))), int(*(int32*(uint64(&mp) + uint64(5))))) # 批次内逐包跟踪按下/释放沿:只看最后一包会漏掉"按下+释放落在同一批"的快速点击 cur: int = self.btn_down saw_press: int = 0 b0: int = int(mp[0]) & 1 if b0 != 0 and cur == 0: saw_press = 1 cur = b0 mn = fs.read(self.mfd, &mp, uint32(16)) # 单轮也有上限:剩下的包留到下一轮,不能被输入洪流卡在本轮。 batches: int = 0 while mn > 0 and batches < 64: batches += 1 if mn >= 9: if self._move_cursor(int(*(int32*(uint64(&mp) + uint64(1)))), int(*(int32*(uint64(&mp) + uint64(5))))) != 0: moved = 1 bi: int = int(mp[0]) & 1 if bi != 0 and cur == 0: saw_press = 1 cur = bi mn = fs.read(self.mfd, &mp, uint32(16)) if moved != 0: self._mark_cursor_at(self.pcy) self._mark_cursor_at(self.cy) self.pcx = self.cx self.pcy = self.cy # 按住标题栏按钮时移出热区:撤销按下态并置脏恢复 if self.btn_win >= 0: cm: desk.WinSlot* = self.slot(self.btn_win) if cm.used == 0 or self._title_btn_hit(cm, self.cx, self.cy) != self.btn_kind: if cm.used != 0: self.mark(cm.uy, TITLE_H) self.btn_win = -1 self.btn_kind = 0 if self.btn_down != 0 and self.capture_win >= 0: # 内容区按住期间:移动事件持续投给捕获窗口。滚动条/滑块拖动 # 依赖按住时的 HOVER;坐标允许越界,由控件自行判断。 cs: desk.WinSlot* = self.slot(self.capture_win) if cs.used != 0: self.push_event(self.capture_win, uint32(desk.DESK_EVT_MOUSE), desk.DESK_MOUSE_HOVER, self.cx - cs.ux, self.cy - cs.uy - TITLE_H - TITLE_GAP, 0) elif self.btn_down == 0: # 悬停:把 HOVER 投给光标下那个窗口(窗口内坐标) hw: int = self.hit_test(self.cx, self.cy) if hw >= 0: hs: desk.WinSlot* = self.slot(hw) self.push_event(hw, uint32(desk.DESK_EVT_MOUSE), desk.DESK_MOUSE_HOVER, self.cx - hs.ux, self.cy - hs.uy - TITLE_H - TITLE_GAP, 0) if saw_press != 0: self.btn_down = 1 self.btn_win = -1 # 新手势开始:清掉上一次可能丢失释放沿留下的按下态 self.btn_kind = 0 self.capture_win = -1 # 任务栏图标优先:命中则激活并跳过窗口命中/拖动 hit: int = -1 tw: int = self._taskbar_icon_hit(self.cx, self.cy) if tw >= 0: self._activate_from_taskbar(tw) else: hit = self.hit_test(self.cx, self.cy) if hit >= 0: s: desk.WinSlot* = self.slot(hit) edge: int = self._resize_edge_at(s, self.cx, self.cy) kind: int = self._title_btn_hit(s, self.cx, self.cy) self.focus(hit) if edge != 0: # 窗口边缘 ⇒ 拖拽缩放 self.resize_win = hit self.resize_edge = edge self.resize_ox = s.ux self.resize_oy = s.uy self.resize_ow = s.w self.resize_oh = s.h self.resize_sx = self.cx self.resize_sy = self.cy elif kind != 0: # 标题栏按钮:只进入按下态,松手且仍在同一按钮上才执行 self.btn_win = hit self.btn_kind = kind self.mark(s.uy, TITLE_H) elif self.cy < s.uy + TITLE_H: # 标题栏 ⇒ 拖动;最大化窗口先还原再拖 restored_ok: int = 1 if s.maximized != 0: restored_ok = self._restore_window(hit) s = self.slot(hit) if restored_ok != 0: self.drag_win = hit self.drag_dx = self.cx - s.ux self.drag_dy = self.cy - s.uy else: # 内容区 ⇒ 捕获鼠标:按下后的移动/松开都投给同一窗口 self.capture_win = hit self.push_event(hit, uint32(desk.DESK_EVT_MOUSE), desk.DESK_MOUSE_PRESS, self.cx - s.ux, self.cy - s.uy - TITLE_H - TITLE_GAP, 1) # 独立判断(不是 elif):同批内按下并松开时,按下分支之后还要立刻走释放, # 否则 btn_down 卡在 1,后续点击永远检测不到按下沿。 if cur == 0 and self.btn_down != 0: self.btn_down = 0 bw: int = self.btn_win bk: int = self.btn_kind self.btn_win = -1 self.btn_kind = 0 if bw >= 0: bs: desk.WinSlot* = self.slot(bw) if bs.used != 0: self.mark(bs.uy, TITLE_H) if self._title_btn_hit(bs, self.cx, self.cy) == bk: if bk == 1: self._close_window(bw) elif bk == 2: self._minimize_window(bw) elif bk == 3: self._toggle_maximize(bw) elif self.resize_win >= 0: # 缩放结束:补发一次最终 RESIZE,防止拖动期间事件环满导致 app 停在旧尺寸。 rs: desk.WinSlot* = self.slot(self.resize_win) if rs.used != 0: self.push_event(self.resize_win, uint32(desk.DESK_EVT_RESIZE), int(rs.fb_w), int(rs.fb_h), 0, 0) self.mark_win(rs.uy, rs.h) self.resize_win = -1 self._mark_cursor_band() elif self.drag_win < 0 and self.capture_win >= 0: # 松手:投给捕获窗口(即使光标已移出窗口) cs: desk.WinSlot* = self.slot(self.capture_win) if cs.used != 0: self.push_event(self.capture_win, uint32(desk.DESK_EVT_MOUSE), desk.DESK_MOUSE_RELEASE, self.cx - cs.ux, self.cy - cs.uy - TITLE_H - TITLE_GAP, 0) self.drag_win = -1 self.capture_win = -1 if self.btn_down != 0 and self.resize_win >= 0: self._drag_resize() elif self.btn_down != 0 and self.drag_win >= 0: s2: desk.WinSlot* = self.slot(self.drag_win) if s2.used != 0: self.mark_win(s2.uy, s2.h) s2.ux = self.cx - self.drag_dx s2.uy = self.cy - self.drag_dy self.mark_win(s2.uy, s2.h) self._update_btn_hover() # 标题栏按钮 hover 变化时只重画相关标题栏(用于变色反馈) def _update_btn_hover(self): nw: int = -1 nk: int = 0 if self.btn_down == 0: hw: int = self.hit_test(self.cx, self.cy) if hw >= 0: s: desk.WinSlot* = self.slot(hw) if self.cy < s.uy + TITLE_H: k: int = self._title_btn_hit(s, self.cx, self.cy) if k != 0: nw = hw nk = k if nw == self.hover_btn_win and nk == self.hover_btn_kind: return if self.hover_btn_win >= 0: os: desk.WinSlot* = self.slot(self.hover_btn_win) if os.used != 0: self.mark(os.uy, TITLE_H) if nw >= 0: ns: desk.WinSlot* = self.slot(nw) self.mark(ns.uy, TITLE_H) self.hover_btn_win = nw self.hover_btn_kind = nk # ---------------- 主循环 ---------------- def serve(self): # 进主循环前先整屏刷一次:内核启动期的 VGA/gfx 测试内容(例如蓝色测试块)不在任何 # 脏行里,只靠后续增量渲染会永远留在屏幕上。 self.mark_all() self.render() # 【临时诊断】首帧结果:back[0] 是"合成出来的像素",fb[0] 是"提交到屏幕的像素"。 # 两者都等于背景色 ⇒ 通路正常(问题在别处);back 有值而 fb 为 0 ⇒ 提交路径断了。 _f1: [176]char # 额外取一个任务栏行上的像素(底边往上 8 行):期望 0xff202020(任务栏底色 32,32,32)。 # 它能证明"首帧确实把任务栏也提交到了 fb",从而把问题限定在"窗口合成"或"显示侧"。 _tb_y: int = self.fb_h - 8 _tb_px: uint32 = self.fb[_tb_y * self.pitch_px + 2] viperlib.snprintf(&_f1, 176, "[explorer] frame1 back0=0x%x fb0=0x%x tb=0x%x w=%d h=%d pitch_px=%d\n", self.back[0], self.fb[0], _tb_px, self.fb_w, self.fb_h, self.pitch_px) devfs.serial_puts(&_f1) _fc: int = 0 # 【临时诊断】帧计数 while True: self.poll_requests() self.poll_input() self.render() # 【临时诊断】每 240 帧打一次:槽位数、最上层窗口的矩形/缓冲偏移,以及该窗口中心 # 已合成出来的像素(用来判断"窗口到底有没有被合成")。 _fc = _fc + 1 if (_fc % 240) == 0: # 【临时诊断】统计槽位 + 最上层窗口的矩形/缓冲偏移,并取窗口中心那个已合成像素。 _uc: int = 0 _tz: int = -1 _tw: int = -1 for _i in range(desk.DESK_MAX_WIN): _s: desk.WinSlot* = self.slot(_i) if _s.used != 0: _uc += 1 if _s.used != 0 and _s.active != 0 and _s.minimized == 0 and _s.z_order > _tz: _tz = _s.z_order _tw = _i _px: uint32 = uint32(0) _spx: uint32 = uint32(0) _wx: int = -1 _wy: int = -1 _ww: int = -1 _wh: int = -1 _woff: uint32 = uint32(0) if _tw >= 0: _ws: desk.WinSlot* = self.slot(_tw) _wx = _ws.ux; _wy = _ws.uy; _ww = _ws.w; _wh = _ws.h; _woff = _ws.fb_off _cxp: int = _wx + _ww / 2 _cyp: int = _wy + _wh / 2 if _cxp >= 0 and _cxp < self.fb_w and _cyp >= 0 and _cyp < self.fb_h: _px = self.back[_cyp * self.fb_w + _cxp] # 共享区里该窗口缓冲的首像素:app 到底有没有往这块内存里画 _sbuf: uint32* = uint32*(self.shm + uint64(_woff)) _spx = _sbuf[0] _f2: [256]char viperlib.snprintf(&_f2, 256, "[explorer] frame%d dirty=%d used=%d top=%d rect=%d,%d,%d,%d off=%u px=0x%x shm0=0x%x flush=%d\n", _fc, self.dirty_any, _uc, _tw, _wx, _wy, _ww, _wh, _woff, _px, _spx, _flush_cnt) devfs.serial_puts(&_f2) process.yield_cpu()