From 16337695347de5cc4019c97cf1ca04f49258b411 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 16 Oct 2026 18:56:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(explorer):=20S3c=20=E2=80=94=E2=80=94=20ex?= =?UTF-8?q?plorer=20=E7=AA=97=E5=8F=A3=E6=9C=8D=E5=8A=A1=E7=AB=AF=E4=B8=8E?= =?UTF-8?q?=E5=90=88=E6=88=90=E5=99=A8=EF=BC=88=E8=AF=B7=E6=B1=82=E7=8E=AF?= =?UTF-8?q?=20+=20=E8=BE=93=E5=85=A5=20+=20=E9=80=90=E8=A1=8C=E5=90=88?= =?UTF-8?q?=E6=88=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apps/Explorer/desk_server.vp(新增):窗口槽分配(含 arena 像素缓冲切分与底色预清)、Z 序与焦点、逐脏行合成(背景整行 memcpy → 各窗口按 Z 序只重画落在该行的那一段 → 光标)、请求处理(CREATE/DESTROY/MOVE/RESIZE/FLUSH/ACTIVATE)、事件投递(CREATED/CLOSE/KEY)、输入轮询(读 /dev/input/keyboard 的 ASCII 与 /dev/input/mouse 的按键+x/y,做命中测试、点击聚焦、标题栏右侧关闭按钮发 CLOSE、按住拖动移窗)。 main.vp:fb/desk/输入三者就绪后交给 DeskServer.serve() 主循环;去掉 S2 的渐变条演示(像素通路已由 fb 映射日志与合成器本身证明)。desk.vp:删掉写错且无人使用 的 DESK_MSG_SIZE 常量。 注意:本切片尚未实现标题文字(需用户态字体表)、任务栏、最小化/缩放、窗口侧边框与样式;且此过渡态下内核 desktop 与 explorer 同时在画屏幕,会互相覆盖 —— S3d(app 改走协议)与 S3e(删内核 UI)收口。 --- Apps/Explorer/desk_server.vp | 391 +++++++++++++++++++++++++++++++++++ Apps/Explorer/main.vp | 30 +-- vpsdk/desk.vp | 1 - 3 files changed, 399 insertions(+), 23 deletions(-) create mode 100644 Apps/Explorer/desk_server.vp diff --git a/Apps/Explorer/desk_server.vp b/Apps/Explorer/desk_server.vp new file mode 100644 index 0000000..2bc3ea6 --- /dev/null +++ b/Apps/Explorer/desk_server.vp @@ -0,0 +1,391 @@ +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 +from stdint import * +import string + + +# ============================================================================ +# explorer 的窗口服务端 + 合成器(S3c)。 +# +# 数据面:app 把窗口像素写进共享区 arena 里自己那块,压一条 DESK_REQ_FLUSH; +# 本服务端合成时按 Z 序把它们逐行 memcpy 进 framebuffer(不搬像素)。 +# 控制面:请求环(app → explorer)与每窗口事件环(explorer → app)。 +# 输入:读数从 /dev/input/keyboard、/dev/input/mouse(内核 devfs 设备已完成 +# 扫描码→ASCII 与鼠标包解码),本服务端负责光标、命中测试、焦点与事件投递。 +# +# 本切片未做(后续):标题文字(要用户态字体表)、最小化/拖拽/缩放、任务栏、 +# 窗口侧边框、样式区分、心跳无响应检测。 +# ============================================================================ + +define TITLE_H = 22 +define CLOSE_SZ = 14 +define CLOSE_PAD = 5 +define CURSOR_SZ = 10 + + +class DeskServer: + # 屏幕 + fb: uint32* # /dev/fb0 映射后的线性地址 + fb_w: int + fb_h: int + pitch_px: int + fmt: uint32 # 0 = RGBA, 1 = BGRA(与内核 gfx 口径一致) + bg_row: uint32* # 一行背景的预构造副本(避免逐像素填) + # 共享区 + shm: uint64 + # 输入 + kfd: int + mfd: int + # 脏行 + dirty: uint8* # 每行 1 字节 + dirty_any: int + # 光标 / 焦点 / 拖拽 + cx: int + cy: int + pcx: int + pcy: int + focused: int + next_z: int + btn_down: int + drag_win: int + drag_dx: int + drag_dy: 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 + + def init(self, fb: uint32*, w: int, h: int, pitch: int, fmt: uint32, + shm: uint64, kfd: int, mfd: int): + self.fb = fb + 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 + # 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.drag_win = -1 + self.drag_dx = 0 + self.drag_dy = 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(18), uint32(24), uint32(32)) + for i in range(w): + self.bg_row[i] = col + 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 row_fill(self, x0: int, y: int, w: int, col: uint32): + if y < 0 or y >= self.fb_h: return + base: int = y * self.pitch_px + x: int = x0 + while x < x0 + w: + if x >= 0 and x < self.fb_w: + self.fb[base + x] = col + 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.fb) + uint64(y * self.pitch_px + 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 alloc_window(self, pid: int, x: int, y: int, w: int, h: int, style: int) -> int: + """找一个空槽并分配像素缓冲。返回 win_id,失败 -1。""" + for i in range(desk.DESK_MAX_WIN): + s: desk.WinSlot* = self.slot(i) + if s.used == 0: + buf_off: uint32 = desk.desk_arena_alloc(self.shm, uint32(w) * uint32(h) * uint32(4)) + if buf_off == uint32(0): return -1 + s.used = 1 + s.pid = pid + s.active = 1 + s.minimized = 0 + s.ux = x + s.uy = y + s.w = w + s.h = h + s.style = style + s.cursor = 0 + s.fb_off = buf_off + s.fb_w = uint32(w) + s.fb_h = uint32(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(w * h): + buf[k] = col + s.z_order = self.next_z + self.next_z += 1 + self.focused = i + self.mark(y, h) + return i + return -1 + + # ---------------- 渲染 ---------------- + def render_range(self, y0: int, y1: int): + # 1) 背景 + for yy in range(y0, y1): + self.row_blit(0, yy, self.bg_row, 0, self.fb_w) + # 2) 窗口按 Z 序升序(选择排序,窗口数 ≤ 8) + order: [desk.DESK_MAX_WIN]int + cnt: int = 0 + 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 + 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 + # 3) 逐个窗口:只重画落在 [y0,y1) 的那几行 + for i in range(cnt): + self.render_window_rows(order[i], y0, y1) + # 4) 光标 + self.render_cursor_rows(y0, y1) + + def render_window_rows(self, win: int, y0: int, y1: int): + s: desk.WinSlot* = self.slot(win) + x: int = s.ux + w: int = s.w + for yy in range(y0, y1): + if yy < s.uy or yy >= s.uy + s.h: continue + rel: int = yy - s.uy + if rel < TITLE_H: + self.row_fill(x, yy, w, self.rgb(uint32(52), uint32(78), uint32(120))) + # 关闭按钮(红块)落在这一行就画上 + bx: int = x + w - CLOSE_SZ - CLOSE_PAD + by: int = s.uy + (TITLE_H - CLOSE_SZ) / 2 + if yy >= by and yy < by + CLOSE_SZ: + self.row_fill(bx, yy, CLOSE_SZ, self.rgb(uint32(210), uint32(70), uint32(70))) + else: + cy: int = rel - TITLE_H + cw: int = int(s.fb_w) + if cw > w: cw = w + if cy < int(s.fb_h): + buf: uint32* = uint32*(self.shm + uint64(s.fb_off)) + self.row_blit(x, yy, buf, cy * int(s.fb_w), cw) + + def render_cursor_rows(self, y0: int, y1: int): + if self.cy + CURSOR_SZ <= y0 or self.cy >= y1: return + self.row_fill(self.cx, self.cy, CURSOR_SZ, self.rgb(uint32(255), uint32(255), uint32(255))) + # 空心效果:中间挖成黑,形状像一个小方框光标 + for yy in range(self.cy + 2, self.cy + CURSOR_SZ - 2): + self.row_fill(self.cx + 2, yy, CURSOR_SZ - 4, self.rgb(uint32(0), uint32(0), uint32(0))) + + def render(self): + if self.dirty_any == 0: return + 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 + self.render_range(y0, y) + self.clear_dirty() + + # ---------------- 事件投递 ---------------- + def push_event(self, win: int, etype: uint32, a: int, b: int, c: int) -> int: + self.evt.type = etype + self.evt.win = win + self.evt.a = a + self.evt.b = b + self.evt.c = c + self.evt.d = 0 + return desk.ring_push(self.shm + desk.desk_evt_ring_off(win), self.evt) + + # ---------------- 请求处理 ---------------- + def handle_request(self, m: desk.DeskMsg): + if m.type == uint32(desk.DESK_REQ_CREATE): + pid: int = m.win + nw: int = self.alloc_window(pid, m.a, m.b, m.c, m.d, 0) + self.push_event(pid, uint32(desk.DESK_EVT_CREATED), nw, 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.mark(s.uy, s.h) + s.used = 0 + if self.focused == win: self.focused = -1 + elif ty == uint32(desk.DESK_REQ_MOVE): + self.mark(s.uy, s.h) + s.ux = m.a + s.uy = m.b + self.mark(s.uy, s.h) + elif ty == uint32(desk.DESK_REQ_RESIZE): + self.mark(s.uy, s.h) + s.w = m.a + s.h = m.b + self.mark(s.uy, s.h) + elif ty == uint32(desk.DESK_REQ_FLUSH): + 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): + while desk.ring_pop(self.shm + desk.desk_req_ring_off(), &self.msg) != 0: + self.handle_request(self.msg) + + # ---------------- 输入 ---------------- + def hit_test(self, x: int, y: int) -> int: + 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): + if win < 0: return + s: desk.WinSlot* = self.slot(win) + self.next_z += 1 + s.z_order = self.next_z + self.focused = win + self.mark(s.uy, s.h) + + def poll_input(self): + # --- 键盘:设备侧已给出 ASCII,逐字符投给焦点窗口 --- + kb: [16]char + kn: int = fs.read(self.kfd, &kb, uint32(16)) + if kn > 0: + if self.focused >= 0: + i: int = 0 + while i < kn: + self.push_event(self.focused, uint32(desk.DESK_EVT_KEY), int(kb[i]), 0, 0) + i += 1 + # --- 鼠标:设备 read 返回 1 字节按键(size>=9 时另带 x/y)--- + mp: [16]uint8 + mn: int = fs.read(self.mfd, &mp, uint32(16)) + if mn > 0: + moved: int = 0 + if mn >= 9: + nx: int = int(*(int32*(uint64(&mp) + uint64(1)))) + ny: int = int(*(int32*(uint64(&mp) + uint64(5)))) + if nx != self.cx or ny != self.cy: + moved = 1 + self.cx = nx + self.cy = ny + 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 + btn: int = int(mp[0]) & 1 + if moved != 0: + self.mark(self.pcy - 1, CURSOR_SZ + 2) + self.mark(self.cy - 1, CURSOR_SZ + 2) + self.pcx = self.cx + self.pcy = self.cy + if btn != 0 and self.btn_down == 0: + self.btn_down = 1 + hit: int = self.hit_test(self.cx, self.cy) + if hit >= 0: + s: desk.WinSlot* = self.slot(hit) + # 标题栏右侧 = 关闭按钮 ⇒ 通知 app 退出 + bx: int = s.ux + s.w - CLOSE_SZ - CLOSE_PAD + by: int = s.uy + (TITLE_H - CLOSE_SZ) / 2 + in_close: int = 0 + if self.cx >= bx and self.cx < bx + CLOSE_SZ and self.cy >= by and self.cy < by + CLOSE_SZ: + in_close = 1 + self.focus(hit) + if in_close != 0: + self.push_event(hit, uint32(desk.DESK_EVT_CLOSE), 0, 0, 0) + else: + self.drag_win = hit + self.drag_dx = self.cx - s.ux + self.drag_dy = self.cy - s.uy + elif btn == 0 and self.btn_down != 0: + self.btn_down = 0 + self.drag_win = -1 + elif self.btn_down != 0 and self.drag_win >= 0: + s2: desk.WinSlot* = self.slot(self.drag_win) + if s2.used != 0: + self.mark(s2.uy, s2.h) + s2.ux = self.cx - self.drag_dx + s2.uy = self.cy - self.drag_dy + self.mark(s2.uy, s2.h + TITLE_H) + + # ---------------- 主循环 ---------------- + def serve(self): + while True: + self.poll_requests() + self.poll_input() + self.render() + process.yield_cpu() diff --git a/Apps/Explorer/main.vp b/Apps/Explorer/main.vp index b4fed7c..27d99ec 100644 --- a/Apps/Explorer/main.vp +++ b/Apps/Explorer/main.vp @@ -4,6 +4,7 @@ import vpsdk.process as process import vpsdk.syscall as syscall from stdint import * import viperlib +import desk_server def _dbg(msg: const str): @@ -38,25 +39,8 @@ export def _start() -> int: viperlib.snprintf(&buf2, 96, "[explorer] fb mapped at 0x%lx\n", fb_va) _dbg(&buf2) - # 2) 等桌面把首屏全量重绘做完(否则刚写的像素会被 _mark_all_dirty 的首次重绘盖掉)。 - # 这里没有时钟可用,就空转 yield:yield_cpu 约每 10ms 真让出一次,200 次约 2 秒。 - for _i in range(200): - process.yield_cpu() - - # 3) 画一条横贯屏幕的渐变条 —— 证明"用户态直接写 framebuffer 真的上屏"。 - w: int = int(info.width) - h: int = int(info.height) - pitch_px: int = int(info.pitch) / 4 - pixels: uint32* = uint32*(fb_va) - y0: int = h / 3 - y1: int = y0 + h / 6 - for y in range(y0, y1): - row: int = y * pitch_px - for x in range(w): - rr: int = (x * 255) / w - bb: int = 255 - rr - pixels[row + x] = uint32((rr << 16) | (0x78 << 8) | bb) - _dbg("[explorer] painted gradient band\n") + # 2) 屏幕几何/映射地址已打印(上面两行),像素通路由此验证;真正的绘制在服务端。 + fb: uint32* = uint32*(fb_va) # 4) 输入设备可达性:能 open 到就说明 /dev/* 通路已打通 # (真正读事件、接管窗口合成放到 S3 —— 内核桌面此刻还在抢同一个 PS/2 口)。 @@ -86,7 +70,9 @@ export def _start() -> int: shm, hdr.ready, uint64(desk.desk_slot_size()), hdr.arena_off) _dbg(&buf4) - # 图形壳暂时只是占位,常驻不退出(app 的 _start 绝不能再 return:栈顶下一格是 0)。 - while True: - process.yield_cpu() + # 6) 进入窗口服务端主循环(永不返回):请求环 + 输入 + 合成。 + srv: desk_server.DeskServer = desk_server.DeskServer() + srv.init(fb, int(info.width), int(info.height), int(info.pitch), info.format, shm, kfd, mfd) + _dbg("[explorer] desk server running\n") + srv.serve() return 0 diff --git a/vpsdk/desk.vp b/vpsdk/desk.vp index 083af95..4746d85 100644 --- a/vpsdk/desk.vp +++ b/vpsdk/desk.vp @@ -33,7 +33,6 @@ define DESK_VERSION = 1 define DESK_MAX_WIN = 8 define DESK_TITLE_MAX = 32 define DESK_RING_SLOTS = 64 -define DESK_MSG_SIZE = 40 # DeskMsg 的字节数(见下) # 固定偏移 define DESK_OFF_HEADER = 0x000000