Files
ViperOS/vpsdk/window.vp
T
TermiNexus aab4643dfa feat(desk): style 透传到 CREATE 请求;任务栏随焦点/窗口集合刷新
① style 透传:DeskMsg 增加 style 字段(放在 d 与 title 之间),window._send 增加 style 可选参数(默认 0,其它调用点不受影响),create_window 把 style 一并压进 CREATE 请求,explorer 的 handle_request 用它建窗(原来硬编码 0)—— 这样 vpui 的 set_style 才能真正生效,样式区分与圆角才有意义。DeskMsg 是共享协议结构,app 与 explorer 必须一起重编。② 任务栏刷新:mark_titles 追加标记任务栏条带(焦点切换会调它,任务栏图标配色随之更新),CREATE 与 DESTROY 分支也各调一次(图标数量变化)。
2026-10-16 22:42:58 +08:00

259 lines
9.5 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import vpsdk.desk as desk
import vpsdk.process as process
import vpsdk.stdlib as stdlib # 不能删:app 的裸名 malloc 靠它把 stdlib 拉进链接图
import vpsdk.devfs as devfs
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 = desk.desk_map(_fd)
if b == 0:
devfs.serial_puts("[win] desk mmap FAILED\n")
_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:
devfs.serial_puts("[win] desk not ready (attach timeout)\n")
_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 = desk.desk_open()
if fd < 0:
devfs.serial_puts("[win] open /dev/desk FAILED\n")
_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,
style: int = 0) -> 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
_msg.style = uint32(style)
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:
# 建窗失败必须**可见**:Tk.mainloop 的循环条件是 window.is_active(win_id),win_id = -1
# 时一次都不循环就返回 ⇒ app 一句日志都没有就 exit(现场:Scene3D spawn 后静默退出)。
if _connect() != 0:
devfs.serial_puts("[win] create_window: connect FAILED\n")
return -1
if _my_pid < 0: return -1
if _send(uint32(desk.DESK_REQ_CREATE), 0, x, y, w, h, title, style) != 0:
devfs.serial_puts("[win] create_window: request ring push FAILED\n")
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
devfs.serial_puts("[win] create_window: CREATED timeout\n")
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)