feat(explorer): 还原窗口样式、阴影、任务栏与三枚窗口按钮,精简注释

对齐被删除的内核桌面 desktop.vp 的视觉:窗口改为四边框 + 标题栏(活动/非活动配色)+ 标题内容分隔线 + 内容按 1px 内缩合成,并按 WinSlot.style 选择样式配色;新增最小化/最大化/关闭三枚按钮;新增矩形窗口阴影(SHADOW_* + self._blend 按 framebuffer 格式做 alpha 混合);任务栏改为深色底 + 分 opper线 + 蓝色 logo 圆盘 + ViperOS 文本 + 每个活动窗口的带边框图标(焦点/普通/最小化三色)与标题首字符,高度 28→50 对齐旧桌面。置脏范围改用 mark_win(含阴影带)/ mark_titles(活动窗口切换时刷新所有标题栏)。同时精简全文注释(不保留历史沿革叙述)。任务栏时钟未实现:用户态无时间源;圆角未实现:style 尚未透传到 CREATE 请求;最小化/最大化仅外观未接线。
This commit is contained in:
TermiNexus committed 2026-10-16 21:47:44 +08:00
1 parent e41701e201
commit ecabd59518
1 file changed
+393 -90
+393 -90
View File
@@ -3,42 +3,50 @@ 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 点阵字体表(vpsdk/font 尚无独立模块,不抄第二份)
import vpsdk.vpui as vpui # 复用它的 8x16 点阵字体表
from stdint import *
import string
import viperlib
# 【诊断·临时】首次收到鼠标包的证据(0 = 还没打过)。用于把"内核没有鼠标中断"与
# "explorer 收到包但没画光标/没投事件"这两种完全不同的故障方向分开。
# 【诊断·临时】首个鼠标包已收到的标记(0 = 未收到)
_mouse_logged: int = 0
# ============================================================================
# explorer 的窗口服务端 + 合成器(S3c)。
#
# 数据面:app 把窗口像素写进共享区 arena 里自己那块,压一条 DESK_REQ_FLUSH;
# 本服务端合成时按 Z 序把它们逐行 memcpy 进 framebuffer(不搬像素)。
# 控制面:请求环(app → explorer)与每窗口事件环(explorer → app)。
# 输入:读数从 /dev/input/keyboard、/dev/input/mouse(内核 devfs 设备已完成
# 扫描码→ASCII 与鼠标包解码),本服务端负责光标、命中测试、焦点与事件投递。
#
# 本切片未做(后续):最小化、窗口侧边框与样式区分、任务栏时钟(用户态还没有时间源)、
# 拖边缩放、心跳无响应检测。字体表直接复用 vpui.font(8x16 点阵),未另抄一份。
# 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 CLOSE_SZ = 14
define CLOSE_PAD = 5
# 光标是 16×16 位图,模板直接取自 VKernel/Kernel/services/desktop.vp(S3e 删除前的实现,
# 见 git 13eee8e^):0 = 透明、1 = 黑边、2 = 白心。同一个历史文件里还有 4 套模板,
# 将来接上 WinSlot.cursor 即可启用:_CURSOR_IBEAM / _CURSOR_RESIZE_EW /
# _CURSOR_RESIZE_NS / _CURSOR_RESIZE_NWSE。
define BTN_GAP = 2
define CURSOR_BMP_W = 16
define CURSOR_BMP_H = 16
define TASKBAR_H = 28
# 任务栏与窗口装饰尺寸(对齐内核桌面 desktop.vp)
define TASKBAR_H = 50
define TASKBAR_LOGO_SZ = 32
define TASKBAR_ICON_SZ = 36
define TASKBAR_ICON_PAD = 6
# 窗口样式(与 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,
@@ -66,8 +74,8 @@ class DeskServer:
fb_w: int
fb_h: int
pitch_px: int
fmt: uint32 # 0 = RGBA, 1 = BGRA(与内核 gfx 口径一致)
bg_row: uint32* # 一行背景的预构造副本(避免逐像素填)
fmt: uint32 # 0 = RGBA, 1 = BGRA
bg_row: uint32* # 一行背景的预构造副本
# 共享区
shm: uint64
# 输入
@@ -97,6 +105,31 @@ class DeskServer:
return (uint32(255) << 24) | (r << 16) | (g << 8) | b
return (uint32(255) << 24) | (b << 16) | (g << 8) | r
# ---- 按 framebuffer 格式把 fg 以 alpha/255 混到 bg 上 ----
def _blend(self, bg: uint32, fg: uint32, a: int) -> uint32:
ia: int = 255 - a
if self.fmt == uint32(1):
br: int = int((bg >> 16) & 0xFF)
bg_g: int = int((bg >> 8) & 0xFF)
bg_b: int = int(bg & 0xFF)
fr: int = int((fg >> 16) & 0xFF)
fg_g: int = int((fg >> 8) & 0xFF)
fg_b: int = int(fg & 0xFF)
r: int = (fr * a + br * ia) >> 8
g: int = (fg_g * a + bg_g * ia) >> 8
b: int = (fg_b * a + bg_b * ia) >> 8
return (uint32(255) << 24) | (uint32(r) << 16) | (uint32(g) << 8) | uint32(b)
br = int(bg & 0xFF)
bg_g = int((bg >> 8) & 0xFF)
bg_b = int((bg >> 16) & 0xFF)
fr = int(fg & 0xFF)
fg_g = int((fg >> 8) & 0xFF)
fg_b = int((fg >> 16) & 0xFF)
r = (fr * a + br * ia) >> 8
g = (fg_g * a + bg_g * ia) >> 8
b = (fg_b * a + bg_b * ia) >> 8
return (uint32(255) << 24) | (uint32(b) << 16) | (uint32(g) << 8) | uint32(r)
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
@@ -129,7 +162,7 @@ class DeskServer:
uint64(syscall.PROT_READ | syscall.PROT_WRITE),
uint64(syscall.MAP_PRIVATE | syscall.MAP_ANONYMOUS),
syscall.MMAP_FD_ANON, 0))
col: uint32 = self.rgb(uint32(18), uint32(24), uint32(32))
col: uint32 = self.rgb(uint32(25), uint32(25), uint32(35))
for i in range(w):
self.bg_row[i] = col
self.mark_all()
@@ -154,6 +187,17 @@ class DeskServer:
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)
# 活动窗口切换会改变标题栏配色:把所有窗口的标题栏行置脏
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)
# ---------------- 基本绘制 ----------------
def row_fill(self, x0: int, y: int, w: int, col: uint32):
if y < 0 or y >= self.fb_h or w <= 0: return
@@ -165,12 +209,11 @@ class DeskServer:
if x + dw > self.fb_w:
dw = self.fb_w - x
if dw <= 0: return
# rep stosd 整段填色(原为逐像素循环)——任务栏/标题栏每帧要填上万像素,是热点之一。
base: int = y * self.pitch_px
string.memset32(rawptr(uint64(self.fb) + 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)。裁剪到屏幕内。"""
"""把 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
@@ -190,13 +233,18 @@ class DeskServer:
def slot(self, win: int) -> desk.WinSlot*:
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。
# 最高 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
尺寸约定(必须与 vpui 对齐):入参 w/h 是**含标题栏**的外框;内容的像素缓冲是
w × (h - TITLE_H - TITLE_GAP),与 vpui 的 `bh = h - TITLE_H - 2` 一致。
arena 只是 bump 分配、不支持回收,所以像素缓冲按 w*h(外框)预留一点余量。
"""
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):
@@ -226,11 +274,12 @@ class DeskServer:
s.z_order = self.next_z
self.next_z += 1
self.focused = i
self.mark(y, h)
self.mark_win(y, h)
self.mark_titles()
return i
return -1
# ---------------- 文本(8x16 点阵,字体表直接复用 vpui.font)----------------
# ---------------- 文本(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
@@ -258,19 +307,197 @@ class DeskServer:
cx += vpui.CHAR_W
i += 1
# ---------------- 窗口阴影 ----------------
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
sa: int = SHADOW_ALPHA
shadow_col: uint32 = self.rgb(uint32(0), uint32(0), uint32(0))
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:
px: int = wx - sl
if px < 0: px = 0
xend: int = wx + ww + sr
if xend > self.fb_w: xend = self.fb_w
while px < xend:
if px < wx or px >= wx + ww or yy < wy or yy >= wy + wh:
cpx: int = px
if cpx < wx: cpx = wx
elif cpx > wx + ww - 1: cpx = wx + ww - 1
cpy: int = yy
if cpy < wy: cpy = wy
elif cpy > wy + wh - 1: cpy = wy + wh - 1
dx: int = px - cpx
dy: int = yy - cpy
adx: int = dx
if adx < 0: adx = 0 - adx
ady: int = dy
if ady < 0: ady = 0 - ady
dist: int = adx
if ady > dist: dist = ady
dist = dist + ((adx + ady - dist) * 3) / 8
me_x: int = sl
if dx > 0: me_x = sr
me_y: int = st
if dy > 0: me_y = sb
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
a: int = (sa * t_inv * t_inv) / 65536
if a > 0:
idx: int = yy * self.pitch_px + px
self.fb[idx] = self._blend(self.fb[idx], shadow_col, a)
px += 1
yy += 1
# ---------------- 窗口按钮(按行绘字形,CLASSIC 配色)----------------
def _draw_close_row(self, bx: int, by: int, sz: int, yy: int):
self.row_fill(bx, yy, sz, self.rgb(uint32(40), uint32(40), uint32(70)))
x_col: uint32 = self.rgb(uint32(150), uint32(150), uint32(150))
cx: int = bx + sz / 2
cy: int = by + sz / 2
i: int = yy - cy
if i >= 0 - 3 and i <= 3:
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, sz: int, yy: int):
cx: int = bx + sz / 2
cy: int = by + sz / 2
if yy == cy + 3:
self.row_fill(cx - 5, yy, 11, self.rgb(uint32(180), uint32(180), uint32(180)))
def _draw_max_row(self, bx: int, by: int, sz: int, yy: int):
col: uint32 = self.rgb(uint32(180), uint32(180), uint32(180))
cx: int = bx + sz / 2
cy: int = by + sz / 2
q: int = 4
dy: int = yy - cy
if dy < 0: dy = 0 - dy
if dy > q: return
if dy == q or yy == cy - q + 1:
self.row_fill(cx - q, yy, 2 * q + 1, col)
else:
self.row_fill(cx - q, yy, 1, col)
self.row_fill(cx + q, yy, 1, col)
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
sz: int = CLOSE_SZ
by: int = wy + (th - sz) / 2
close_x: int = wx + ww - sz - CLOSE_PAD
max_x: int = close_x - sz - BTN_GAP
min_x: int = max_x - sz - BTN_GAP
yy: int = y0
if yy < by: yy = by
yend: int = y1
if yend > by + sz: yend = by + sz
while yy < yend:
self._draw_close_row(close_x, by, sz, yy)
self._draw_min_row(min_x, by, sz, yy)
self._draw_max_row(max_x, by, sz, yy)
yy += 1
# ---------------- 任务栏 ----------------
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.fb[yy * self.pitch_px + px] = lc_core
else:
self.fb[yy * self.pitch_px + 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_x0: int = logo_x + TASKBAR_LOGO_SZ + 80
icon_y: int = bar_y + (TASKBAR_H - TASKBAR_ICON_SZ) / 2
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 = icon_x0 + cnt * (TASKBAR_ICON_SZ + TASKBAR_ICON_PAD)
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):
# 1) 背景
for yy in range(y0, y1):
self.row_blit(0, yy, self.bg_row, 0, self.fb_w)
# 2) 任务栏(在窗口之前画 —— 与旧内核桌面同序,窗口会盖住它)
tb_y: int = self.fb_h - TASKBAR_H
if y1 > tb_y:
for yy in range(y0, y1):
if yy >= tb_y:
self.row_fill(0, yy, self.fb_w, self.rgb(uint32(28), uint32(34), uint32(48)))
self.draw_str(10, tb_y + (TASKBAR_H - vpui.CHAR_H) / 2, "ViperOS",
self.rgb(uint32(200), uint32(210), uint32(230)))
# 2) 任务栏(先于窗口,窗口会盖住它 —— 与内核桌面同序)
self._draw_taskbar_rows(y0, y1)
# 3) 窗口按 Z 序升序(选择排序,窗口数 ≤ 8)
order: [desk.DESK_MAX_WIN]int
cnt: int = 0
@@ -287,7 +514,6 @@ class DeskServer:
t: int = order[a]
order[a] = order[b]
order[b] = t
# 3) 逐个窗口:只重画落在 [y0,y1) 的那几行
for i in range(cnt):
self.render_window_rows(order[i], y0, y1)
# 4) 光标
@@ -295,34 +521,112 @@ class DeskServer:
def render_window_rows(self, win: int, y0: int, y1: int):
s: desk.WinSlot* = self.slot(win)
x: int = s.ux
w: int = s.w
for yy in range(y0, y1):
if yy < s.uy or yy >= s.uy + s.h: continue
rel: int = yy - s.uy
if rel < TITLE_H:
self.row_fill(x, yy, w, self.rgb(uint32(52), uint32(78), uint32(120)))
# 关闭按钮(红块)落在这一行就画上
bx: int = x + w - CLOSE_SZ - CLOSE_PAD
by: int = s.uy + (TITLE_H - CLOSE_SZ) / 2
if yy >= by and yy < by + CLOSE_SZ:
self.row_fill(bx, yy, CLOSE_SZ, self.rgb(uint32(210), uint32(70), uint32(70)))
wx: int = s.ux
wy: int = s.uy
ww: int = s.w
wh: int = s.h
th: int = TITLE_H
style: int = s.style
active: int = 0
if s.z_order == self._top_z(): active = 1
# 样式配色:CLASSIC 为默认;其余样式仅配色/分隔线可区分(几何同 CLASSIC)
title_col: uint32 = self.rgb(uint32(50), uint32(50), uint32(90))
border_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
if style == STYLE_WIN10:
has_separator = 0
if active != 0:
title_col = self.rgb(uint32(0), uint32(0), uint32(0))
border_col = self.rgb(uint32(0), uint32(0), uint32(0))
content_col = self.rgb(uint32(32), uint32(32), uint32(32))
else:
cy: int = rel - TITLE_H - TITLE_GAP
cw: int = int(s.fb_w)
if cw > w: cw = w
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)
# 标题文字:本段脏行与标题栏相交就整条重画(文字很短,代价可忽略)
if y0 < s.uy + TITLE_H and y1 > s.uy:
self.draw_str(x + 8, s.uy + (TITLE_H - vpui.CHAR_H) / 2, &s.title[0],
self.rgb(uint32(236), uint32(240), uint32(248)))
title_col = self.rgb(uint32(32), uint32(32), uint32(32))
border_col = self.rgb(uint32(32), uint32(32), uint32(32))
content_col = self.rgb(uint32(24), uint32(24), uint32(24))
elif style == STYLE_WIN11:
has_separator = 0
if active != 0:
title_col = self.rgb(uint32(32), uint32(32), uint32(32))
border_col = self.rgb(uint32(32), uint32(32), uint32(32))
content_col = self.rgb(uint32(32), uint32(32), uint32(32))
else:
title_col = self.rgb(uint32(40), uint32(40), uint32(40))
border_col = self.rgb(uint32(40), uint32(40), uint32(40))
content_col = self.rgb(uint32(40), uint32(40), uint32(40))
elif style == STYLE_WIN7:
has_separator = 0
if active != 0:
title_col = self.rgb(uint32(156), uint32(181), uint32(209))
border_col = self.rgb(uint32(156), uint32(181), uint32(209))
content_col = self.rgb(uint32(240), uint32(240), uint32(240))
else:
title_col = self.rgb(uint32(180), uint32(195), uint32(210))
border_col = self.rgb(uint32(180), uint32(195), uint32(210))
content_col = self.rgb(uint32(240), uint32(240), uint32(240))
elif style == STYLE_WINXP:
has_separator = 0
if active != 0:
title_col = self.rgb(uint32(107), uint32(158), uint32(212))
border_col = self.rgb(uint32(107), uint32(158), uint32(212))
content_col = self.rgb(uint32(235), uint32(235), uint32(235))
else:
title_col = self.rgb(uint32(140), uint32(160), uint32(195))
border_col = self.rgb(uint32(140), uint32(160), uint32(195))
content_col = self.rgb(uint32(235), uint32(235), uint32(235))
else:
if active != 0:
title_col = self.rgb(uint32(70), uint32(70), uint32(130))
border_col = self.rgb(uint32(130), uint32(130), uint32(220))
# 阴影画在窗口本体之前(混到已合成的下层像素上),其行范围可能超出窗口外框
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
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
if rel == 0:
self.row_fill(wx, yy, ww, border_col) # 上边框
elif rel < th:
self.row_fill(wx, yy, ww, title_col) # 标题栏
elif rel == th:
if has_separator != 0:
self.row_fill(wx, yy, ww, border_col) # 标题/内容分隔线
else:
self.row_fill(wx, yy, ww, content_col)
elif rel == th + 1:
self.row_fill(wx, yy, ww, content_col)
elif rel == wh - 1:
self.row_fill(wx, yy, ww, border_col) # 下边框
else:
self.row_fill(wx, yy, ww, content_col)
row: int = yy - content_y0
if row >= 0 and row < fh:
self.row_blit(wx + 1, yy, buf, row * fw + 1, ww - 2)
if rel > 0 and rel < wh - 1:
self.row_fill(wx, yy, 1, border_col) # 左边框
self.row_fill(wx + ww - 1, yy, 1, border_col) # 右边框
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):
# 按 16×16 位图模板绘制光标(模板取自 desktop.vp 的历史实现):
# 0 = 透明 —— **不写像素**,背景由 render_range 先重画窗口内容恢复,所以移动不留残影;
# 1 = 黑边(外轮廓)、2 = 白心。同一行里连续的同色像素合并成一次 row_fill。
# 0 透明(不写像素,靠重画背景恢复),1 黑边,2 白心;同行连续同色合并成一次 row_fill。
cy0: int = self.cy
cy1: int = self.cy + CURSOR_BMP_H
if cy1 <= y0 or cy0 >= y1: return
@@ -357,8 +661,7 @@ class DeskServer:
def render(self):
if self.dirty_any == 0: return
# 写屏前等垂直回扫:直接写 framebuffer 会与显示器扫描并发 ⇒ 撕裂/闪烁。
# 用户态没有 IO 权限(不能 inb 0x3DA),由内核代等。
# 写屏前等垂直回扫(用户态不能 inb 0x3DA,由内核代等),避免撕裂
devfs.fb_wait_vblank(self.fb_fd)
y: int = 0
while y < self.fb_h:
@@ -373,7 +676,7 @@ class DeskServer:
# ---------------- 事件投递 ----------------
def push_event(self, win: int, etype: uint32, a: int, b: int, c: int, d: int) -> int:
"""按收件窗口的 pid 投进**该进程自己的**事件环。a..d 是消息的 4 个载荷位。"""
"""按收件窗口的 pid 投进该进程自己的事件环。a..d 是消息的 4 个载荷位。"""
s: desk.WinSlot* = self.slot(win)
self.evt.type = etype
self.evt.win = win
@@ -398,7 +701,7 @@ class DeskServer:
# ---------------- 请求处理 ----------------
def handle_request(self, m: desk.DeskMsg):
if m.type == uint32(desk.DESK_REQ_CREATE):
# CREATE:pid 由请求携带(窗口还没诞生,事件环只能按 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)
@@ -411,19 +714,20 @@ class DeskServer:
if s.used == 0: return
ty: uint32 = m.type
if ty == uint32(desk.DESK_REQ_DESTROY):
self.mark(s.uy, s.h)
self.mark_win(s.uy, s.h)
s.used = 0
if self.focused == win: self.focused = -1
self.mark_titles()
elif ty == uint32(desk.DESK_REQ_MOVE):
self.mark(s.uy, s.h)
self.mark_win(s.uy, s.h)
s.ux = m.a
s.uy = m.b
self.mark(s.uy, s.h)
self.mark_win(s.uy, s.h)
elif ty == uint32(desk.DESK_REQ_RESIZE):
self.mark(s.uy, s.h)
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 分配、不能回收:换尺寸一律重新切片,旧块留给后续复用(不做 free)
# 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
@@ -431,7 +735,7 @@ class DeskServer:
s.h = m.b
s.fb_w = uint32(m.a)
s.fb_h = uint32(new_ch)
self.mark(s.uy, s.h)
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)
@@ -466,10 +770,10 @@ class DeskServer:
self.next_z += 1
s.z_order = self.next_z
self.focused = win
self.mark(s.uy, s.h)
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
@@ -490,14 +794,13 @@ class DeskServer:
while i < kn:
self.push_event(self.focused, uint32(desk.DESK_EVT_KEY), int(kb[i]), 0, 0, 0)
i += 1
# --- 鼠标:内核每次返回**一个包**的相对位移。serve 一轮要跑 poll_requests + poll_input
# + render(毫秒级),若每轮只取一个包,PS/2 100Hz 的连续输入会排队累积 ⇒ 光标
# 明显滞后。所以这里把队列里已到的包一次取完:位移全部累加,按键以最后一个包为准
# (那才是"当前状态")。
# --- 鼠标:内核每次返回一个包。serve 一轮耗时毫秒级,若每轮只取一个包,
# PS/2 100Hz 的连续输入会排队累积 ⇒ 光标滞后;故把已到的包一次取完,
# 位移累加、按键以最后一个包为准。---
mp: [16]uint8
mn: int = fs.read(self.mfd, &mp, uint32(16))
if mn > 0:
# 【诊断·临时】首个鼠标包:证明 内核 PS/2 中断 → devfs mouse 设备 → 本进程 这条链是通的。
# 【诊断·临时】首个鼠标包:证明 中断→devfs→本进程 链路已通
if _mouse_logged == 0:
_mouse_logged = 1
_mb: [128]char
@@ -541,21 +844,21 @@ class DeskServer:
in_close = 1
self.focus(hit)
if in_close != 0:
# 关闭:先通知一次 CLOSE,随后把槽位置空回收 —— window.vp 的 is_active()
# 见到 used==0 即返回 0,app 的 `while is_active()` 自然退出并调 process.exit。
# 像素缓冲不回收(arena 是 bump 分配),只放槽位 ⇒ 缓冲区永不复用,
# 因此不会出现"新窗口复用旧块、被尚未退出的老 app 写坏"的别名问题。
# 关闭:通知一次 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(s.uy, s.h)
self.mark_win(s.uy, s.h)
s.used = 0
if self.focused == hit: self.focused = -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
# 内容区 ⇒ 按窗口内坐标把点击投给 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)
@@ -573,10 +876,10 @@ class DeskServer:
elif self.btn_down != 0 and self.drag_win >= 0:
s2: desk.WinSlot* = self.slot(self.drag_win)
if s2.used != 0:
self.mark(s2.uy, s2.h)
self.mark_win(s2.uy, s2.h)
s2.ux = self.cx - self.drag_dx
s2.uy = self.cy - self.drag_dy
self.mark(s2.uy, s2.h)
self.mark_win(s2.uy, s2.h)
# ---------------- 主循环 ----------------
def serve(self):