feat(explorer): S3c —— explorer 窗口服务端与合成器(请求环 + 输入 + 逐行合成)

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)收口。
This commit is contained in:
TermiNexus committed 2026-10-16 18:56:30 +08:00
1 parent 30708d3250
commit 1633769534
3 files changed
+399 -23

No files matched your search

+391
View File
@@ -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()
+8 -22
View File
@@ -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