Files
ViperOS/vpsdk/window.vp
T
TermiNexus a45b9808b0 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 对齐)。
2026-10-16 19:08:18 +08:00

249 lines
8.8 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.
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)