feat(s3d): vpsdk/window.vp 改走 /dev/desk 共享协议(对外 API 一字未改)

window.vp 重写:不再走内核 UI syscall,全部经共享区与用户态 explorer 通信。对外函数名/签名/常量/InputEvent 全部保持原样 ⇒ vpui.vp 与四个 app 零改动。

数据面:get_winbuf 直接返回共享 arena 里本窗口那块像素缓冲的线性地址(app 就地画),flush 只压一条 FLUSH 请求 ⇒ 全程零像素拷贝(旧实现是 app 私有缓冲 + 整块拷给内核)。控制面:请求环 + 按 pid 分的事件环;is_active/get_win_size 直接读共享槽(零 syscall)。create_window 压 CREATE(标题内联在消息里)并等 CREATED 答复拿 win_id。

协议补齐:DeskMsg 增内联 title(CREATE/TITLE 用,ring_push/pop 同步搬运);事件环按 pid 分(不能按 win_id——建窗答复送不到;也不能单环——单消费者结构多 app 会互抢);新增 DESK_MOUSE_PRESS/RELEASE/HOVER 语义常量。

合成器:关闭按钮改为置 active=0(app 的 while is_active() 自然退出)并通知一次 CLOSE;点击只投给窗口**内容区**(标题栏用于拖动)、松手投 RELEASE、移动投 HOVER,坐标均换算成窗口内坐标;拖动改为只在标题栏生效;RESIZE 重新从 arena 切片并更新内容尺寸(与 vpui 的 bh = h - TITLE_H - 2 对齐)。
This commit is contained in:
TermiNexus committed 2026-10-16 19:08:18 +08:00
1 parent bb82c2fe75
commit a45b9808b0
3 files changed
+331 -133

No files matched your search

+58 -12
View File
@@ -20,7 +20,8 @@ import string
# 窗口侧边框、样式区分、心跳无响应检测。
# ============================================================================
define TITLE_H = 22
define TITLE_H = 32 # 必须与 vpsdk/vpui.vp 的 TITLE_H 一致(app 用 h - TITLE_H - 2 算内容高)
define TITLE_GAP = 2
define CLOSE_SZ = 14
define CLOSE_PAD = 5
define CURSOR_SZ = 10
@@ -151,7 +152,14 @@ class DeskServer:
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。"""
"""找一个空槽并分配像素缓冲。返回 win_id,失败 -1。
尺寸约定(必须与 vpui 对齐):入参 w/h 是**含标题栏**的外框;内容的像素缓冲是
w × (h - TITLE_H - TITLE_GAP),与 vpui 的 `bh = h - TITLE_H - 2` 一致。
arena 只是 bump 分配、不支持回收,所以像素缓冲按 w*h(外框)预留一点余量。
"""
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:
@@ -169,7 +177,7 @@ class DeskServer:
s.cursor = 0
s.fb_off = buf_off
s.fb_w = uint32(w)
s.fb_h = uint32(h)
s.fb_h = uint32(content_h)
string.memset(&s.title, 0, desk.DESK_TITLE_MAX)
# 像素缓冲先清成窗口底色,避免 app 还没画就露出垃圾
buf: uint32* = uint32*(self.shm + uint64(buf_off))
@@ -225,10 +233,10 @@ class DeskServer:
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
cy: int = rel - TITLE_H - TITLE_GAP
cw: int = int(s.fb_w)
if cw > w: cw = w
if cy < int(s.fb_h):
if cy >= 0 and 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)
@@ -254,7 +262,7 @@ class DeskServer:
# ---------------- 事件投递 ----------------
def push_event(self, win: int, etype: uint32, a: int, b: int, c: int) -> int:
"""按窗口的 pid 投递到**单一**事件环(app 侧按自己的 pid 过滤)。"""
"""按收件窗口的 pid 投进**该进程自己的**事件环。"""
s: desk.WinSlot* = self.slot(win)
self.evt.type = etype
self.evt.win = win
@@ -263,7 +271,7 @@ class DeskServer:
self.evt.b = b
self.evt.c = c
self.evt.d = 0
return desk.ring_push(self.shm + desk.desk_evt_ring_off(), self.evt)
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) -> int:
"""建窗答复专用:此时窗口槽还没有 pid,直接按请求里带的 pid 投递。"""
@@ -274,13 +282,16 @@ class DeskServer:
self.evt.b = b
self.evt.c = c
self.evt.d = 0
return desk.ring_push(self.shm + desk.desk_evt_ring_off(), self.evt)
return desk.ring_push(self.shm + desk.desk_evt_ring_off(pid), self.evt)
# ---------------- 请求处理 ----------------
def handle_request(self, m: desk.DeskMsg):
if m.type == uint32(desk.DESK_REQ_CREATE):
# CREATE:pid 由请求携带(窗口还没诞生,事件环只能按 pid 过滤)
# CREATE:pid 由请求携带(窗口还没诞生,事件环只能按 pid 找到),标题内联在消息里
nw: int = self.alloc_window(m.pid, m.a, m.b, m.c, m.d, 0)
if nw >= 0:
ns: desk.WinSlot* = self.slot(nw)
string.memcpy(&ns.title, &m.title, uint64(desk.DESK_TITLE_MAX))
self.push_event_pid(m.pid, nw, uint32(desk.DESK_EVT_CREATED), nw, 0, 0)
return
win: int = m.win
@@ -299,9 +310,20 @@ class DeskServer:
self.mark(s.uy, s.h)
elif ty == uint32(desk.DESK_REQ_RESIZE):
self.mark(s.uy, s.h)
new_ch: int = m.b - TITLE_H - TITLE_GAP
if new_ch < 1: new_ch = 1
# arena 是 bump 分配、不能回收:换尺寸一律重新切片,旧块留给后续复用(不做 free)
new_off: uint32 = desk.desk_arena_alloc(self.shm, uint32(m.a) * uint32(m.b) * uint32(4))
if new_off != uint32(0):
s.fb_off = new_off
s.w = m.a
s.h = m.b
s.fb_w = uint32(m.a)
s.fb_h = uint32(new_ch)
self.mark(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_FLUSH):
self.mark(s.uy, s.h)
elif ty == uint32(desk.DESK_REQ_ACTIVATE):
@@ -367,12 +389,19 @@ class DeskServer:
self.mark(self.cy - 1, CURSOR_SZ + 2)
self.pcx = self.cx
self.pcy = self.cy
# 悬停:把 HOVER 投给光标下那个窗口(窗口内坐标)
if self.btn_down == 0:
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 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
@@ -380,13 +409,30 @@ class DeskServer:
in_close = 1
self.focus(hit)
if in_close != 0:
# 关闭:置 active=0 让 app 的 `while is_active()` 自然退出,并通知一次
s.active = 0
self.push_event(hit, uint32(desk.DESK_EVT_CLOSE), 0, 0, 0)
else:
self.mark(s.uy, s.h)
elif self.cy < s.uy + TITLE_H:
# 标题栏(非关闭按钮)⇒ 开始拖动
self.drag_win = hit
self.drag_dx = self.cx - s.ux
self.drag_dy = self.cy - s.uy
else:
# 内容区 ⇒ 把点击按窗口内坐标投给 app
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 == 0 and self.btn_down != 0:
self.btn_down = 0
if self.drag_win < 0:
# 松手:投 RELEASE 给光标下的窗口
rw: int = self.hit_test(self.cx, self.cy)
if rw >= 0:
rs: desk.WinSlot* = self.slot(rw)
self.push_event(rw, uint32(desk.DESK_EVT_MOUSE),
desk.DESK_MOUSE_RELEASE,
self.cx - rs.ux, self.cy - rs.uy - TITLE_H - TITLE_GAP, 0)
self.drag_win = -1
elif self.btn_down != 0 and self.drag_win >= 0:
s2: desk.WinSlot* = self.slot(self.drag_win)
@@ -394,7 +440,7 @@ class DeskServer:
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)
self.mark(s2.uy, s2.h)
# ---------------- 主循环 ----------------
def serve(self):
+25 -14
View File
@@ -56,6 +56,11 @@ define DESK_EVT_KEY = 103 # a=ascii b=keycode c=state
define DESK_EVT_MOUSE = 104 # a=x b=y c=buttons d=type
define DESK_EVT_RESIZE = 105 # a=w b=h
# 鼠标事件的细分(DESK_EVT_MOUSE 的 a 字段);vpsdk/window.vp 把它映射成 app 的 EVENT_TYPE_*
define DESK_MOUSE_PRESS = 1
define DESK_MOUSE_RELEASE = 2
define DESK_MOUSE_HOVER = 3
@__attribute__(packed)
class DeskHeader:
@@ -94,13 +99,15 @@ class WinSlot:
@__attribute__(packed)
class DeskMsg:
type: uint32
win: int # 请求:目标窗口;CREATE 时为 0(窗口还没诞生)
pid: int # 事件的接收方 pid(请求时由 app 填自己的 pid)
a: int
b: int
c: int
d: int
type: uint32
win: int # 请求:目标窗口;CREATE 时为 0(窗口还没诞生)
pid: int # 事件的接收方 pid(请求时由 app 填自己的 pid)
a: int
b: int
c: int
d: int
# 标题随消息内联(CREATE / REQ_TITLE 用)—— 消息有固定上限,标题最多 DESK_TITLE_MAX-1。
title: [DESK_TITLE_MAX]char
@__attribute__(packed)
@@ -150,6 +157,7 @@ def ring_push(ctrl_off: uint64, msg: DeskMsg) -> int:
slot.b = msg.b
slot.c = msg.c
slot.d = msg.d
string.memcpy(&slot.title, &msg.title, uint64(DESK_TITLE_MAX))
c.tail = nxt # 最后写 tail:读者以 tail 为"有货"的标志
return 0
@@ -166,6 +174,7 @@ def ring_pop(ctrl_off: uint64, out: DeskMsg*) -> int:
out.b = slot.b
out.c = slot.c
out.d = slot.d
string.memcpy(&out.title, &slot.title, uint64(DESK_TITLE_MAX))
c.head = (c.head + uint32(1)) % c.size
return 1
@@ -182,14 +191,15 @@ def desk_slot_off(win_id: int) -> uint64:
def desk_req_ring_off() -> uint64:
return uint64(DESK_OFF_REQ)
def desk_evt_ring_off() -> uint64:
"""事件环只有**一个**(不是每窗口一个)。
def desk_evt_ring_off(pid: int) -> uint64:
"""事件环按**接收方 pid** 分环(不是按 win_id,也不是单环)。
理由:建窗答复 CREATED 必须在 app 知道自己的 win_id **之前**就能送达,
所以事件环不能按 win_id 索引;改为单环 + 每条事件带目标 pid,
由 app 按自己的 pid 过滤 —— 与内核旧接口 poll_event(buf, caller_pid) 同口径。
· 不能按 win_id:建窗答复 CREATED 必须在 app 知道 win_id 之前送达;
· 不能只用一个共享单环:环是单消费者结构,多个 app 各自 pop 会互相抢事件。
⇒ 按 pid 索引(进程数 = MAX_PROCESSES = 8,与 DESK_MAX_WIN 同量级),
app 用自己的 pid 找到属于自己的那条环,天然隔离。
"""
return uint64(DESK_OFF_EVENTS)
return uint64(DESK_OFF_EVENTS) + uint64(pid) * uint64(0x1000)
# ---------------------------------------------------------------------------
@@ -216,7 +226,8 @@ def desk_format(base: uint64, fb_w: uint32, fb_h: uint32, fb_pitch: uint32):
h.win_count = 0
h.ready = 0
ring_init(base + desk_req_ring_off(), uint32(DESK_RING_SLOTS))
ring_init(base + desk_evt_ring_off(), uint32(DESK_RING_SLOTS))
for pi in range(DESK_MAX_WIN):
ring_init(base + desk_evt_ring_off(pi), uint32(DESK_RING_SLOTS))
# 区首清零后 magic 必须最后写,避免 app 读到半初始化状态
h.magic = uint32(DESK_MAGIC)
h.ready = 1
+248 -107
View File
@@ -1,107 +1,248 @@
from . import syscall as syscall
import vpsdk.stdlib as stdlib
define STYLE_CLASSIC = 0
define STYLE_WIN10 = 1
define STYLE_WIN11 = 2
define STYLE_WIN7 = 3
define STYLE_WINXP = 4
define EVENT_TYPE_NONE = 0
define EVENT_TYPE_CLICK = 1
define EVENT_TYPE_HOVER = 2
define EVENT_TYPE_RELEASE = 3
define EVENT_TYPE_HOVER_LEAVE = 4
define EVENT_TYPE_KEY = 5
define EVENT_TYPE_KEY_RELEASE = 6
define EVENT_TYPE_PING = 7
define EVENT_TYPE_RESIZE = 8
define KEY_STATE_PRESS = 1
define KEY_STATE_RELEASE = 2
define KEY_STATE_REPEAT = 3
define CURSOR_DEFAULT = 0
define CURSOR_IBEAM = 1
define CURSOR_RESIZE_EW = 2
define CURSOR_RESIZE_NS = 3
define CURSOR_RESIZE_NWSE = 4
define VK_LEFT = -1
define VK_RIGHT = -2
define VK_UP = -3
define VK_DOWN = -4
define VK_HOME = -5
define VK_END = -6
define VK_DELETE = -7
define VK_PRIOR = -8
define VK_NEXT = -9
@__attribute__(packed)
struct InputEvent:
type: uint32
win_id: int
x: int
y: int
button: uint8
_pad0: uint8
_pad1: uint8
_pad2: uint8
def create_window(x: int, y: int, w: int, h: int, title: const str, style: int) -> int:
return int(syscall._syscall6(uint64(syscall.Syscall.CREATE_WINDOW), uint64(x), uint64(y), uint64(w), uint64(h), uint64(title), uint64(style)))
def draw_text(win_id: int, x: int, y: int, text: const str, color: uint32) -> int:
return int(syscall._syscall5(uint64(syscall.Syscall.DRAW_TEXT), uint64(win_id), uint64(x), uint64(y), uint64(text), uint64(color)))
# 每窗口一份**用户态**后备缓冲(stdlib.malloc → MMAP → 0x20000000 用户堆)。
# 旧实现用 GET_WINBUF 把内核窗口缓冲地址直接交给 app,那片页 U=0 → app 一画就 #PF(err=0x7);
# 现在 app 画在自己的用户缓冲里,flush(win_id) 交给内核拷进窗口缓冲(WIN_FLUSH(win_id, src))。
# 注意:这里必须写成**单个字面量** —— `1024*1024*4` 会按最小宽度(i16)做算术而回绕成 0,
# 让缓冲只有几字节,随后内核 memcpy 240KB 就会越界读(三重故障的根)。4MB。
define WINBUF_MAX_BYTES = 4194304
_winbufs: [64]uint64
def get_winbuf(win_id: int) -> uint32*:
if win_id < 0 or win_id >= 64: return None
if _winbufs[win_id] == 0:
p: rawptr = stdlib.malloc(WINBUF_MAX_BYTES)
if p is None: return None
_winbufs[win_id] = uint64(p)
return uint32*(_winbufs[win_id])
def get_win_size(win_id: int) -> int:
return int(syscall._syscall2(uint64(syscall.Syscall.GET_WIN_SIZE), uint64(win_id), 0))
def flush(win_id: int):
buf: uint32* = get_winbuf(win_id)
syscall._syscall3(uint64(syscall.Syscall.WIN_FLUSH), uint64(win_id), uint64(buf))
def mainloop(win_id: int):
syscall._syscall1(uint64(syscall.Syscall.WAIT_WINDOW), uint64(win_id))
def poll_event(evt: InputEvent*) -> int:
return int(syscall._syscall1(uint64(syscall.Syscall.POLL_EVENT), uint64(evt)))
def is_active(win_id: int) -> int:
return int(syscall._syscall2(uint64(syscall.Syscall.WAIT_WINDOW), uint64(win_id), 1))
def set_cursor_type(win_id: int, cursor_t: int):
syscall._syscall2(uint64(syscall.Syscall.SET_CURSOR), uint64(win_id), uint64(cursor_t))
def set_title(win_id: int, title: const str) -> int:
return int(syscall._syscall2(uint64(syscall.Syscall.SET_TITLE), uint64(win_id), uint64(title)))
def set_geometry(win_id: int, x: int, y: int, w: int, h: int) -> int:
return int(syscall._syscall5(uint64(syscall.Syscall.SET_GEOMETRY), uint64(win_id), uint64(x), uint64(y), uint64(w), uint64(h)))
def set_resizable(win_id: int, rw: int, rh: int) -> int:
return int(syscall._syscall3(uint64(syscall.Syscall.SET_RESIZABLE), uint64(win_id), uint64(rw), uint64(rh)))
#class Window:
# HWND: int
# def __init__(self):
# self.HWND = create_window(100, 100, 100, 100)
from . import syscall as syscall
import vpsdk.stdlib as stdlib
import vpsdk.devfs as devfs
import vpsdk.desk as desk
import vpsdk.process as process
import string
define STYLE_CLASSIC = 0
define STYLE_WIN10 = 1
define STYLE_WIN11 = 2
define STYLE_WIN7 = 3
define STYLE_WINXP = 4
define EVENT_TYPE_NONE = 0
define EVENT_TYPE_CLICK = 1
define EVENT_TYPE_HOVER = 2
define EVENT_TYPE_RELEASE = 3
define EVENT_TYPE_HOVER_LEAVE = 4
define EVENT_TYPE_KEY = 5
define EVENT_TYPE_KEY_RELEASE = 6
define EVENT_TYPE_PING = 7
define EVENT_TYPE_RESIZE = 8
define KEY_STATE_PRESS = 1
define KEY_STATE_RELEASE = 2
define KEY_STATE_REPEAT = 3
define CURSOR_DEFAULT = 0
define CURSOR_IBEAM = 1
define CURSOR_RESIZE_EW = 2
define CURSOR_RESIZE_NS = 3
define CURSOR_RESIZE_NWSE = 4
define VK_LEFT = -1
define VK_RIGHT = -2
define VK_UP = -3
define VK_DOWN = -4
define VK_HOME = -5
define VK_END = -6
define VK_DELETE = -7
define VK_PRIOR = -8
define VK_NEXT = -9
@__attribute__(packed)
struct InputEvent:
type: uint32
win_id: int
x: int
y: int
button: uint8
_pad0: uint8
_pad1: uint8
_pad2: uint8
# ============================================================================
# 窗口层:不再走内核 UI syscall,全部经 /dev/desk 共享区与用户态图形壳 explorer
# 通信(S3d)。**对外 API 与函数签名一字未改**,因此 vpui 与各 app 无需改动。
#
# 数据面:get_winbuf 直接返回共享 arena 里属于本窗口那块像素缓冲的线性地址,
# app 就地画;画完压一条 FLUSH 请求让 explorer 合成 —— 全程零像素拷贝。
# 控制面:请求环(app → explorer)+ **按 pid 分**的事件环(explorer → app):
# 按 pid 而不是按 win_id,是因为建窗答复 CREATED 必须在 app 知道 win_id
# 之前送达;也不能只用一个共享单环(那是单消费者结构,多 app 会互相抢)。
# ============================================================================
# 等 explorer 就绪的上限(每次 yield_cpu 约 10ms 真让出一次 ⇒ 4000 次 ≈ 40s)
define DESK_ATTACH_TRIES = 4000
_base: uint64 = 0 # 共享区首地址(0 = 还没接上)
_fd: int = 0 # /dev/desk 的 fd(仅 _state == 1 时有效;devfs fd 0 是合法值)
_state: int = 0 # 0 = 还没连过;1 = 已连上;-1 = 连接失败(不再重试)
_my_pid: int = -1
_msg: desk.DeskMsg
_evt: desk.DeskMsg
def _attach() -> int:
"""映射共享区并等 explorer 置 ready。返回 0 成功 / -1 失败(结果缓存,只做一次)。"""
global _base, _state, _my_pid
if _state == 1: return 0
if _state < 0: return -1
b: uint64 = devfs.desk_map(_fd)
if b == 0:
_state = -1
return -1
i: int = 0
while desk.desk_ready(b) == 0 and i < DESK_ATTACH_TRIES:
process.yield_cpu()
i += 1
if desk.desk_ready(b) == 0:
_state = -1
return -1
_base = b
_state = 1
return 0
def _connect() -> int:
"""懒连接:第一次真正用到窗口层时才 open /dev/desk。"""
global _fd, _state, _my_pid
if _state == 1: return 0
if _state < 0: return -1
fd: int = devfs.desk_open()
if fd < 0:
_state = -1
return -1
_fd = fd
_my_pid = process.get_pid()
return _attach()
def _send(etype: uint32, win: int, a: int, b: int, c: int, d: int, title: const str) -> int:
"""组一条请求压进请求环。title 可为 None(只对 CREATE / TITLE 有意义)。"""
if _connect() != 0: return -1
_msg.type = etype
_msg.win = win
_msg.pid = _my_pid
_msg.a = a
_msg.b = b
_msg.c = c
_msg.d = d
string.memset(&_msg.title, 0, desk.DESK_TITLE_MAX)
if title is not None:
i: int = 0
while i < desk.DESK_TITLE_MAX - 1:
ch: char = title[i]
if ch == 0: break
_msg.title[i] = ch
i += 1
return desk.ring_push(_base + desk.desk_req_ring_off(), _msg)
def _slot(win: int) -> desk.WinSlot*:
return desk.WinSlot*(_base + desk.desk_slot_off(win))
def create_window(x: int, y: int, w: int, h: int, title: const str, style: int) -> int:
if _connect() != 0: return -1
if _my_pid < 0: return -1
if _send(uint32(desk.DESK_REQ_CREATE), 0, x, y, w, h, title) != 0:
return -1
# 等 CREATED 答复(win_id 由 explorer 分配)。建窗通常在 app 初始化阶段、
# 事件环是空的;这期间若弹到非 CREATED 事件(属于本进程的其它窗口输入)就丢弃,
# 只影响建窗这一瞬的输入,且不会把别人的事件吃掉(事件环是按 pid 分的)。
i: int = 0
while i < DESK_ATTACH_TRIES:
if desk.ring_pop(_base + desk.desk_evt_ring_off(_my_pid), &_evt) != 0:
if _evt.type == uint32(desk.DESK_EVT_CREATED):
return _evt.a
process.yield_cpu()
i += 1
return -1
def get_winbuf(win_id: int) -> uint32*:
"""本窗口的像素缓冲(共享区 arena 里那块),app 就地在用户态画。
与旧实现的关键差别:旧版是 app 自己的 stdlib.malloc 缓冲 + flush 时整块拷给内核;
现在缓冲本身就在 explorer 能看到的共享区里,flush 只压一条"请合成"的请求。
"""
if _connect() != 0: return None
if win_id < 0 or win_id >= desk.DESK_MAX_WIN: return None
s: desk.WinSlot* = _slot(win_id)
if s.used == 0: return None
return uint32*(_base + uint64(s.fb_off))
def get_win_size(win_id: int) -> int:
"""返回 (内容宽 << 16) | 内容高;内容高不含标题栏 —— 与旧接口同口径。"""
if _connect() != 0: return 0
if win_id < 0 or win_id >= desk.DESK_MAX_WIN: return 0
s: desk.WinSlot* = _slot(win_id)
if s.used == 0: return 0
return (int(s.fb_w) << 16) | (int(s.fb_h) & 0xFFFF)
def flush(win_id: int):
_send(uint32(desk.DESK_REQ_FLUSH), win_id, 0, 0, 0, 0, None)
def is_active(win_id: int) -> int:
if _connect() != 0: return 0
if win_id < 0 or win_id >= desk.DESK_MAX_WIN: return 0
s: desk.WinSlot* = _slot(win_id)
if s.used == 0: return 0
return s.active
def mainloop(win_id: int):
while is_active(win_id) != 0:
process.yield_cpu()
def poll_event(evt: InputEvent*) -> int:
"""取一条属于本进程的事件并翻译成 InputEvent。返回 1 = 有事件,0 = 空。"""
if _connect() != 0: return 0
if desk.ring_pop(_base + desk.desk_evt_ring_off(_my_pid), &_evt) == 0:
return 0
t: uint32 = _evt.type
evt.win_id = _evt.win
evt.button = uint8(0)
evt._pad0 = uint8(0)
evt._pad1 = uint8(0)
evt._pad2 = uint8(0)
if t == uint32(desk.DESK_EVT_KEY):
evt.type = uint32(EVENT_TYPE_KEY)
evt.x = _evt.a # ASCII
evt.y = _evt.b # keycode
evt.button = uint8(KEY_STATE_PRESS)
return 1
if t == uint32(desk.DESK_EVT_RESIZE):
evt.type = uint32(EVENT_TYPE_RESIZE)
evt.x = _evt.a # 新内容宽
evt.y = _evt.b # 新内容高
return 1
if t == uint32(desk.DESK_EVT_MOUSE):
kind: int = _evt.a
if kind == desk.DESK_MOUSE_PRESS:
evt.type = uint32(EVENT_TYPE_CLICK)
elif kind == desk.DESK_MOUSE_RELEASE:
evt.type = uint32(EVENT_TYPE_RELEASE)
else:
evt.type = uint32(EVENT_TYPE_HOVER)
evt.x = _evt.b # 窗口内坐标
evt.y = _evt.c
evt.button = uint8(_evt.d)
return 1
if t == uint32(desk.DESK_EVT_CLOSE):
# 关闭只作为通知发一次;窗口的 usable 状态由 explorer 置 active=0 表达,
# 因此 vpui 的 `while is_active()` 会自然退出。这里给 NONE 让调用方忽略。
evt.type = uint32(EVENT_TYPE_NONE)
return 1
return 0
def set_cursor_type(win_id: int, cursor_t: int):
# TODO(S3c-2):光标形状目前由 explorer 统一画成方框,尚无按窗口切换的请求。
pass
def set_title(win_id: int, title: const str) -> int:
return _send(uint32(desk.DESK_REQ_TITLE), win_id, 0, 0, 0, 0, title)
def set_geometry(win_id: int, x: int, y: int, w: int, h: int) -> int:
"""移动 + 换尺寸拆成两条请求(协议里 MOVE 与 RESIZE 是分开的)。"""
r1: int = _send(uint32(desk.DESK_REQ_MOVE), win_id, x, y, 0, 0, None)
r2: int = _send(uint32(desk.DESK_REQ_RESIZE), win_id, w, h, 0, 0, None)
if r1 != 0 or r2 != 0: return -1
return 0
def set_resizable(win_id: int, rw: int, rh: int) -> int:
# TODO(S3c-2):拖边缩放还没做,先接受调用并忽略(不返回错误,避免调用方误判建窗失败)。
return 0
def destroy_window(win_id: int) -> int:
"""主动销毁窗口(app 退出前调用;服务端会释放槽位并重画该区域)。"""
return _send(uint32(desk.DESK_REQ_DESTROY), win_id, 0, 0, 0, 0, None)