鼠标: - 内容区按下后记录 capture_win,按住期间的移动仍投 HOVER、松开投 RELEASE (即使光标移出窗口)。此前 btn_down != 0 时完全不发 HOVER,滚动条 拖动拿不到位置更新。 - 标题栏按钮按下态改为通用 btn_win/btn_kind,支持关闭/最小化/最大化。 窗口: - WinSlot 新增 maximized、restore_*、fb_cap 字段 - 最小化:隐藏窗口并把焦点交给最高可见窗口;任务栏点击恢复, 再点一次已聚焦窗口则最小化 - 最大化:保存还原几何,铺满工作区(不含任务栏)并发送 RESIZE 事件; 再次点击还原;拖动最大化窗口会先还原 - 像素缓冲按 fb_cap 复用,仅在容量不足时从 arena 扩容,避免反复 最大化/还原耗尽 bump arena - 1920x1080 最大化需要约 7.3MB 内容缓冲:DESK_SHM_SIZE 8MB → 32MB
250 lines
10 KiB
Plaintext
250 lines
10 KiB
Plaintext
import vpsdk.devfs as devfs
|
||
import vpsdk.fs as fs
|
||
import string
|
||
from stdint import *
|
||
|
||
|
||
# ============================================================================
|
||
# /dev/desk 共享内存协议(S3):图形壳 explorer 与各 app 之间唯一的通信通道。
|
||
#
|
||
# 内核只提供"同一物理区间映射给所有打开者"(devfs 的 /dev/desk + mmap),
|
||
# 区间内部的布局与语义全部由本模块约定 —— 用户态协商,内核不参与。
|
||
#
|
||
# 区间布局(偏移均相对共享区首,全部 8 字节对齐):
|
||
# +0x000000 DeskHeader 全局头(含 magic/几何/窗口数/arena 指针)
|
||
# +0x000100 [DESK_MAX_WIN]WinSlot 窗口槽表(explorer 是唯一写者)
|
||
# +0x002000 RingCtrl + slots 请求环(app → explorer)
|
||
# +0x004000 [DESK_MAX_WIN] RingCtrl+slots 每窗口事件环(explorer → app),每窗 0x1000
|
||
# +0x200000 像素 arena 各窗口后备缓冲从这里切(bump 分配)
|
||
#
|
||
# 约定:
|
||
# · explorer 启动后把 header.ready 置 1 —— app 见到 0 表示"图形壳还没起来";
|
||
# · 窗口槽由 explorer 分配/回收;app 只通过请求环申请与更新,绝不直接改槽表;
|
||
# · 像素缓冲由 app 直接写(arena 里属于自己窗口的那块),写完压一条 FLUSH 请求
|
||
# 让 explorer 合成;这是"共享内存 + 消息"而不是"拷贝像素"的关键。
|
||
# ============================================================================
|
||
|
||
# 必须与内核 VKernel/Kernel/drivers/devfs/devfs.vp 的 DESK_SHM_SIZE 一致
|
||
# (mmap 时用户必须先给出长度,所以这里不能靠"问内核")。
|
||
# 32MB:1920×1080 全屏窗口的内容缓冲约 7.3MB,最大化需要足够 arena。
|
||
define DESK_SHM_SIZE = 33554432
|
||
define DESK_MAGIC = 0x4B534544 # 'DESK'
|
||
define DESK_VERSION = 1
|
||
|
||
define DESK_MAX_WIN = 8
|
||
define DESK_TITLE_MAX = 32
|
||
# 每 pid 事件环间隔 0x1000;环总大小 = RingCtrl(16) + SLOTS × DeskMsg(64),必须 ≤ 0x1000。
|
||
# 64 槽会占 4112B,第 63 号槽的尾部覆盖下一个 pid 的 RingCtrl(size 被写 0 ⇒ 该 pid 收不到事件,
|
||
# 且 ring_push 的 (tail+1)%0 会除零打死图形壳),所以取 63(4048B)。
|
||
define DESK_RING_SLOTS = 63
|
||
|
||
# 固定偏移
|
||
define DESK_OFF_HEADER = 0x000000
|
||
define DESK_OFF_SLOTS = 0x000100
|
||
define DESK_OFF_REQ = 0x002000
|
||
define DESK_OFF_EVENTS = 0x004000
|
||
define DESK_OFF_ARENA = 0x200000
|
||
|
||
# 消息类型:请求(app → explorer)
|
||
define DESK_REQ_CREATE = 1 # a=x b=y c=w d=h,title 在数据区;explorer 回 DESK_EVT_CREATED
|
||
define DESK_REQ_DESTROY = 2
|
||
define DESK_REQ_MOVE = 3 # a=x b=y
|
||
define DESK_REQ_RESIZE = 4 # a=w b=h
|
||
define DESK_REQ_TITLE = 5 # title 在数据区
|
||
define DESK_REQ_FLUSH = 6 # 像素已写,请求合成该窗口
|
||
define DESK_REQ_ACTIVATE = 7 # 置顶并聚焦
|
||
# 消息类型:事件(explorer → app)
|
||
define DESK_EVT_CREATED = 101 # a=win_id(失败为 -1)
|
||
define DESK_EVT_CLOSE = 102 # 用户点了关闭按钮,app 应当退出
|
||
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:
|
||
magic: uint32
|
||
version: uint32
|
||
ready: int # explorer 置 1 表示图形壳已接管
|
||
win_count: int
|
||
fb_w: uint32
|
||
fb_h: uint32
|
||
fb_pitch: uint32
|
||
_pad0: uint32
|
||
arena_off: uint32
|
||
arena_used: uint32
|
||
_pad1: uint32
|
||
|
||
|
||
@__attribute__(packed)
|
||
class WinSlot:
|
||
used: int
|
||
pid: int
|
||
active: int
|
||
minimized: int
|
||
maximized: int
|
||
z_order: int
|
||
ux: int
|
||
uy: int
|
||
w: int
|
||
h: int
|
||
# 最大化前的还原几何
|
||
restore_x: int
|
||
restore_y: int
|
||
restore_w: int
|
||
restore_h: int
|
||
style: int
|
||
cursor: int
|
||
fb_off: uint32
|
||
fb_w: uint32
|
||
fb_h: uint32
|
||
fb_cap: uint32 # 像素缓冲容量(以像素计;扩容后复用,避免 arena 泄漏)
|
||
title: [DESK_TITLE_MAX]char
|
||
|
||
|
||
@__attribute__(packed)
|
||
class DeskMsg:
|
||
type: uint32
|
||
win: int # 请求:目标窗口;CREATE 时为 0(窗口还没诞生)
|
||
pid: int # 事件的接收方 pid(请求时由 app 填自己的 pid)
|
||
a: int
|
||
b: int
|
||
c: int
|
||
d: int
|
||
style: uint32 # CREATE:窗口样式(取值同 vpsdk/window.vp 的 STYLE_*)
|
||
# 标题随消息内联(CREATE / REQ_TITLE 用)—— 消息有固定上限,标题最多 DESK_TITLE_MAX-1。
|
||
title: [DESK_TITLE_MAX]char
|
||
|
||
|
||
@__attribute__(packed)
|
||
class RingCtrl:
|
||
head: uint32 # 消费者下一次取的位置
|
||
tail: uint32 # 生产者下一次放的位置
|
||
size: uint32 # 槽数
|
||
_pad: uint32
|
||
|
||
|
||
def desk_slot_size() -> uint32:
|
||
return uint32(WinSlot.__sizeof__())
|
||
|
||
def desk_ring_ctrl_size() -> uint32:
|
||
return uint32(RingCtrl.__sizeof__())
|
||
|
||
def desk_msg_size() -> uint32:
|
||
return uint32(DeskMsg.__sizeof__())
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 环形队列:控制块在给定偏移处,槽紧随其后。单生产者单消费者,无锁 ——
|
||
# head == tail 空,(tail+1)%size == head 满。跨进程共享内存下靠"每次读写都落到
|
||
# 内存"的顺序保证:这里不缓存跨调用的状态,全部经指针读写。
|
||
# ---------------------------------------------------------------------------
|
||
def ring_init(ctrl_off: uint64, size: uint32):
|
||
c: RingCtrl* = RingCtrl*(ctrl_off)
|
||
c.head = uint32(0)
|
||
c.tail = uint32(0)
|
||
c.size = size
|
||
c._pad = uint32(0)
|
||
for i in range(size):
|
||
m: DeskMsg* = DeskMsg*(ctrl_off + uint64(desk_ring_ctrl_size()) + uint64(i) * uint64(desk_msg_size()))
|
||
string.memset(m, 0, desk_msg_size())
|
||
|
||
def ring_push(ctrl_off: uint64, msg: DeskMsg) -> int:
|
||
"""生产者入队。返回 0 = 成功,-1 = 满。"""
|
||
c: RingCtrl* = RingCtrl*(ctrl_off)
|
||
nxt: uint32 = (c.tail + uint32(1)) % c.size
|
||
if nxt == c.head:
|
||
return -1
|
||
slot: DeskMsg* = DeskMsg*(ctrl_off + uint64(desk_ring_ctrl_size()) + uint64(c.tail) * uint64(desk_msg_size()))
|
||
# 整块拷贝:DeskMsg 将来再加字段不必同步改这里(逐字段拷贝曾漏掉 style)
|
||
string.memcpy(rawptr(slot), rawptr(&msg), uint64(desk_msg_size()))
|
||
c.tail = nxt # 最后写 tail:读者以 tail 为"有货"的标志
|
||
return 0
|
||
|
||
def ring_pop(ctrl_off: uint64, out: DeskMsg*) -> int:
|
||
"""消费者出队。返回 1 = 取到一条,0 = 空。"""
|
||
c: RingCtrl* = RingCtrl*(ctrl_off)
|
||
if c.head == c.tail:
|
||
return 0
|
||
slot: DeskMsg* = DeskMsg*(ctrl_off + uint64(desk_ring_ctrl_size()) + uint64(c.head) * uint64(desk_msg_size()))
|
||
# 整块拷贝(同 ring_push)
|
||
string.memcpy(rawptr(out), rawptr(slot), uint64(desk_msg_size()))
|
||
c.head = (c.head + uint32(1)) % c.size
|
||
return 1
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 各固定结构的共享区偏移
|
||
# ---------------------------------------------------------------------------
|
||
def desk_header_off() -> uint64:
|
||
return uint64(DESK_OFF_HEADER)
|
||
|
||
def desk_slot_off(win_id: int) -> uint64:
|
||
return uint64(DESK_OFF_SLOTS) + uint64(win_id) * uint64(desk_slot_size())
|
||
|
||
def desk_req_ring_off() -> uint64:
|
||
return uint64(DESK_OFF_REQ)
|
||
|
||
def desk_evt_ring_off(pid: int) -> uint64:
|
||
"""事件环按**接收方 pid** 分环(不是按 win_id,也不是单环)。
|
||
|
||
· 不能按 win_id:建窗答复 CREATED 必须在 app 知道 win_id 之前送达;
|
||
· 不能只用一个共享单环:环是单消费者结构,多个 app 各自 pop 会互相抢事件。
|
||
⇒ 按 pid 索引(进程数 = MAX_PROCESSES = 8,与 DESK_MAX_WIN 同量级),
|
||
app 用自己的 pid 找到属于自己的那条环,天然隔离。
|
||
"""
|
||
return uint64(DESK_OFF_EVENTS) + uint64(pid) * uint64(0x1000)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 挂载:open /dev/desk + mmap 整个共享区,返回区首线性地址(0 = 失败)。
|
||
# explorer 负责格式化(magic/version/几何/各环 init/ready=1);app 只校验。
|
||
# ---------------------------------------------------------------------------
|
||
def desk_open() -> int:
|
||
return fs.open(devfs.DEV_DESK, uint32(0))
|
||
|
||
def desk_map(fd: int) -> uint64:
|
||
return devfs.shm_map(fd, uint64(DESK_SHM_SIZE))
|
||
|
||
def desk_format(base: uint64, fb_w: uint32, fb_h: uint32, fb_pitch: uint32):
|
||
"""explorer 专用:初始化共享区头与各环。"""
|
||
h: DeskHeader* = DeskHeader*(base + desk_header_off())
|
||
string.memset(h, 0, DeskHeader.__sizeof__())
|
||
h.magic = uint32(DESK_MAGIC)
|
||
h.version = uint32(DESK_VERSION)
|
||
h.fb_w = fb_w
|
||
h.fb_h = fb_h
|
||
h.fb_pitch = fb_pitch
|
||
h.arena_off = uint32(DESK_OFF_ARENA)
|
||
h.arena_used = uint32(0)
|
||
h.win_count = 0
|
||
h.ready = 0
|
||
ring_init(base + desk_req_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
|
||
|
||
def desk_ready(base: uint64) -> int:
|
||
"""app 专用:判断图形壳是否已接管并可通信。"""
|
||
h: DeskHeader* = DeskHeader*(base + desk_header_off())
|
||
if h.magic != uint32(DESK_MAGIC):
|
||
return 0
|
||
return h.ready
|
||
|
||
def desk_arena_alloc(base: uint64, size: uint32) -> uint32:
|
||
"""explorer 专用:从像素 arena 切一块,返回相对区首的偏移(0 = 空间不足)。"""
|
||
h: DeskHeader* = DeskHeader*(base + desk_header_off())
|
||
need: uint32 = (size + uint32(15)) & ~uint32(15)
|
||
used: uint32 = h.arena_used
|
||
if uint32(DESK_OFF_ARENA) + used + need > uint32(DESK_SHM_SIZE):
|
||
return uint32(0)
|
||
h.arena_used = used + need
|
||
return uint32(DESK_OFF_ARENA) + used
|