Files
ViperOS/Apps/Explorer/desk_server.vp
T
TermiNexus bd1cd2f228 fix(explorer): 逐包跟踪按下/释放沿,修复单击需长按
批量取包后只保留最后一包的按键状态:快速点击的按下+释放落在同一批里,最终 btn=0,按下沿永远检测不到(表现为必须长按)。改为批次内维护 cur 并记录 saw_press(任一分包出现 0→1)。同时把释放与拖动从 elif 改为独立 if:同批内按下并松开时,按下分支之后必须立刻走释放,否则 btn_down 卡在 1,后续点击继续失效。
2026-10-17 15:52:32 +08:00

1370 lines
59 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.devfs as devfs
import vpsdk.fs as fs
import vpsdk.process as process
import vpsdk.syscall as syscall
import vpsdk.vpui as vpui # 复用它的 8x16 点阵字体表
from stdint import *
import string
import viperlib
# 【诊断·临时】首个鼠标包已收到的标记(0 = 未收到)
_mouse_logged: int = 0
# 首次读到键盘字节时打一次(区分"内核没数据"与"事件没送达 app")
_kbd_logged: int = 0
# ============================================================================
# explorer 的窗口服务端 + 合成器(S3c):app 把窗口像素写进共享区 arena 后压一条
# DESK_REQ_FLUSH,本服务端按 Z 序逐行合成进 framebuffer;输入来自 /dev/input/*,
# 负责光标、命中测试、焦点与事件投递。未实现:任务栏时钟(用户态无时间源)、拖边缩放。
# ============================================================================
define TITLE_H = 32 # 必须与 vpsdk/vpui.vp 的 TITLE_H 一致(app 用 h - TITLE_H - 2 算内容高)
define TITLE_GAP = 2
define CURSOR_BMP_W = 16
define CURSOR_BMP_H = 16
# 任务栏与窗口装饰尺寸(对齐内核桌面 desktop.vp)
define TASKBAR_H = 50
define TASKBAR_LOGO_SZ = 32
define TASKBAR_ICON_SZ = 36
define TASKBAR_ICON_PAD = 6
# 窗口按钮尺寸(按样式;poll_input 的关闭键热区必须复用同一套取值)
define BTN_SIZE = 16 # CLASSIC
define BTN_WIN_W = 46 # WIN10/WIN11:宽条,高 = TITLE_H - 2
define BTN_W7_W = 28 # WIN7
define BTN_W7_H = 20
define BTN_XP_SZ = 21 # WINXP
define WIN_RADIUS = 8 # WIN11/WIN7/WINXP 的窗口圆角半径
# 窗口样式(与 vpsdk/window.vp 的 STYLE_* 对齐);当前 CREATE 一律按 CLASSIC 建窗
define STYLE_CLASSIC = 0
define STYLE_WIN10 = 1
define STYLE_WIN11 = 2
define STYLE_WIN7 = 3
define STYLE_WINXP = 4
# 窗口阴影(0 = 关闭)
define SHADOW_ENABLED = 1
define SHADOW_LEFT = 8
define SHADOW_RIGHT = 12
define SHADOW_TOP = 4
define SHADOW_BOTTOM = 14
define SHADOW_ALPHA = 100
# 光标 16×16 位图模板(取自 desktop.vp 的历史实现):0 = 透明、1 = 黑边、2 = 白心。
# 同一历史文件里的 _CURSOR_IBEAM / _CURSOR_RESIZE_* 待接 WinSlot.cursor 后再启用。
_CURSOR_ARROW: [256]uint8 = [
1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,
1,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,
1,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,
1,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,
1,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,
1,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,
1,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,
1,2,2,2,1,1,1,1,1,1,0,0,0,0,0,0,
1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,
1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
]
# 圆角窗口的角点/边框判定(移植自内核桌面 desktop.vp)
def _is_in_corner(px: int, py: int, cx: int, cy: int, r: int) -> int:
dx: int = px - cx
dy: int = py - cy
if dx < 0: dx = 0 - dx
if dy < 0: dy = 0 - dy
if dx <= r and dy <= r:
if dx * dx + dy * dy > r * r: return 0
return 1
def _is_on_border(rx: int, ry: int, ww: int, wh: int, r: int) -> int:
if rx == 0 or rx == ww - 1 or ry == 0 or ry == wh - 1:
return 1
if ry < r:
if rx < r:
dx: int = rx - r
dy: int = ry - r
if dx * dx + dy * dy >= (r - 1) * (r - 1): return 1
elif rx >= ww - r:
dx = rx - (ww - r - 1)
dy = ry - r
if dx * dx + dy * dy >= (r - 1) * (r - 1): return 1
elif ry >= wh - r:
if rx < r:
dx = rx - r
dy = ry - (wh - r - 1)
if dx * dx + dy * dy >= (r - 1) * (r - 1): return 1
elif rx >= ww - r:
dx = rx - (ww - r - 1)
dy = ry - (wh - r - 1)
if dx * dx + dy * dy >= (r - 1) * (r - 1): return 1
return 0
def _corner_alpha(px: int, py: int, cx: int, cy: int, r: int) -> int:
dx: int = px - cx
dy: int = py - cy
if dx < 0: dx = 0 - dx
if dy < 0: dy = 0 - dy
if dx <= r and dy <= r:
d2: int = dx * dx + dy * dy
if d2 > r * r: return 0
if d2 >= (r + 1) * (r + 1): return 160
return 255
return 255
class DeskServer:
# 屏幕
fb: uint32* # /dev/fb0 映射后的线性地址
fb_fd: int # /dev/fb0 的 fd(等 vblank 用)
fb_w: int
fb_h: int
pitch_px: int
fmt: uint32 # 0 = RGBA, 1 = BGRA
bg_row: uint32* # 一行背景的预构造副本
back: uint32* # 后备缓冲:绘制先落这里,整帧后逐行拷到 fb
# 共享区
shm: uint64
# 输入
kfd: int
mfd: int
# 脏行
dirty: uint8* # 每行 1 字节
dirty_any: int
# 光标 / 焦点 / 拖拽
cx: int
cy: int
pcx: int
pcy: int
focused: int
next_z: int
btn_down: int
drag_win: int
drag_dx: int
drag_dy: int
# 复用缓冲
msg: desk.DeskMsg
evt: desk.DeskMsg
# ---- 颜色:按 framebuffer 格式打包(与内核 gfx.COLOR_RGB 同口径)----
def rgb(self, r: uint32, g: uint32, b: uint32) -> uint32:
if self.fmt == uint32(1):
return (uint32(255) << 24) | (r << 16) | (g << 8) | b
return (uint32(255) << 24) | (b << 16) | (g << 8) | r
# ---- 按 alpha/255 把 fg 混到 bg 上(RGBA/BGRA 同为 R/B@0,16、G@8,可共用 SWAR)----
def _blend(self, bg: uint32, fg: uint32, a: int) -> uint32:
ia: int = 255 - a
v: int = ((int(fg & uint32(0x00FF00FF)) * a) + (int(bg & uint32(0x00FF00FF)) * ia)) >> 8
g: int = ((int((fg >> 8) & uint32(0xFF)) * a) + (int((bg >> 8) & uint32(0xFF)) * ia)) >> 8
return (uint32(255) << 24) | (uint32(v) & uint32(0x00FF00FF)) | (uint32(g & 0xFF) << 8)
def init(self, fb: uint32*, w: int, h: int, pitch: int, fmt: uint32,
shm: uint64, kfd: int, mfd: int, fb_fd: int):
self.fb = fb
self.fb_fd = fb_fd
self.fb_w = w
self.fb_h = h
self.pitch_px = pitch / 4
self.fmt = fmt
self.shm = shm
self.kfd = kfd
self.mfd = mfd
# dirty 与 bg_row 必须真正分配(匿名 mmap),不能把整数当地址用
self.dirty = uint8*(syscall.mmap(0, uint64(h) + uint64(64),
uint64(syscall.PROT_READ | syscall.PROT_WRITE),
uint64(syscall.MAP_PRIVATE | syscall.MAP_ANONYMOUS),
syscall.MMAP_FD_ANON, 0))
self.dirty_any = 0
self.cx = w / 2
self.cy = h / 2
self.pcx = self.cx
self.pcy = self.cy
self.focused = -1
self.next_z = 1
self.btn_down = 0
self.drag_win = -1
self.drag_dx = 0
self.drag_dy = 0
# 预构造一行背景,渲染时整行 memcpy
self.bg_row = uint32*(syscall.mmap(0, uint64(w) * uint64(4),
uint64(syscall.PROT_READ | syscall.PROT_WRITE),
uint64(syscall.MAP_PRIVATE | syscall.MAP_ANONYMOUS),
syscall.MMAP_FD_ANON, 0))
col: uint32 = self.rgb(uint32(25), uint32(25), uint32(35))
for i in range(w):
self.bg_row[i] = col
# 后备缓冲:整屏像素,行距 = fb_w(与 framebuffer 的 pitch 无关)
back_va: uint64 = syscall.mmap(0, uint64(w) * uint64(h) * uint64(4),
uint64(syscall.PROT_READ | syscall.PROT_WRITE),
uint64(syscall.MAP_PRIVATE | syscall.MAP_ANONYMOUS),
syscall.MMAP_FD_ANON, 0)
if back_va == 0:
devfs.serial_puts("[explorer] back buffer alloc FAILED\n")
self.back = self.fb # 退化:直写 framebuffer,保留原行为
else:
self.back = uint32*(back_va)
self.mark_all()
# ---------------- 脏行 ----------------
def mark(self, y0: int, h: int):
y: int = y0
while y < y0 + h:
if y >= 0 and y < self.fb_h:
if self.dirty[y] == 0:
self.dirty[y] = 1
self.dirty_any = 1
y += 1
def mark_all(self):
for y in range(self.fb_h):
self.dirty[y] = 1
self.dirty_any = 1
def clear_dirty(self):
for y in range(self.fb_h):
self.dirty[y] = 0
self.dirty_any = 0
# 窗口移动/增删时连同阴影一起置脏(阴影超出窗口外框)
def mark_win(self, y: int, h: int):
self.mark(y - SHADOW_TOP, h + SHADOW_TOP + SHADOW_BOTTOM)
# 窗口画在任务栏之上,覆盖到底部条带时必须把任务栏一并置脏,否则残留旧内容。
tb: int = self.fb_h - TASKBAR_H
if y + h + SHADOW_BOTTOM > tb:
self.mark(tb, TASKBAR_H)
# 窗口集合/层级变化:所有可见窗口整体置脏(含阴影带)
def mark_all_windows(self):
for i in range(desk.DESK_MAX_WIN):
w: desk.WinSlot* = self.slot(i)
if w.used != 0 and w.active != 0 and w.minimized == 0:
self.mark_win(w.uy, w.h)
# 活动窗口切换会改变标题栏配色:把所有窗口的标题栏行置脏
def mark_titles(self):
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used != 0:
self.mark(s.uy, TITLE_H)
# 任务栏图标随焦点/窗口集合变化,一起刷新
self.mark(self.fb_h - TASKBAR_H, TASKBAR_H)
# ---------------- 基本绘制 ----------------
def row_fill(self, x0: int, y: int, w: int, col: uint32):
if y < 0 or y >= self.fb_h or w <= 0: return
x: int = x0
dw: int = w
if x < 0:
dw = dw + x
x = 0
if x + dw > self.fb_w:
dw = self.fb_w - x
if dw <= 0: return
base: int = y * self.fb_w
string.memset32(rawptr(uint64(self.back) + uint64((base + x) * 4)), col, uint64(dw))
def row_blit(self, x0: int, y: int, src: uint32*, src_off: int, w: int):
"""把 src[src_off .. src_off+w) 拷到屏幕 (x0, y),裁剪到屏幕内。"""
if y < 0 or y >= self.fb_h or w <= 0: return
sx: int = 0
dw: int = w
dx: int = x0
if dx < 0:
sx = 0 - dx
dw = dw + dx
dx = 0
if dx + dw > self.fb_w:
dw = self.fb_w - dx
if dw <= 0: return
dst: uint64 = uint64(self.back) + uint64(y * self.fb_w + dx) * uint64(4)
srcp: uint64 = uint64(src) + uint64(src_off + sx) * uint64(4)
string.memcpy(rawptr(dst), rawptr(srcp), uint64(dw) * uint64(4))
# ---------------- 窗口槽 ----------------
def slot(self, win: int) -> desk.WinSlot*:
return desk.WinSlot*(self.shm + desk.desk_slot_off(win))
# 最高 z 序(用于判定活动窗口;无可见窗口返回 -1)
def _top_z(self) -> int:
m: int = -1
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used != 0 and s.active != 0 and s.minimized == 0:
if s.z_order > m: m = s.z_order
return m
# 键盘目标:焦点窗口有效就用它,否则取 z 序最高的可见窗口(无则 -1)
def _key_target(self) -> int:
if self.focused >= 0:
f: desk.WinSlot* = self.slot(self.focused)
if f.used != 0 and f.active != 0 and f.minimized == 0:
return self.focused
best: int = -1
best_z: int = -1
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used != 0 and s.active != 0 and s.minimized == 0:
if s.z_order > best_z:
best_z = s.z_order
best = i
return best
def alloc_window(self, pid: int, x: int, y: int, w: int, h: int, style: int) -> int:
"""找空槽并分配像素缓冲,返回 win_id(失败 -1)。入参 w/h 是含标题栏的外框;
内容缓冲 w × (h - TITLE_H - TITLE_GAP),与 vpui 的 `bh = h - TITLE_H - 2` 一致。"""
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:
buf_off: uint32 = desk.desk_arena_alloc(self.shm, uint32(w) * uint32(h) * uint32(4))
if buf_off == uint32(0): return -1
s.used = 1
s.pid = pid
s.active = 1
s.minimized = 0
s.ux = x
s.uy = y
s.w = w
s.h = h
s.style = style
s.cursor = 0
s.fb_off = buf_off
s.fb_w = uint32(w)
s.fb_h = uint32(content_h)
string.memset(&s.title, 0, desk.DESK_TITLE_MAX)
# 像素缓冲先清成窗口底色,避免 app 还没画就露出垃圾
buf: uint32* = uint32*(self.shm + uint64(buf_off))
col: uint32 = self.rgb(uint32(30), uint32(30), uint32(50))
for k in range(w * h):
buf[k] = col
s.z_order = self.next_z
self.next_z += 1
self.focused = i
self.mark_win(y, h)
self.mark_titles()
return i
return -1
# ---------------- 文本(8x16 点阵,字体表复用 vpui.font)----------------
def draw_char(self, x: int, y: int, ch: char, col: uint32):
ci: int = int(uint8(ch))
if ci >= 128: ci = ci & 127
fy: int = 0
while fy < vpui.CHAR_H:
ty: int = y + fy
if ty >= 0 and ty < self.fb_h:
row: uint8 = vpui.font[ci][fy]
if row != uint8(0):
base: int = ty * self.fb_w
fx: int = 0
while fx < vpui.CHAR_W:
if row & (0x80 >> fx):
px: int = x + fx
if px >= 0 and px < self.fb_w:
self.back[base + px] = col
fx += 1
fy += 1
def draw_str(self, x: int, y: int, s: const str, col: uint32):
cx: int = x
i: int = 0
while s[i] != 0:
self.draw_char(cx, y, s[i], col)
cx += vpui.CHAR_W
i += 1
# ---------------- 窗口阴影 ----------------
# 单个阴影像素的 alpha(与原逐像素算法完全相同)
def _shadow_alpha(self, adx: int, ady: int, me_x: int, me_y: int) -> int:
dist: int = adx
if ady > dist: dist = ady
dist = dist + ((adx + ady - dist) * 3) / 8
me: int = me_x
if adx + ady > 0:
me = (me_x * adx + me_y * ady) / (adx + ady)
if me > 0 and dist > 0 and dist < me:
t_val: int = (256 * dist) / me
t_inv: int = 256 - t_val
return (SHADOW_ALPHA * t_inv * t_inv) / 65536
return 0
def _draw_shadow(self, s: desk.WinSlot*, y0: int, y1: int):
wx: int = s.ux
wy: int = s.uy
ww: int = s.w
wh: int = s.h
sl: int = SHADOW_LEFT
sr: int = SHADOW_RIGHT
st: int = SHADOW_TOP
sb: int = SHADOW_BOTTOM
# 左右窄带(dy=0)的 alpha 只随 adx 变化,预建表
lt: [16]int
rt: [16]int
tl: [16]int
tr: [16]int
i: int = 0
while i <= sl:
lt[i] = self._shadow_alpha(i, 0, sl, st)
i += 1
i = 0
while i <= sr:
rt[i] = self._shadow_alpha(i, 0, sr, st)
i += 1
yy: int = y0
if yy < wy - st: yy = wy - st
yend: int = y1
if yend > wy + wh + sb: yend = wy + wh + sb
while yy < yend:
if yy >= 0 and yy < self.fb_h:
side: int = 0
ady: int = 0
me_y: int = st
if yy < wy:
ady = wy - yy
elif yy >= wy + wh:
ady = yy - (wy + wh - 1)
me_y = sb
else:
side = 1
tm: int = 0
if side == 0:
i = 0
while i <= sl:
tl[i] = self._shadow_alpha(i, ady, sl, me_y)
i += 1
i = 0
while i <= sr:
tr[i] = self._shadow_alpha(i, ady, sr, me_y)
i += 1
tm = self._shadow_alpha(0, ady, sl, me_y)
px: int = wx - sl
if px < 0: px = 0
xend: int = wx + ww + sr
if xend > self.fb_w: xend = self.fb_w
base: int = yy * self.fb_w
while px < xend:
a: int = 0
adx: int = 0
if px < wx:
adx = wx - px
if side != 0: a = lt[adx]
else: a = tl[adx]
elif px >= wx + ww:
adx = px - (wx + ww - 1)
if side != 0: a = rt[adx]
else: a = tr[adx]
elif side == 0:
a = tm
if a > 0:
idx: int = base + px
bg: uint32 = self.back[idx]
ia: int = 255 - a
rb: int = (int(bg & uint32(0x00FF00FF)) * ia) >> 8
g: int = (int((bg >> 8) & uint32(0xFF)) * ia) >> 8
self.back[idx] = (uint32(255) << 24) | (uint32(rb) & uint32(0x00FF00FF)) | (uint32(g & 0xFF) << 8)
px += 1
yy += 1
# ---------------- 窗口逐行绘制(按样式)----------------
# 把 col 以 alpha 混到后备缓冲已有像素上(WIN7 半透明标题栏用);逐像素内联 SWAR
def _blend_row(self, x0: int, y: int, w: int, col: uint32, a: int):
if y < 0 or y >= self.fb_h or w <= 0 or a <= 0: return
x: int = x0
dw: int = w
if x < 0:
dw = dw + x
x = 0
if x + dw > self.fb_w:
dw = self.fb_w - x
if dw <= 0: return
ia: int = 255 - a
frb: int = int(col & uint32(0x00FF00FF)) * a
fg: int = int((col >> 8) & uint32(0xFF)) * a
base: int = y * self.fb_w
i: int = 0
while i < dw:
idx: int = base + x + i
bg: uint32 = self.back[idx]
v: int = (frb + int(bg & uint32(0x00FF00FF)) * ia) >> 8
g: int = (fg + int((bg >> 8) & uint32(0xFF)) * ia) >> 8
self.back[idx] = (uint32(255) << 24) | (uint32(v) & uint32(0x00FF00FF)) | (uint32(g & 0xFF) << 8)
i += 1
# 标题栏每行颜色:WIN7/WINXP 竖直渐变 + 首两行高光 + 末行暗边,其余样式纯色
def _title_row_color(self, style: int, rel: int, th: int, active: int, title_col: uint32) -> uint32:
if style != STYLE_WIN7 and style != STYLE_WINXP:
return title_col
ratio: int = (rel * 100) / th
if ratio > 100: ratio = 100
r: int = 0
g: int = 0
b: int = 0
if style == STYLE_WIN7:
if active != 0:
r = 156 + ((186 - 156) * ratio) / 100
g = 181 + ((209 - 181) * ratio) / 100
b = 209 + ((233 - 209) * ratio) / 100
else:
r = 180 + ((200 - 180) * ratio) / 100
g = 195 + ((210 - 195) * ratio) / 100
b = 210 + ((225 - 210) * ratio) / 100
else:
if active != 0:
r = 107 + ((58 - 107) * ratio) / 100
g = 158 + ((124 - 158) * ratio) / 100
b = 212 + ((195 - 212) * ratio) / 100
else:
r = 150 + ((115 - 150) * ratio) / 100
g = 170 + ((135 - 170) * ratio) / 100
b = 205 + ((175 - 205) * ratio) / 100
col: uint32 = self.rgb(uint32(r), uint32(g), uint32(b))
if rel == 0:
if style == STYLE_WIN7:
col = self._blend(col, self.rgb(uint32(220), uint32(235), uint32(255)), 50)
else:
col = self._blend(col, self.rgb(uint32(200), uint32(225), uint32(255)), 50)
elif rel == 1:
if style == STYLE_WIN7:
col = self._blend(col, self.rgb(uint32(210), uint32(225), uint32(250)), 20)
else:
col = self._blend(col, self.rgb(uint32(200), uint32(225), uint32(255)), 25)
if rel == th - 1:
if style == STYLE_WIN7:
col = self._blend(col, self.rgb(uint32(130), uint32(160), uint32(200)), 25)
else:
col = self._blend(col, self.rgb(uint32(100), uint32(160), uint32(230)), 40)
return col
# 直边行:整行底色/内容 blit + 左右/上下 1px 边框(内容行不再先铺整行底色)
def _draw_flat_row(self, s: desk.WinSlot*, yy: int, rel: int, row_col: uint32,
row_is_title: int, border_col: uint32, title_alpha: int,
buf: uint32*, fw: int, fh: int, content_y0: int):
wx: int = s.ux
ww: int = s.w
wh: int = s.h
content_row: int = yy - content_y0
if row_is_title != 0 and title_alpha < 255:
self._blend_row(wx + 1, yy, ww - 2, row_col, title_alpha)
elif rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh:
self.row_blit(wx + 1, yy, buf, content_row * fw + 1, ww - 2)
else:
self.row_fill(wx, yy, ww, row_col)
if rel == 0 or rel == wh - 1:
self.row_fill(wx, yy, ww, border_col)
else:
self.row_fill(wx, yy, 1, border_col)
self.row_fill(wx + ww - 1, yy, 1, border_col)
# 圆角带的行:只有左右各 r 列需要逐像素,中段按直行整段处理
def _draw_round_row(self, s: desk.WinSlot*, yy: int, rel: int, row_col: uint32,
row_is_title: int, outline_col: uint32, r: int, title_alpha: int,
buf: uint32*, fw: int, fh: int, content_y0: int):
wx: int = s.ux
ww: int = s.w
wh: int = s.h
content_row: int = yy - content_y0
do_blit: int = 0
if rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh:
do_blit = 1
cyc: int = r
if rel >= r: cyc = wh - r - 1
if ww > r + r:
seg: int = 0
while seg < 2:
xs: int = wx
xe: int = wx + r
cxc: int = r
if seg == 1:
xs = wx + ww - r
xe = wx + ww
cxc = ww - r - 1
px: int = xs
while px < xe:
if px >= 0 and px < self.fb_w:
rx: int = px - wx
if _is_in_corner(rx, rel, cxc, cyc, r) != 0:
on_border: int = _is_on_border(rx, rel, ww, wh, r)
idx: int = yy * self.fb_w + px
if on_border == 0 and do_blit != 0:
self.back[idx] = buf[content_row * fw + rx]
px += 1
continue
col: uint32 = row_col
if on_border != 0:
col = outline_col
ca: int = _corner_alpha(rx, rel, cxc, cyc, r)
if on_border != 0 and ca > 0 and ca < 255:
ca = 255
if row_is_title != 0 and title_alpha < 255 and rel >= 1 and rx >= 1 and rx < ww - 1 and ca == 255 and on_border == 0:
ca = title_alpha
if ca > 0:
if ca < 255:
self.back[idx] = self._blend(self.back[idx], col, ca)
else:
self.back[idx] = col
px += 1
seg += 1
mid0: int = wx + r
mid1: int = wx + ww - r
midw: int = mid1 - mid0
if midw > 0:
if rel == 0 or rel == wh - 1:
self.row_fill(mid0, yy, midw, outline_col)
elif do_blit != 0:
self.row_blit(mid0, yy, buf, content_row * fw + r, midw)
elif row_is_title != 0 and title_alpha < 255:
self._blend_row(mid0, yy, midw, row_col, title_alpha)
else:
self.row_fill(mid0, yy, midw, row_col)
return
# 窄窗回退:整行逐像素
px: int = wx
xend: int = wx + ww
while px < xend:
if px >= 0 and px < self.fb_w:
rx: int = px - wx
cxr: int = r
if rx >= ww - r: cxr = ww - r - 1
if _is_in_corner(rx, rel, cxr, cyc, r) != 0:
on_border: int = _is_on_border(rx, rel, ww, wh, r)
idx: int = yy * self.fb_w + px
if on_border == 0 and do_blit != 0:
self.back[idx] = buf[content_row * fw + rx]
px += 1
continue
col: uint32 = row_col
if on_border != 0:
col = outline_col
ca: int = _corner_alpha(rx, rel, cxr, cyc, r)
if on_border != 0 and ca > 0 and ca < 255:
ca = 255
if row_is_title != 0 and title_alpha < 255 and rel >= 1 and rx >= 1 and rx < ww - 1 and ca == 255 and on_border == 0:
ca = title_alpha
if ca > 0:
if ca < 255:
self.back[idx] = self._blend(self.back[idx], col, ca)
else:
self.back[idx] = col
px += 1
# ---------------- 窗口按钮(按样式逐行绘制)----------------
# 圆角块的一行是否保留该列
def _round_keep(self, lx: int, lry: int, bw: int, bh: int, r: int) -> int:
if lry < r:
if lx < r: return _is_in_corner(lx, lry, r, r, r)
if lx >= bw - r: return _is_in_corner(lx, lry, bw - r - 1, r, r)
elif lry >= bh - r:
if lx < r: return _is_in_corner(lx, lry, r, bh - r - 1, r)
if lx >= bw - r: return _is_in_corner(lx, lry, bw - r - 1, bh - r - 1, r)
return 1
# 关闭键底色圆角(WIN7/WINXP 的红色圆角块):整行只做一次 row_fill
def _round_fill_row(self, bx: int, by: int, bw: int, bh: int, yy: int, col: uint32, r: int):
lry: int = yy - by
if lry < 0 or lry >= bh: return
lo: int = 0
while lo < bw and self._round_keep(lo, lry, bw, bh, r) == 0:
lo += 1
hi: int = bw
while hi > lo and self._round_keep(hi - 1, lry, bw, bh, r) == 0:
hi -= 1
if hi > lo:
self.row_fill(bx + lo, yy, hi - lo, col)
def _draw_close_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int):
lry: int = yy - by
if lry < 0 or lry >= bh: return
cx: int = bx + bw / 2
cy: int = by + bh / 2
d: int = 3
if bw >= 30: d = 4
x_col: uint32 = self.rgb(uint32(200), uint32(200), uint32(200))
if style == STYLE_CLASSIC:
self.row_fill(bx, yy, bw, self.rgb(uint32(40), uint32(40), uint32(70)))
elif style == STYLE_WIN7 or style == STYLE_WINXP:
self._round_fill_row(bx, by, bw, bh, yy, self.rgb(uint32(200), uint32(50), uint32(50)), 3)
x_col = self.rgb(uint32(255), uint32(255), uint32(255))
else:
x_col = self.rgb(uint32(150), uint32(150), uint32(150))
i: int = yy - cy
if i >= 0 - d and i <= d:
self.row_fill(cx + i, yy, 1, x_col)
self.row_fill(cx - i, yy, 1, x_col)
def _draw_min_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int):
cx: int = bx + bw / 2
cy: int = by + bh / 2
lw: int = 5
if bw >= 30: lw = 8
if yy == cy + 3:
self.row_fill(cx - lw, yy, lw * 2 + 1, self.rgb(uint32(180), uint32(180), uint32(180)))
def _draw_max_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int):
cx: int = bx + bw / 2
cy: int = by + bh / 2
sz: int = 4
if bw >= 30: sz = 6
dy: int = yy - cy
if dy < 0 - sz or dy > sz: return
col: uint32 = self.rgb(uint32(180), uint32(180), uint32(180))
if dy == 0 - sz or dy == 0 - sz + 1 or dy == sz:
self.row_fill(cx - sz, yy, sz * 2 + 1, col)
else:
self.row_fill(cx - sz, yy, 1, col)
self.row_fill(cx + sz, yy, 1, col)
# 关闭键宽/高/右内边距(按样式;poll_input 热区复用这三者)
def _close_btn_w(self, style: int) -> int:
if style == STYLE_WIN10 or style == STYLE_WIN11: return BTN_WIN_W
if style == STYLE_WIN7: return BTN_W7_W
if style == STYLE_WINXP: return BTN_XP_SZ
return BTN_SIZE
def _close_btn_h(self, style: int) -> int:
if style == STYLE_WIN10 or style == STYLE_WIN11: return TITLE_H - 2
if style == STYLE_WIN7: return BTN_W7_H
if style == STYLE_WINXP: return BTN_XP_SZ
return BTN_SIZE
def _close_btn_pad(self, style: int) -> int:
if style == STYLE_WIN10 or style == STYLE_WIN11: return 0
if style == STYLE_WIN7 or style == STYLE_WINXP: return 5
return 4
def _draw_window_btns(self, s: desk.WinSlot*, y0: int, y1: int, th: int):
wx: int = s.ux
wy: int = s.uy
ww: int = s.w
style: int = s.style
bw: int = self._close_btn_w(style)
bh: int = self._close_btn_h(style)
by: int = wy + (th - bh) / 2
close_x: int = wx + ww - bw - self._close_btn_pad(style)
gap: int = 0
if style == STYLE_WIN7 or style == STYLE_WINXP: gap = 1
max_x: int = close_x - bw - gap
min_x: int = max_x - bw - gap
yy: int = y0
if yy < by: yy = by
yend: int = y1
if yend > by + bh: yend = by + bh
while yy < yend:
self._draw_close_row(close_x, by, bw, bh, yy, style)
self._draw_min_row(min_x, by, bw, bh, yy, style)
self._draw_max_row(max_x, by, bw, bh, yy, style)
yy += 1
# ---------------- 任务栏 ----------------
# 第 ordinal 个图标的左边界;绘制与命中测试共用
def _taskbar_icon_x(self, ordinal: int) -> int:
return 4 + TASKBAR_LOGO_SZ + 80 + ordinal * (TASKBAR_ICON_SZ + TASKBAR_ICON_PAD)
# 图标行的顶边界;绘制与命中测试共用
def _taskbar_icon_y(self) -> int:
return self.fb_h - TASKBAR_H + (TASKBAR_H - TASKBAR_ICON_SZ) / 2
# 任务栏图标命中:返回窗口下标(-1 = 未命中);筛选与 ordinal 顺序同绘制
def _taskbar_icon_hit(self, x: int, y: int) -> int:
iy: int = self._taskbar_icon_y()
if y < iy or y >= iy + TASKBAR_ICON_SZ: return -1
cnt: int = 0
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used == 0 or s.active == 0:
continue
ix: int = self._taskbar_icon_x(cnt)
if x >= ix and x < ix + TASKBAR_ICON_SZ:
return i
cnt += 1
return -1
# 点击图标:最小化先恢复,再走既有 focus(置顶并整体置脏)
def _activate_from_taskbar(self, win: int):
if win < 0 or win >= desk.DESK_MAX_WIN: return
s: desk.WinSlot* = self.slot(win)
if s.used == 0: return
if s.minimized != 0:
s.minimized = 0
self.mark_win(s.uy, s.h)
self.focus(win)
def _draw_taskbar_rows(self, y0: int, y1: int):
bar_y: int = self.fb_h - TASKBAR_H
if y1 <= bar_y or y0 >= self.fb_h: return
bar_col: uint32 = self.rgb(uint32(32), uint32(32), uint32(32))
yy: int = y0
if yy < bar_y: yy = bar_y
while yy < y1 and yy < self.fb_h:
self.row_fill(0, yy, self.fb_w, bar_col)
yy += 1
# 顶部分隔线
if bar_y >= y0 and bar_y < y1:
self.row_fill(0, bar_y, self.fb_w, self.rgb(uint32(60), uint32(60), uint32(60)))
# logo:内亮外环的蓝色圆盘
logo_x: int = 4
logo_y: int = bar_y + (TASKBAR_H - TASKBAR_LOGO_SZ) / 2
lr: int = TASKBAR_LOGO_SZ / 2
lc_ring: uint32 = self.rgb(uint32(80), uint32(120), uint32(220))
lc_core: uint32 = self.rgb(uint32(100), uint32(160), uint32(255))
yy = y0
if yy < logo_y: yy = logo_y
while yy < y1 and yy < logo_y + TASKBAR_LOGO_SZ:
ly2: int = yy - logo_y - lr
lx: int = 0
while lx < TASKBAR_LOGO_SZ:
lx2: int = lx - lr
d2: int = lx2 * lx2 + ly2 * ly2
if d2 <= lr * lr:
px: int = logo_x + lx
if px >= 0 and px < self.fb_w:
if d2 <= (lr * lr) / 2:
self.back[yy * self.fb_w + px] = lc_core
else:
self.back[yy * self.fb_w + px] = lc_ring
lx += 1
yy += 1
# 品牌文字
text_y: int = bar_y + (TASKBAR_H - vpui.CHAR_H) / 2
if text_y + vpui.CHAR_H > y0 and text_y < y1:
self.draw_str(logo_x + TASKBAR_LOGO_SZ + 8, text_y, "ViperOS",
self.rgb(uint32(255), uint32(255), uint32(255)))
# 窗口图标:有边框方块 + 标题首字符(焦点/最小化配色不同)
icon_y: int = self._taskbar_icon_y()
cnt: int = 0
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used == 0 or s.active == 0:
continue
ix: int = self._taskbar_icon_x(cnt)
ic_bg: uint32 = self.rgb(uint32(40), uint32(40), uint32(70))
ic_bd: uint32 = self.rgb(uint32(80), uint32(80), uint32(140))
if i == self.focused:
ic_bg = self.rgb(uint32(60), uint32(60), uint32(120))
ic_bd = self.rgb(uint32(120), uint32(120), uint32(220))
elif s.minimized != 0:
ic_bg = self.rgb(uint32(30), uint32(30), uint32(50))
yy = y0
if yy < icon_y: yy = icon_y
while yy < y1 and yy < icon_y + TASKBAR_ICON_SZ:
rel: int = yy - icon_y
self.row_fill(ix, yy, TASKBAR_ICON_SZ, ic_bg)
if rel == 0 or rel == TASKBAR_ICON_SZ - 1:
self.row_fill(ix, yy, TASKBAR_ICON_SZ, ic_bd)
else:
self.row_fill(ix, yy, 1, ic_bd)
self.row_fill(ix + TASKBAR_ICON_SZ - 1, yy, 1, ic_bd)
yy += 1
if s.title[0] != 0:
ch_col: uint32 = self.rgb(uint32(255), uint32(255), uint32(255))
if i != self.focused:
ch_col = self.rgb(uint32(180), uint32(180), uint32(200))
ch_x: int = ix + (TASKBAR_ICON_SZ - vpui.CHAR_W) / 2
ch_y: int = icon_y + (TASKBAR_ICON_SZ - vpui.CHAR_H) / 2
if ch_y + vpui.CHAR_H > y0 and ch_y < y1:
self.draw_char(ch_x, ch_y, s.title[0], ch_col)
cnt += 1
# ---------------- 渲染 ----------------
def render_range(self, y0: int, y1: int):
# 窗口按 Z 序升序(选择排序,窗口数 ≤ 8);顺带缓存几何/样式,避免逐行查槽
order: [desk.DESK_MAX_WIN]int
sx: [desk.DESK_MAX_WIN]int
sy: [desk.DESK_MAX_WIN]int
sw: [desk.DESK_MAX_WIN]int
sh: [desk.DESK_MAX_WIN]int
smode: [desk.DESK_MAX_WIN]int
cnt: int = 0
top_z: int = -1
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used != 0 and s.active != 0 and s.minimized == 0:
order[cnt] = i
sx[i] = s.ux
sy[i] = s.uy
sw[i] = s.w
sh[i] = s.h
if s.style == STYLE_WIN7:
smode[i] = 2
elif s.style == STYLE_WIN11 or s.style == STYLE_WINXP:
smode[i] = 1
else:
smode[i] = 0
if s.z_order > top_z: top_z = s.z_order
cnt += 1
for a in range(cnt - 1):
for b in range(a + 1, cnt):
za: int = self.slot(order[a]).z_order
zb: int = self.slot(order[b]).z_order
if za > zb:
t: int = order[a]
order[a] = order[b]
order[b] = t
# 1) 背景:只铺"没有被窗口覆盖"的 x 区间;混合样式的可见行再单独铺一次底
yy: int = y0
while yy < y1:
xx: int = 0
for i in range(cnt):
w: int = order[i]
if yy < sy[w] or yy >= sy[w] + sh[w]: continue
if sx[w] > xx:
self.row_blit(xx, yy, self.bg_row, xx, sx[w] - xx)
ex: int = sx[w] + sw[w]
if ex > xx: xx = ex
if smode[w] != 0:
rel: int = yy - sy[w]
need: int = 0
if smode[w] == 2:
if rel < TITLE_H or rel >= sh[w] - WIN_RADIUS: need = 1
elif rel < WIN_RADIUS or rel >= sh[w] - WIN_RADIUS:
need = 1
if need != 0:
self.row_blit(sx[w], yy, self.bg_row, sx[w], sw[w])
if xx < self.fb_w:
self.row_blit(xx, yy, self.bg_row, xx, self.fb_w - xx)
yy += 1
# 2) 任务栏(先于窗口,窗口会盖住它 —— 与内核桌面同序)
self._draw_taskbar_rows(y0, y1)
# 3) 窗口
for i in range(cnt):
self.render_window_rows(order[i], y0, y1, top_z)
# 4) 光标
self.render_cursor_rows(y0, y1)
def render_window_rows(self, win: int, y0: int, y1: int, top_z: int):
s: desk.WinSlot* = self.slot(win)
wx: int = s.ux
wy: int = s.uy
wh: int = s.h
th: int = TITLE_H
style: int = s.style
active: int = 0
if s.z_order == top_z: active = 1
# 每样式调色板(对齐内核桌面 desktop.vp 的 _draw_app_window)
title_col: uint32 = self.rgb(uint32(50), uint32(50), uint32(90))
border_col: uint32 = self.rgb(uint32(100), uint32(100), uint32(180))
outline_col: uint32 = self.rgb(uint32(100), uint32(100), uint32(180))
content_col: uint32 = self.rgb(uint32(30), uint32(30), uint32(50))
has_separator: int = 1
rounded: int = 0
title_alpha: int = 255
if style == STYLE_WIN10:
has_separator = 0
if active != 0:
title_col = self.rgb(uint32(0), uint32(0), uint32(0))
content_col = self.rgb(uint32(32), uint32(32), uint32(32))
outline_col = self.rgb(uint32(80), uint32(80), uint32(80))
else:
title_col = self.rgb(uint32(32), uint32(32), uint32(32))
content_col = self.rgb(uint32(24), uint32(24), uint32(24))
outline_col = self.rgb(uint32(60), uint32(60), uint32(60))
border_col = title_col
elif style == STYLE_WIN11:
has_separator = 0
rounded = 1
if active != 0:
title_col = self.rgb(uint32(32), uint32(32), uint32(32))
content_col = self.rgb(uint32(32), uint32(32), uint32(32))
outline_col = self.rgb(uint32(70), uint32(70), uint32(70))
else:
title_col = self.rgb(uint32(40), uint32(40), uint32(40))
content_col = self.rgb(uint32(40), uint32(40), uint32(40))
outline_col = self.rgb(uint32(60), uint32(60), uint32(60))
border_col = title_col
elif style == STYLE_WIN7:
has_separator = 0
rounded = 1
title_alpha = 102
content_col = self.rgb(uint32(240), uint32(240), uint32(240))
if active != 0:
title_col = self.rgb(uint32(156), uint32(181), uint32(209))
outline_col = self.rgb(uint32(100), uint32(130), uint32(170))
else:
title_col = self.rgb(uint32(180), uint32(195), uint32(210))
outline_col = self.rgb(uint32(140), uint32(155), uint32(175))
border_col = title_col
elif style == STYLE_WINXP:
has_separator = 0
rounded = 1
content_col = self.rgb(uint32(235), uint32(235), uint32(235))
if active != 0:
title_col = self.rgb(uint32(107), uint32(158), uint32(212))
outline_col = self.rgb(uint32(43), uint32(87), uint32(151))
else:
title_col = self.rgb(uint32(140), uint32(160), uint32(195))
outline_col = self.rgb(uint32(100), uint32(115), uint32(145))
border_col = title_col
else:
if active != 0:
title_col = self.rgb(uint32(70), uint32(70), uint32(130))
border_col = self.rgb(uint32(130), uint32(130), uint32(220))
outline_col = border_col
# 阴影画在窗口本体之前(混到已合成的下层像素上),其行范围可能超出窗口外框
if SHADOW_ENABLED != 0:
self._draw_shadow(s, y0, y1)
if wy + wh <= y0 or wy >= y1: return
buf: uint32* = uint32*(self.shm + uint64(s.fb_off))
fw: int = int(s.fb_w)
fh: int = int(s.fb_h)
content_y0: int = wy + th + TITLE_GAP
r: int = WIN_RADIUS
yy: int = y0
if yy < wy: yy = wy
yend: int = y1
if yend > wy + wh: yend = wy + wh
while yy < yend:
rel: int = yy - wy
row_is_title: int = 0
row_col: uint32 = content_col
if rel < th:
row_is_title = 1
row_col = self._title_row_color(style, rel, th, active, title_col)
elif rel == th and has_separator != 0:
row_col = border_col # 标题/内容分隔线(CLASSIC)
if rounded != 0 and (rel < r or rel >= wh - r):
self._draw_round_row(s, yy, rel, row_col, row_is_title, outline_col, r,
title_alpha, buf, fw, fh, content_y0)
else:
self._draw_flat_row(s, yy, rel, row_col, row_is_title, border_col,
title_alpha, buf, fw, fh, content_y0)
yy += 1
self._draw_window_btns(s, y0, y1, th)
if s.title[0] != 0:
text_y: int = wy + (th - vpui.CHAR_H) / 2
if text_y + vpui.CHAR_H > y0 and text_y < y1:
tcol: uint32 = self.rgb(uint32(255), uint32(255), uint32(255))
if active == 0: tcol = self.rgb(uint32(170), uint32(170), uint32(170))
self.draw_str(wx + 8, text_y, &s.title[0], tcol)
def render_cursor_rows(self, y0: int, y1: int):
# 0 透明(不写像素,靠重画背景恢复),1 黑边,2 白心;同行连续同色合并成一次 row_fill。
cy0: int = self.cy
cy1: int = self.cy + CURSOR_BMP_H
if cy1 <= y0 or cy0 >= y1: return
y: int = cy0
if y < y0: y = y0
while y < cy1 and y < y1:
row: int = (y - cy0) * CURSOR_BMP_W
col: int = 0
while col < CURSOR_BMP_W:
v: uint8 = _CURSOR_ARROW[row + col]
if v == 0:
col += 1
continue
start: int = col
col += 1
while col < CURSOR_BMP_W and _CURSOR_ARROW[row + col] == v:
col += 1
px: int = self.cx + start
seg_w: int = col - start
if px + seg_w > 0 and px < self.fb_w:
if px < 0:
seg_w = seg_w + px
px = 0
if px + seg_w > self.fb_w:
seg_w = self.fb_w - px
if seg_w > 0:
if v == 1:
self.row_fill(px, y, seg_w, self.rgb(uint32(0), uint32(0), uint32(0)))
else:
self.row_fill(px, y, seg_w, self.rgb(uint32(255), uint32(255), uint32(255)))
y += 1
def render(self):
if self.dirty_any == 0: return
devfs.fb_wait_vblank(self.fb_fd)
# 逐脏段渲染并立刻提交。**不要**合并成"最小脏行到最大脏行"的一整段:窗口在顶部、
# 任务栏在底部时那会退化成每帧全屏渲染 + 全屏提交(4MB memcpy),帧率直接崩。
# 段内先全画进 back 再逐行提交,保证每一行提交出去的都是完整一帧的内容。
y: int = 0
while y < self.fb_h:
if self.dirty[y] == 0:
y += 1
continue
y0: int = y
while y < self.fb_h and self.dirty[y] != 0:
y += 1
y1: int = y
self.render_range(y0, y1)
yy: int = y0
while yy < y1:
if self.dirty[yy] != 0:
# fb 行距 = pitch_px,back 行距 = fb_w
string.memcpy(rawptr(uint64(self.fb) + uint64(yy * self.pitch_px) * uint64(4)),
rawptr(uint64(self.back) + uint64(yy * self.fb_w) * uint64(4)),
uint64(self.fb_w) * uint64(4))
yy += 1
self.clear_dirty()
# ---------------- 事件投递 ----------------
def push_event(self, win: int, etype: uint32, a: int, b: int, c: int, d: int) -> int:
"""按收件窗口的 pid 投进该进程自己的事件环。a..d 是消息的 4 个载荷位。"""
s: desk.WinSlot* = self.slot(win)
self.evt.type = etype
self.evt.win = win
self.evt.pid = s.pid
self.evt.a = a
self.evt.b = b
self.evt.c = c
self.evt.d = d
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, d: int) -> int:
"""建窗答复专用:此时窗口槽还没有 pid,直接按请求里带的 pid 投递。"""
self.evt.type = etype
self.evt.win = win
self.evt.pid = pid
self.evt.a = a
self.evt.b = b
self.evt.c = c
self.evt.d = d
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):
# pid 随请求携带(窗口还没诞生),标题内联在消息里
_sb: [96]char
viperlib.snprintf(&_sb, 96, "[explorer] create style=%d w=%d h=%d\n",
int(m.style), m.c, m.d)
devfs.serial_puts(&_sb)
nw: int = self.alloc_window(m.pid, m.a, m.b, m.c, m.d, int(m.style))
if nw >= 0:
ns: desk.WinSlot* = self.slot(nw)
string.memcpy(&ns.title, &m.title, uint64(desk.DESK_TITLE_MAX))
self.mark_all_windows()
self.mark_titles()
self.push_event_pid(m.pid, nw, uint32(desk.DESK_EVT_CREATED), nw, 0, 0, 0)
return
win: int = m.win
if win < 0 or win >= desk.DESK_MAX_WIN: return
s: desk.WinSlot* = self.slot(win)
if s.used == 0: return
ty: uint32 = m.type
if ty == uint32(desk.DESK_REQ_DESTROY):
self.mark(s.uy, s.h)
s.used = 0
if self.focused == win: self.focused = -1
# 拖拽中的窗口被销毁:必须清掉 drag_win,否则后续移动会继续操作这个死槽位。
if self.drag_win == win: self.drag_win = -1
self.mark_all_windows()
self.mark_titles()
elif ty == uint32(desk.DESK_REQ_MOVE):
self.mark_win(s.uy, s.h)
s.ux = m.a
s.uy = m.b
self.mark_win(s.uy, s.h)
elif ty == uint32(desk.DESK_REQ_RESIZE):
self.mark_win(s.uy, s.h)
new_ch: int = m.b - TITLE_H - TITLE_GAP
if new_ch < 1: new_ch = 1
# arena 是 bump 分配、不能回收:换尺寸一律重新切片,旧块留给后续复用
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_win(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):
self.next_z += 1
s.z_order = self.next_z
self.focused = win
self.mark(0, self.fb_h)
def poll_requests(self):
# 单轮最多处理一整圈:head 已前进,剩余请求留到下一轮,不会重复也不会自旋。
n: int = 0
while n < desk.DESK_RING_SLOTS and desk.ring_pop(self.shm + desk.desk_req_ring_off(), &self.msg) != 0:
self.handle_request(self.msg)
n += 1
# ---------------- 输入 ----------------
def hit_test(self, x: int, y: int) -> int:
best: int = -1
best_z: int = -1
for i in range(desk.DESK_MAX_WIN):
s: desk.WinSlot* = self.slot(i)
if s.used != 0 and s.active != 0 and s.minimized == 0:
if x >= s.ux and x < s.ux + s.w and y >= s.uy and y < s.uy + s.h:
if s.z_order > best_z:
best_z = s.z_order
best = i
return best
def focus(self, win: int):
# 只允许聚焦"活"的槽位:死槽位上的 z_order/focused 会把焦点指向已销毁窗口。
if win < 0 or win >= desk.DESK_MAX_WIN: return
s: desk.WinSlot* = self.slot(win)
if s.used == 0: return
self.next_z += 1
s.z_order = self.next_z
self.focused = win
# 层级变化会改变互相覆盖的关系:所有可见窗口整体置脏
self.mark_all_windows()
self.mark_titles()
def _move_cursor(self, dx: int, dy: int) -> int:
"""把相对位移累加到光标并钳制到屏幕内;返回是否发生移动。"""
if dx == 0 and dy == 0: return 0
self.cx = self.cx + dx
self.cy = self.cy + dy
if self.cx < 0: self.cx = 0
if self.cy < 0: self.cy = 0
if self.cx >= self.fb_w: self.cx = self.fb_w - 1
if self.cy >= self.fb_h: self.cy = self.fb_h - 1
return 1
def poll_input(self):
global _mouse_logged, _kbd_logged
# --- 键盘:设备侧已给出 ASCII,逐字符投给焦点窗口(无焦点/失效则顶层窗口)---
kb: [16]char
kn: int = fs.read(self.kfd, &kb, uint32(16))
if kn > 0:
# 【诊断·临时】首个键盘字节:证明 内核 → devfs → explorer 这段是通的
if _kbd_logged == 0:
_kbd_logged = 1
_kb: [96]char
viperlib.snprintf(&_kb, 96, "[explorer] kbd n=%d ch=%d\n", kn, int(kb[0]))
devfs.serial_puts(&_kb)
# 无目标窗口(target < 0)时本批字节丢弃:这里只读一次、不重试,不会空转。
target: int = self._key_target()
if target >= 0:
i: int = 0
while i < kn:
self.push_event(target, uint32(desk.DESK_EVT_KEY), int(kb[i]), 0, 0, 0)
i += 1
# --- 鼠标:内核每次返回一个包。serve 一轮耗时毫秒级,若每轮只取一个包,
# PS/2 100Hz 的连续输入会排队累积 ⇒ 光标滞后;故把已到的包一次取完,
# 位移累加、按键以最后一个包为准。---
mp: [16]uint8
mn: int = fs.read(self.mfd, &mp, uint32(16))
if mn > 0:
# 【诊断·临时】首个鼠标包:证明 中断→devfs→本进程 链路已通
if _mouse_logged == 0:
_mouse_logged = 1
_mb: [128]char
viperlib.snprintf(&_mb, 128, "[explorer] mouse pkt n=%d btn=%d\n", mn, int(mp[0]))
devfs.serial_puts(&_mb)
moved: int = 0
if mn >= 9:
moved = self._move_cursor(int(*(int32*(uint64(&mp) + uint64(1)))),
int(*(int32*(uint64(&mp) + uint64(5)))))
# 批次内逐包跟踪按下/释放沿:只看最后一包会漏掉"按下+释放落在同一批"的快速点击
cur: int = self.btn_down
saw_press: int = 0
b0: int = int(mp[0]) & 1
if b0 != 0 and cur == 0: saw_press = 1
cur = b0
mn = fs.read(self.mfd, &mp, uint32(16))
while mn > 0:
if mn >= 9:
if self._move_cursor(int(*(int32*(uint64(&mp) + uint64(1)))),
int(*(int32*(uint64(&mp) + uint64(5))))) != 0:
moved = 1
bi: int = int(mp[0]) & 1
if bi != 0 and cur == 0: saw_press = 1
cur = bi
mn = fs.read(self.mfd, &mp, uint32(16))
if moved != 0:
self.mark(self.pcy - 1, CURSOR_BMP_H + 2)
self.mark(self.cy - 1, CURSOR_BMP_H + 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 saw_press != 0:
self.btn_down = 1
# 任务栏图标优先:命中则激活并跳过窗口命中/拖动
hit: int = -1
tw: int = self._taskbar_icon_hit(self.cx, self.cy)
if tw >= 0:
self._activate_from_taskbar(tw)
else:
hit = self.hit_test(self.cx, self.cy)
if hit >= 0:
s: desk.WinSlot* = self.slot(hit)
# 关闭键热区按样式尺寸算,必须与 _draw_window_btns 一致
cw: int = self._close_btn_w(s.style)
ch: int = self._close_btn_h(s.style)
bx: int = s.ux + s.w - cw - self._close_btn_pad(s.style)
by: int = s.uy + (TITLE_H - ch) / 2
in_close: int = 0
if self.cx >= bx and self.cx < bx + cw and self.cy >= by and self.cy < by + ch:
in_close = 1
self.focus(hit)
if in_close != 0:
# 关闭:通知一次 CLOSE 后把槽位置空回收;window.vp 的 is_active()
# 见到 used==0 即返回 0,app 的 while 循环自然退出。
# 像素缓冲不回收(arena 是 bump 分配),只放槽位。
self.push_event(hit, uint32(desk.DESK_EVT_CLOSE), 0, 0, 0, 0)
self.mark_win(s.uy, s.h)
s.used = 0
if self.focused == hit: self.focused = -1
if self.drag_win == hit: self.drag_win = -1
self.mark_titles()
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_down 卡在 1,后续点击永远检测不到按下沿。
if cur == 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
if self.btn_down != 0 and self.drag_win >= 0:
s2: desk.WinSlot* = self.slot(self.drag_win)
if s2.used != 0:
self.mark_win(s2.uy, s2.h)
s2.ux = self.cx - self.drag_dx
s2.uy = self.cy - self.drag_dy
self.mark_win(s2.uy, s2.h)
# ---------------- 主循环 ----------------
def serve(self):
# 进主循环前先整屏刷一次:内核启动期的 VGA/gfx 测试内容(例如蓝色测试块)不在任何
# 脏行里,只靠后续增量渲染会永远留在屏幕上。
self.mark_all()
self.render()
while True:
self.poll_requests()
self.poll_input()
self.render()
process.yield_cpu()