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:
1 parent
30708d3250
commit
1633769534
3 files changed
+399
-23
No files matched your search
@@ -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
@@ -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
|
||||
Reference in new issue
Block a user