diff --git a/Apps/Explorer/desk_server.vp b/Apps/Explorer/desk_server.vp index 8444bf3..3af80ef 100644 --- a/Apps/Explorer/desk_server.vp +++ b/Apps/Explorer/desk_server.vp @@ -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): diff --git a/vpsdk/desk.vp b/vpsdk/desk.vp index 71a9e93..fc1eff4 100644 --- a/vpsdk/desk.vp +++ b/vpsdk/desk.vp @@ -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 diff --git a/vpsdk/window.vp b/vpsdk/window.vp index 707a9fc..696fd43 100644 --- a/vpsdk/window.vp +++ b/vpsdk/window.vp @@ -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)