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:
1 parent
bb82c2fe75
commit
a45b9808b0
3 files changed
+331
-133
No files matched your search
@@ -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
@@ -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
@@ -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)
|
||||
Reference in new issue
Block a user