buninu-land
v0.4.1
Published
A Wayland compositor in JavaScript and WebAssembly for Bun
Maintainers
Readme
Buninu Land
English | 繁體中文
A Wayland compositor in JavaScript and WebAssembly for Bun
警告: 本專案仍處於非常早期的開發階段,功能尚不完整,行為與 API 可能大幅變動,目前不適合日常使用。
用 Bun 直接執行的 Wayland compositor。第一版支援 headless 軟體合成,已用系統的 weston-simple-shm 驗證動畫,以及 Casty 的啟動、CDP 設定和畫面接收流程。沒有 build step、npm runtime dependencies 或 compositor GPU 繪製路徑。
執行
標準 Wayland 傳輸需要 Linux 或 Android 64-bit(aarch64 或 x86_64);Windows 具名 mapping 實驗性傳輸見後文。需要提供 Bun.XML、Bun.Image、bun:ffi 的 Bun。process.platform === 'android' 時載入 Bionic libc.so/__errno;Linux 優先載入 libc.so.6/__errno_location。已在 Linux aarch64、Bun 1.4.3-canary.1 實測;作者也已完成 Android 原生環境驗證。
直接執行專案入口,就會自動啟動 Casty,再由 Casty 帶起 compositor:
bun index.js套件名稱為 buninu-land,全域指令也叫 buninu-land。發布至 npm 後可使用:
npm install -g buninu-land
buninu-land
# 或不全域安裝:
npx buninu-land
bunx buninu-land
bun x buninu-land需先有 Bun。啟動器沿用 browser 的流程:PATH 同時有 npx 與 bun 時使用 npx,否則使用 bunx,再否則使用 bun x。Casty 套件為 @drxiaozhi/casty(GitHub 原始碼);尚未安裝/快取時,套件執行器可能下載它。npx 保留原有安裝詢問,不加 --yes。
發布後第一次執行 npx buninu-land,npx 會取得 Buninu Land,接著 launcher
會在需要時自動下載終端前端
@drxiaozhi/casty
(GitHub 原始碼)。
bun index.js 使用同一套套件啟動流程;headless 模式不會下載 Casty。
--casty 為預設前端;--headless 只執行 compositor。--win32 使用 Casty 現有的 Windows 原生視窗;--gtk、--appkit、--qt 仍為占位命令,會以狀態碼 2 離開。第一個參數選擇前端,其餘參數原樣傳遞。
bun index.js --help # 或 -h
bun index.js --version # 或 -V
bun index.js --readme
bun index.js --readme-zh # 或 --docs
bun index.js --headless --help
bun index.js --casty --helpLand launcher 會自動加上 --no-shortcuts,將頁面上的全部按鍵傳給 Wayland client。點網址列左側 @ 開啟空白 cmd>,輸入 q 再按 Enter 退出 casty。點網址仍可編輯,編輯時保留快捷鍵;需搭配新版 casty。
也可自行從 Casty 啟動:
export CASTY_BROWSER="$PWD/compositor.js"
bunx @drxiaozhi/casty --no-shortcuts about:blankSocket 目錄依序採用明確設定的 $XDG_RUNTIME_DIR、$TMPDIR、系統暫存目錄。Socket 直接放在該目錄,不另加子目錄;自動選取第一個未被鎖定的名稱:wayland-buninu、wayland-buninu1、wayland-buninu2,依此類推。例如另一個終端:
WAYLAND_DISPLAY="${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/wayland-buninu" weston-simple-shm未指定 --socket 時,用核心的 flock 排他鎖判斷是否仍在使用,不以 socket 檔案存在與否判斷。程序退出或崩潰後會釋放鎖,下次啟動可回收殘留 socket 和前面的空缺編號。明確指定 --socket=NAME 時不自動改名;被占用會報錯。啟動輸出的 WAYLAND_DISPLAY 是本次實際名稱/路徑,客戶端應使用它。多開 headless 時可加 --remote-debugging-port=0,讓 CDP port 也自動分配。
啟動 Konsole 時,請加上 QT_QPA_PLATFORM=wayland 明確選擇 Qt 的 Wayland 後端,避免選到錯誤的平台後端。在另一個終端使用與 compositor 相同的 socket 目錄:
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}"
export WAYLAND_DISPLAY=wayland-buninu
QT_QPA_PLATFORM=wayland konsoleKonsole 的啟動、畫面顯示,以及鍵盤與滑鼠輸入已驗證。
桌布與桌面 JavaScript
透過引擎參數指定桌布:
bun index.js -- --wallpaper /path/to/image.jpg
bun index.js --headless --wallpaper=/path/to/image.jpg資料夾、不存在/無法讀取的檔案,或不支援/損壞的圖片,都會退回 Land 內建的
wallpaper.jpg,不是指定資料夾裡的檔案。未傳參數時保留原本純色背景,直到呼叫
setWallpaper()。圖片維持比例,置中裁切填滿畫面。
在 Casty 網址列輸入:
javascript:setWallpaper('/path/to/image.jpg')
javascript:setWallpaper(prompt('Wallpaper path'))
javascript:alert(1 + 2)
javascript:if (confirm('Change wallpaper?')) setWallpaper('/path/to/image.jpg')setWallpaper(path) 回傳 Promise,解析為實際採用的絕對路徑;fallback 時回傳內建
圖片的路徑。相對路徑以 Land 的工作目錄為準;null 路徑(取消 prompt)不更換桌布,
解析為 null。一般網址仍顯示 Wayland socket 位址;
javascript: 不會建立新分頁,也不會讓 Wayland client 導航。
桌面指令在首次使用時才建立的持續性 worker 裡,以原生 Bun JavaScript 執行,
沒有 sandbox,可正常使用 Bun 的檔案與程序權限。它與 client 的 JavaScript、
render worker 都是分開的。同步 alert、confirm、prompt 只阻塞指令 worker,
Land 仍能處理 UI 與 Wayland 事件。關閉 Land 時會終止 worker、取消待回覆的執行請求,
並關閉對話框。
Land 自行把對話框畫成軟體覆蓋圖層,只使用既有 CanvasKit 的文字與點陣繪圖能力, 不啟動瀏覽器引擎或額外 renderer 程序。長訊息可用滾輪、方向鍵、Page Up/Down、 Home/End 捲動;可拖曳或 Ctrl-A 選取文字,以 Ctrl-C 複製。Prompt 支援編輯、選取、 複製貼上、Tab、Enter 與 Escape。剪貼板沿用 Land 優先讀取 host 的既有路徑。 內附 DejaVu Sans Mono;會從系統字型目錄、已安裝或隔壁的 buninu-browser 字型資源 尋找 Roboto、Noto CJK、彩色 emoji、旗幟及 Symbols/Symbols2 符號字型, 包含字型提供的高音譜記號 𝄞 等音樂符號。不載入瀏覽器程式碼,也不下載字型。 關閉對話框會釋放文字排版與像素緩衝,關閉 Land 會釋放字型管理器。 單純桌布與不使用對話框的指令不會載入 CanvasKit。
對話框開啟時,依賴 DOM 的 CDP evaluate 會立即回報錯誤,避免等待已暫停的指令
worker;滑鼠、鍵盤輸入與 Page.handleJavaScriptDialog 仍能操作。
桌面指令依然在原生指令 worker 中執行。
Chromium 與 Firefox
在另一個終端,使用與 compositor 相同的 socket 目錄與名稱。作者實測 Chromium 只需設定 XDG_RUNTIME_DIR、WAYLAND_DISPLAY,再加上 --ozone-platform=wayland --no-sandbox 即可啟動,不需要 --disable-gpu:
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}"
export WAYLAND_DISPLAY=wayland-buninu
chromium --ozone-platform=wayland --no-sandbox
firefoxFirefox ESR 140.12.0 也已實測只需上述 XDG_RUNTIME_DIR 和 WAYLAND_DISPLAY,直接執行 firefox 即可自動使用 Wayland;不需要設定 MOZ_ENABLE_WAYLAND 或 GDK_BACKEND。此測試未設定 DISPLAY 或 XDG_SESSION_TYPE,網頁顯示、文字輸入與滑鼠點擊均通過。
已用系統安裝的 Chromium 153.0.8010.52 與 Firefox ESR 140.12.0 驗證真實視窗、網頁軟體渲染、文字輸入和滑鼠點擊。測試使用獨立 profile,沒有下載或安裝瀏覽器;未驗證 WebGL、影片或所有網站。若瀏覽器已在其他桌面執行,請先關閉,避免命令被轉交既有程序。
上述 Chromium 指令中的 --no-sandbox 會停用瀏覽器沙箱,只適合隔離環境中的受信任內容測試,不建議用於一般瀏覽。Firefox 不需停用 sandbox。Android 原生瀏覽器執行檔尚未在本次測試中驗證。
Firefox 使用的 subsurface 已接入合成與滑鼠座標處理。wl_data_device_manager 已支援剪貼簿選取資料傳輸;拖放仍未實作。
Casty 會傳入 --remote-debugging-port=0,由系統選擇可用 port;Casty 從 compositor 輸出的 DevTools URL 取得實際 port。手動 headless 啟動預設使用 9222。
Casty 傳入的 Chromium 相容 flags(例如 --no-sandbox、--user-data-dir、--disable-*)目前忽略;--remote-debugging-port 與 --window-size 會實際生效。
直接 headless 執行:
bun compositor.js --headless --remote-debugging-port=9222Headless 啟動後立即提供代表現有桌面的單一 CDP page;尚未連入 Wayland client 時也有正常的桌面背景。不需要 Kitty 終端機,也不用事先建立 target。 在另一個終端機執行:
bunx @drxiaozhi/casty --attach 9222 --no-shortcuts加上 --readonly 可只觀看而不傳送輸入。觀看端斷線後,Land 與 Wayland
clients 繼續運行。一般 Casty 啟動也會重用同一個桌面 target。
CDP 監聽 127.0.0.1,其他機器需透過 tunnel 連接。
手動 headless 與 Casty 自動啟動可並存,預設會分配不同 socket。Casty 的 URL 不會開網頁;顯示的是 Wayland 桌面。
bun compositor.js --help 列出選項。預設從 wayland-buninu 開始尋找可用 socket,預設畫面 800×600。--socket=NAME 可選擇其他 socket;--remote-debugging-port=0 自動選 port。BUNINU_LOG=1 將 Wayland requests 寫到 stderr。
Land 導航後會在網址列顯示本次實際的 Wayland socket 路徑,不受傳入網址影響,也不會開啟網頁。
更新與清除快取
可用 npx buninu-land@latest 明確要求最新發布版本。Casty 由選定的套件執行器
另外解析;只清除 Buninu Land 快取,不會一併清除 Casty。Casty 離開時會印出版本。
要重新取得快取中的套件,可使用以下指令。npm 11 或更新版本先列出項目,再刪除
buninu-land 與 @drxiaozhi/casty 對應的 key;請將佔位文字換成實際列出的 key:
npm cache npx ls
npm cache npx rm <buninu-land-key> <casty-key>
# 或清除所有 npx 項目,包含其他工具:
npm cache npx rm --force
# 若 launcher 使用 bunx 或 bun x,清除 Bun 的套件快取:
bun pm cache rm舊版 npm 可先執行 npm config get cache,再刪除該目錄內的 _npx 子目錄。
Linux/Android 常見路徑是 ~/.npm/_npx,但自訂 npm cache 設定可能改變位置。
清除整個 npx 或 Bun 快取也會影響其他已快取的套件。這些操作不會移除全域安裝的
套件;若使用全域安裝,更新方式是 npm install -g buninu-land@latest。
參考:npm cache、 Bun 套件快取指令。
結構
index.js:沿用 browser 的套件 launcher,預設啟動 Casty,設定CASTY_BROWSER指向 compositor。compositor.js:headless controller、CDP context adapter、生命週期與 CLI。lib/cdp-server.js:沿用 browser CDP server,增加兩個可選的原始輸入 hook,可選的導航網址轉換 hook,以及單一桌面 target;未修改原版保留於vendor/buninu-browser/cdp-server.js,差異見patches/cdp-input.patch。user-agent.js也原樣沿用,因此 CDP 保留 Buninu 的相容識別。lib/protocol.js:使用Bun.XML.parse()在執行時讀標準 XML,依原始順序處理 opcode/argument。lib/transport.js:僅以 Bun FFI 載入 libc;Unix socket、recvmsg/SCM_RIGHTS、非阻塞傳輸、socket lock、FD 清理。lib/compositor.js:每個 client 的 object table、SHM pool/buffer 生命週期、surface commit、xdg configure/ack、frame callback。lib/renderer.ts/lib/direct-compositor.ts:Bun 直接載入的純 TypeScript CPU 合成器;lib/renderer.js保留既有 JS 入口。原本 JS/CanvasKit renderer 保留為createLegacyRenderer。lib/frame.js沿用 browser 的Bun.Image編碼工具。
SHM 在 commit 時使用 libc pread 複製到 compositor 持有的像素,然後釋放 client buffer。這讓 client 可以安全重用 buffer;也避免 client 截短 mmap backing file 導致 compositor SIGBUS。Pool resource 被銷毀後,只要 buffer 還存在,FD 就仍然保留。
第一版範圍
公布 wl_compositor v4、wl_shm v1、xdg_wm_base v2、wl_output v2、wl_seat v5(滑鼠與鍵盤)、wl_subcompositor v1、wl_data_device_manager v3、ext_data_control_manager_v1 v1、zwlr_data_control_manager_v1 v2。SHM 支援 ARGB8888 與 XRGB8888。第一個一般視窗填滿工作區,保留 8 像素外側間距;新增視窗會分割平鋪區域。約 60 Hz 排程,每次提交仍完整複製 buffer。
Casty 滑鼠與鍵盤事件已轉送 Wayland,包含 JS 產生的 XKB keymap、焦點、基本 popup 與全螢幕 configure,仍屬早期實作。已支援 subsurface 的同步/非同步提交、位置與堆疊。尚未支援觸控、拖放、GPU 合成、通用 DMA-BUF 支援、完整 popup 邊界調整,以及全面的協定與角色生命週期驗證。
滑鼠捲動
CDP 像素位移以 Wayland continuous 軸事件傳遞,包含水平與斜向捲動。滾輪事件 中的 Shift/Alt/Ctrl/Meta 與已按住的按鍵狀態合併,在該 pointer frame 後還原, 由客戶端決定修飾鍵的用途。GPUI 等客戶端因此不必等待離散滾輪刻度才捲動。 Land 不會因 Shift 自行交換軸向。請搭配新版 Casty 與能傳送水平回報的外層終端, 而客戶端元件本身也須啟用水平捲動。即使 Kitty 鍵盤能傳 Super,終端滑鼠回報 仍無法攜帶 Super。
視窗快捷鍵
以下快捷鍵接上 Land 已有的視窗操作,對照 Omarchy 的視窗綁定:
| 快捷鍵 | 操作 | | --- | --- | | Super+方向鍵 | 切換到目前工作區中該方向的視窗 | | Super+W/Q | 請求關閉焦點視窗;應用程式仍可能要求確認 | | Super+F | 切換全螢幕,再按一次恢復布局 | | Super+T | 切換浮動/平鋪 | | Super+Alt+F | 切換最大化;使用 Land 的整個單一輸出區域 | | Super+Shift+方向鍵 | 與該方向的平鋪視窗交換位置 | | Alt+Tab/Alt+Shift+Tab | 在目前工作區向前/向後循環視窗 | | Super+1–9 | 切換工作區 | | Super+Shift+1–9 | 搬移焦點視窗並跟隨到工作區 | | Super+-/= | 水平方向縮小/放大 100 邏輯像素;平鋪時調整分割比例 | | Super+Shift+-/= | 垂直方向縮小/放大 100 邏輯像素;平鋪時調整分割比例 | | 上述大小調整再加 Alt/Ctrl | 改用 25/300 的調整量 |
全螢幕時切換方向焦點,會將全螢幕轉移到選中的視窗;該方向沒有視窗則不動作。循環視窗在全螢幕時也會轉移全螢幕。工作區目前限 1–9;不攔截 Super+0、Super+Tab、群組、scratchpad 或其他尚未實作的組合。修飾鍵必須完全符合表列組合,其他按鍵交回應用程式。大小調整沿用 Land 既有的浮動尺寸限制與平鋪比例演算法,不代表完整複製 Hyprland 的 resize 行為。請在支援 Kitty keyboard 的終端(例如新版 jsgotty 或 bunterm)裡使用新版 casty。Casty 會自動協商並將 Super 以 CDP Meta 傳入,包括按鍵放開事件。Ctrl+Alt 組合現在交回應用程式,不再作為視窗快捷鍵的替代。傳統終端仍能使用,但無法可靠傳遞 Super。修改後須重啟 casty 和 compositor。
協定定義原樣保留,但實作為 JS,並不是 Hyprland C++ 的逐位元複本,也不代表 XML 中每個 request 都已實作。參考來源見 SOURCES.md。
剪貼簿
核心 data-device 支援 v1–v3,source 與 offer 繼承協商版本。拖放 action 請求會驗證,
但拖放仍會取消。Client 可能在本地快取 selection,貼上時完全不送出 receive;
這種情況 Land 無法讀取 host,需要新的 selection 通知才會讓 client 清除快取。
這次支援沒有加入 host 輪詢。
Wayland 程式透過 wl_data_device 與 ext_data_control/wlr_data_control
交換公布的 MIME 類型。接收方的檔案描述符直接轉交來源程式,保留檔案清單、
複製/剪下標記與二進位資料,compositor 不額外編碼或緩衝內容。一般程式取得
焦點後收到選取資料;data-control 程式不需要焦點。比照 Hyprland 的
DataDevice.cpp,一般程式設定選取時不檢查鍵盤焦點或輸入 serial。data-control 的 primary
selection 獨立保存於 Land 內。目前不支援拖放,也不在來源程式退出後保存檔案/二進位選取資料。
系統剪貼簿橋接目前傳輸 UTF-8 文字,上限 1 MiB。使用 Bun.which 尋找命令,
所有平台(包含 Windows)一律優先 xclip。其餘候選為 Windows 的 PowerShell、
macOS 的 pbcopy/pbpaste;其他平台依序嘗試上層 compositor 的
wl-copy/wl-paste、xsel、Termux 剪貼簿命令。失敗會嘗試下一個工具,
不影響 Wayland 程式間交換資料。每個系統剪貼簿命令最多執行 2.5 秒。PowerShell 引號與換行處理原樣複製自 jsmdcui。
Linux/Android 使用標準 fd 傳輸;Windows 的實驗性傳輸使用具名共享記憶體。Windows 命令優先順序已由單元測試驗證。
複製文字選取時,僅匯出一次;空文字不會覆蓋系統剪貼簿,也不會取代下述快取。不設剪貼簿計時器,
也不在焦點切換時或輪詢讀取系統剪貼簿。每次收到純文字貼上請求時先讀系統,
即使舊來源仍在執行,也能取得外部新複製的文字;系統失敗或回傳空值才向原來源取文字。
要求檔案等其他 MIME 的請求及 primary selection 仍直接在程式間傳輸,不用系統文字覆蓋。
Land 另在記憶體保留上次成功取得的複製文字,即使匯出至系統失敗也保留。
沒有 Wayland 選取時,即使剛啟動、完全沒有快取,也會提供按需讀取系統的文字 offer。
文字來源退出後,其快取也走同一路徑。此系統/快取來源每收到一次 receive
就讀取系統一次,包括不需焦點、會自動接收的 data-control 程式(例如剪貼簿管理器):成功取得非空文字就更新快取;失敗或空回覆則使用快取,沒有快取就回傳空文字(Android 拒絕
存取時可能回傳空值)。因此單純清空系統剪貼簿不會清掉這份回退快取。
快取上限 1 MiB、不寫入磁碟,Land 結束時消失;不取代仍存在的來源所提供的檔案/剪下 MIME 資料。
內部 importHost() 仍可明確發布系統文字快照;一般系統 fallback 貼上不需呼叫它。空回覆或讀取失敗
不會覆蓋現有選取。若 wl-copy/wl-paste 會連回 Land 自己則跳過。
不會自動安裝工具或 daemon。已啟動的 Land 需重開才會載入。
畫面轉換成本
Renderer 以不可變 SHM 快照、幾何、堆疊、游標與 viewport 作為場景快取條件;場景不變時重用 BGRA 合成結果。明確的 clip 直接合成裁切區域;標準 maxWidth/maxHeight 縮圖則從完整合成結果縮放。替換場景或關閉 renderer 時釋放舊快取。
Casty zoom 透過 CDP deviceScaleFactor 套用:排版與輸入保留邏輯座標,截圖則使用實際像素尺寸。使用縮圖 fallback 的客戶端在 zoom 小於 1 時採用線性取樣,減少縮小內容造成的鋸齒與筆畫遺失。這些修改只在 land,Casty 與 buninu-browser 維持不變。
Land 提供 wp_fractional_scale_manager_v1 與 wp_viewporter。支援的客戶端可以依指定比例繪製 buffer,同時保留視窗邏輯尺寸及輸入座標。Viewport 裁切/目標尺寸隨 surface commit 套用,也支援同步子視窗;符合實際像素尺寸的 fractional buffer 會對齊像素,避免再次插值。不支援的客戶端仍使用原本的縮圖路徑。
已檢查兩個 Firefox 版本的縮放行為:
- **Firefox 153.5.0 ESR:**執行中的 Casty 60% 工作階段使用 667 × 810 邏輯 viewport,輸出為 400 × 486。Firefox 共享像素 buffer 也包含 400 × 486 像素,其中 99.95% 的 RGB 像素與擷取的 PNG 完全相同,確認此次輸出保留客戶端像素,而非再縮小較大的影像。PNG 中的文字較清楚;未量測終端最後顯示的效果,此次也未重跑整套瀏覽器輸入測試。
- **Firefox 140.12.0 ESR:**測試時預設未啟用 fractional scaling。在
about:config設定widget.wayland.fractional-scale.enabled=true並重啟後,已於獨立 profile 驗證 60% 下的目標解析度網頁 buffer、文字輸入、點擊與捲動。該次測試的 GTK 視窗裝飾仍走 fallback。
不使用 fractional scaling 的客戶端仍能顯示並按原本邏輯座標接受輸入,但縮小其點陣影像仍可能模糊。同一應用程式內的不同 surface 也可能採用不同路徑。Casty zoom 與 Firefox 自己的網頁 zoom 是分開的;即使 buffer 符合輸出解析度,網頁 zoom 仍可能讓文字很小。
更新後需重啟 land 及客戶端以使用新協定。若 Firefox 仍走 fallback,可在 about:config 檢查 widget.wayland.fractional-scale.enabled;land 不會自動修改 Firefox 偏好設定。
可用已安裝的 Firefox 重跑 60% 獨立 profile 測試;此測試會明確啟用 fractional scaling,不用來驗證瀏覽器的預設值:BUNINU_TEST_BROWSERS=1 BUNINU_TEST_FRACTIONAL_SCALE=1 bun test tests/browser-clients.test.js -t firefox。
PNG:SHM BGRA → TypeScript 軟體合成 → 內建快速 PNG encoder(預設),或 Bun.Image → CDP base64 → Casty。
JPEG/WebP:SHM BGRA → TypeScript 軟體合成 → BGRA BMP 包裝 → Bun.Image 編碼 → CDP base64 → Casty。BMP 包裝仍有一次完整畫面複製,但不再需要 CanvasKit readback 或通道轉換。裁切的變動通知仍有編碼成本;clip 0×0 通知不產生圖片。
協商原始像素
不透明的全螢幕 BGRA surface 可直接將不可變的客戶端快照寫入 raw 檔案,只合成並覆寫客戶端遊標所在的小區塊。產生端會完成所有寫入才回傳描述資訊;Kitty 傳輸和檔案所有權保持不變。遊標區塊過大或使用內建指標時,維持完整合成路徑。
搭配相容的 Casty,raw screencast 會明確帶入私有擴充 clip: {x: 0, y: 0, width: 0, height: 0, scale: 1}。Land 在場景更新時回傳固定 1×1 PNG 佔位資料,不擷取或編碼預覽;Casty 再要求真正的 raw 畫面。一般 screencast 與標準截圖保持原有行為。此約定僅在 screencast 事件路徑處理;一般 captureScreenshot 仍拒絕零尺寸。vendor CDP server 與 Browser 同步,Land 專用 hooks 另外記錄於 patch。
標準 CDP clip 可與 RGBA/BGRA 檔案共用;Land 直接合成指定範圍,小數尺寸保留到換算實體像素時才取整。scale: 1 不額外縮放,避免終端因一列誤差退回慢路徑。
Casty 也會在 Page.startScreencast 傳入私有 clip;優先於相容用的 max 限制,以原像素密度裁切、不縮放。寬高是 CSS 單位,實體像素仍隨 device scale 換算。此擴充不是標準 CDP。
更新後的 Casty 與原生 bunterm 會優先協商 raw:先確認檔案傳輸與終端偏好,再實際試一次 CDP 截圖。只有兩端都支援才啟用;失敗回到使用者原本格式設定,auto/file 使用 JPEG 並在靜止後補 PNG,auto/inline 使用 PNG。raw 畫面不經 PNG 壓縮或像素 base64;尺寸與通道排列相符且完全不透明時,原生 bunterm 可直接複製像素,否則退回 CanvasKit。PNG/JPEG/WebP 與一般瀏覽器的 CDP 路徑保持相容。
Land 的私有 Page.captureScreenshot 格式 bgra 與 rgba,在 data 回傳 JSON 描述(version:1、transport:"file"、path、width、height、stride、format、bytes),而非 base64 像素。BGRA 直接寫出合成器既有像素;RGBA 在需要時交換紅藍通道。Casty 透過 Kitty 暫存檔傳輸轉送路徑,由 bunterm 讀取並刪除檔案。產生端管理及清理目錄,包含 render worker 被終止的情況。未消費檔案上限為 16 個/128 MiB;再次產生 raw 畫面時會清除超過 60 秒的遺留檔案。這需要共用本機檔案系統,不能直接用於跨網路的遠端 CDP。
Casty 會驗證回傳的檔案描述及本機檔案;引擎不支援、無法共用檔案、錯誤或逾時時,維持原本設定的編碼。CDP server 與 browser vendor 副本未為此修改。
私有 Kitty 查詢使用 f=1111970369,必須收到完整的 OK Buninu1 bgra 或 OK Buninu1 rgba;只有 OK 不足以啟用。即使明確指定其他編碼,raw 仍優先,該編碼改作 fallback。BGRA 顯示使用私有格式;RGBA 維持標準 f=32。私有格式與偏好回覆都不是上游 Kitty 標準。原生 bunterm 根據 framebuffer 通道位元配置選擇格式;未知配置保守使用 PNG。瀏覽器中的 jsgotty 不宣告此擴充。
桌布準備與快取生命週期
由 Bun.Image 使用 bilinear 縮放,輸出壓縮等級 0 的 PNG,再由小型 JS decoder 將自己產生的非交錯 RGBA8 像素轉成 BGRA,透明部分疊到原本背景色上。這條路徑 不載入 CanvasKit,也不使用 native addon。每個邏輯寬/高/device scale 組合都保留 準備好的 surface,直到換桌布或關閉 Land;切回用過的尺寸不會再次解碼或縮放。 一般 TS compositor 將桌布視為最底層 surface;不透明且覆蓋畫面的 client 仍可走 原本整張畫面跳過合成的快路徑。Render worker 沿用現有 surface 傳輸接收不可變像素。
每份尺寸快取需要每實體像素 4 bytes,1920×1080 約 7.9 MiB。來源檔案、解碼與
cover 中間圖片都有 bytes/像素上限;目前桌布的所有已要求尺寸會刻意留在快取,
與指令 worker 的記憶體分開看待。換桌布會釋放舊尺寸快取。可執行
bun bench/wallpaper.js 重現本地比較,詳見測試說明。
Renderer worker
軟體合成與截圖編碼預設在 Worker 中執行,讓 Wayland socket 所在的執行緒保持可回應。使用 bun index.js -- --no-render-worker 或設定 BUNINU_RENDER_WORKER=0,可改回主執行緒繪製。先前的實驗環境變數仍受支援,但 BUNINU_RENDER_WORKER 的設定優先。
若 Worker 在 10 秒內未完成啟動、意外退出、發生未捕捉錯誤,或執行中的截圖請求超過 5 秒未回覆,Land 會終止 Worker,印出一次診斷訊息並回退到主執行緒 renderer,重新嘗試失敗的截圖。無效截圖參數仍視為一般請求錯誤,不會觸發回退。Worker 路徑最多接受八個排隊或執行中的截圖;關閉時會拒絕未完成請求並終止 Worker。
驗證
bun test測試包括協定 XML opcode、FD 參數、分段封包、socket 排他鎖、無效 client 隔離,以及真實 weston-simple-shm 動畫和 CDP PNG/JPEG。若沒有已安裝的 weston client,相關測試跳過。Casty 整合測試會在測試所需條件可用時,執行其真實 launch/setup/screencast 程式,否則跳過;不自動安裝依賴。測試的截圖路徑會印在輸出中。Casty 測試涵蓋收到影像的流程,並未自動驗證終端 Kitty 顯示。
若系統已安裝 Chromium/Firefox,可額外執行真實瀏覽器測試(獨立 profile、驗證網頁文字輸入與點擊,不自動安裝依賴):
BUNINU_TEST_BROWSERS=1 bun test tests/browser-clients.test.js在本次 proot 環境,測試指令還需加上 BUNINU_TEST_CHROMIUM_NO_SANDBOX=1;此變數只停用測試用 Chromium 的 sandbox。一般 bun test 會跳過這兩項瀏覽器測試,但會驗證 subsurface 同步、buffer 快照、堆疊和模式切換。
Linux DRM client 硬體加速
Linux 已有可用的 Mesa driver 與 /dev/dri/renderD* 節點時,可以指定 Land 提供給 Wayland client 的 GPU:
bun index.js -- --gpu=/dev/dri/renderD129
# 在 client 環境中,使用 Land 印出的 socket:
env WAYLAND_DISPLAY=wayland-buninu glmark2-wayland
# 也可以用實際 PCI 位址,另外指定 client 的 GPU:
env WAYLAND_DISPLAY=wayland-buninu DRI_PRIME=pci-0000_04_00_0 glmark2-wayland--gpu 是客戶端硬體加速的快捷設定,自動啟用 --linear-dmabuf --lazy-dmabuf --poll-interval=2。可用 --gpu=/dev/dri/renderD129 或 --gpu /dev/dri/renderD129 指定 DRM 裝置;單獨 --gpu 不指定裝置,沿用 v3 DMA-BUF 路徑。明確設定的 --poll-interval=MS 與 --drm-device=PATH 優先於快捷設定,不受參數順序影響。這不是 GPU 合成;2 ms 是此次 Intel HD Graphics 5500 實測的較佳設定,不保證所有硬體都最佳。
以上裝置路徑與 PCI 位址只是範例。可用 ls -l /sys/class/drm/renderD*/device 對照硬體,並查看各裝置的 vendor、device 與 driver。Render node 編號可能隨開機改變;Land 的環境與 client 的 chroot 都必須能存取選定的節點。
--drm-device 必須搭配 --linear-dmabuf,會啟用 linux-dmabuf v4 的預設與各 surface feedback,提供唯讀格式表、選定的 DRM 裝置及 LINEAR ARGB8888/XRGB8888 格式。Mesa 因而能先選到硬體裝置,再套用 DRI_PRIME;不需要 udev daemon。舊 client 仍可取得 v3 格式/modifier 事件。不指定 --drm-device 時維持原本 v3 路徑;一般無參數啟動仍使用 SHM,不宣告 DMA-BUF。裝置不是 render node 或無法開啟時,啟動會明確失敗。
這加速的是 client 繪圖。Land 仍透過 CPU 映射 linear buffer,同步、複製像素,再以軟體合成;不載入 EGL、Vulkan 或 libdrm。不支援提供這類 buffer 的硬體/driver 組合仍無法使用。在 Intel HD Graphics 5500 + Nouveau NV106、Mesa 25.0.7 的真機上,Intel 已確認正確繪圖與持續變化的截圖。Nouveau 雖已選到 NV106 硬體 renderer,但匯入畫面仍為空白或雜訊,因此這條 NVIDIA 路徑目前還不能正常使用。此處 NVIDIA 使用 Mesa Nouveau,並非 NVIDIA 專有 driver。
自動 CPU 繪製優化
所有場景以純 TypeScript 軟體合成,支援旋轉、翻轉、viewport 裁切、小數座標、縮放、預乘透明度與游標,不再退回 CanvasKit。完整不透明畫面仍可直接複製;舊實作完整保留在 lib/renderer.js 與 lib/direct-compositor.js,也用於畫素對照。要退回時,把 lib/renderer.js 的 TypeScript re-export 換成 export {createLegacyRenderer as createRenderer}; 即可;只有使用舊 renderer 或桌面對話框才載入 CanvasKit。這不是 GPU 合成,也不需要 AOT 或預先編譯。XRGB 的未使用 alpha 一律視為不透明。
Land 會先把 SHM/DMA-BUF 內容複製成自己持有的不可變快照;環境支援時以 SharedArrayBuffer 儲存,renderer worker 可以共用快照,省掉另一次完整像素複製。client 持有的映射不會直接分享給 worker,原有 buffer 釋放與讀取同步仍保留。不支援共享 buffer 的 runtime 會使用一般快照。
快速無損 PNG 預設啟用。若較重視檔案大小,可改回一般編碼:
bun index.js -- --no-fast-png
# 搭配其他 engine 參數時,也已預設啟用快速 PNG:
bun index.js -- --gpu=/dev/dri/renderD129仍可用 --fast-png 明確啟用;與 --no-fast-png 同時指定時,以最後一個為準。快速 PNG 同時適用於 worker 與主執行緒。它以較大的 PNG 檔案換取較低編碼成本,不改 JPEG/WebP 設定或 Casty 截圖間隔。i5-5200U 實驗的 1080p PNG 約大一倍,因此仍須考慮終端與網路吞吐量。這個參數只影響請求 PNG 的截圖,不會強制 Casty 選用 PNG。
Direct Freedreno/KGSL 實驗
目前安裝的自訂 aarch64 Mesa 可以在 Adreno 830 上直接透過 Freedreno 繪製 OpenGL。觀察 Termux X11 的執行行為可見 DRI3 DMA-BUF 提交;未閱讀或複製 Termux X11 原始碼。檢查已安裝的 Mesa EGL 二進位檔發現,成功的 Wayland roundtrip 會跳過既有 KGSL 裝置開啟分支。對獨立副本調整該分支後,已能直接使用 Freedreno 繪製並向 Land 提交 LINEAR DMA-BUF,也驗證了持續變化的截圖。
啟動新的 Land,開啟實驗性 linear DMA-BUF 匯入:
bun index.js -- --linear-dmabuf --poll-interval=1未指定 --drm-device 時,--linear-dmabuf 宣告第 3 版協定,只提供明確指定 LINEAR 的 ARGB8888/XRGB8888 格式。匯入器透過 libc mmap 與 DMA-BUF 讀取同步,支援可由 CPU 映射的單平面 buffer;不支援的配置會匯入失敗。預設啟動不宣告此協定。--poll-interval=1 降低 socket 輪詢延遲,但增加 CPU 成本;預設仍為 4 ms。--poll-interval=-1 以 setImmediate 啟用 busy polling,每次輪詢之間讓出事件迴圈,但會持續消耗 CPU。
接著在 Debian/proot 內執行,使用 Land 印出的實際 socket:
WAYLAND_DISPLAY=wayland-buninu bun tests/kgsl-wayland.js
# 或量測十秒:
WAYLAND_DISPLAY=wayland-buninu bun tests/kgsl-wayland.js -b build:duration=10腳本使用已安裝、支援 AArch64 的 objdump,尋找唯一一個通往 loader_open_device("/dev/kgsl-3d0") 的 Wayland roundtrip 分支,核對反組譯指令與 ELF 內容後,只修改私人暫存副本中的一條指令,不依賴固定檔案雜湊或 offset。可執行 bun tests/kgsl-wayland.js --check-only,只檢查相容性而不啟動 client;用 KGSL_EGL_LIBRARY 選擇其他 EGL 函式庫,或用 OBJDUMP 指定反組譯工具。它不替換系統 Mesa,也不把 EGL 載入 Land。腳本選擇 kgsl、清除強制 Gallium/軟體呈現設定,並將 vblank_mode 預設為 0。這是針對符合控制流程的自訂 AArch64 Mesa 所做的診斷性修補,不是通用或正式 driver 修正。缺少符號、控制流程不同、匹配不唯一或缺少 KGSL 支援時會拒絕修補。預期 renderer 為 freedreno/Adreno (TM) 830。
先前十秒、800×600 的 build 場景,以原生 Land 與 1 ms 輪詢測得 465 client FPS;暫時的 busy polling 實驗達到 1125 FPS,Termux X11 在 vblank_mode=0 下為 1992 FPS。數字會變動,後來一次短時間 busy polling 測量為 588 FPS。現在可用 --poll-interval=-1 選擇 busy polling,仍需手動啟用且會持續消耗 CPU。這些測量僅定期檢查截圖,沒有啟用持續 Casty 串流,不能當成終端顯示 FPS。Land 仍會複製匯入的像素並以 CPU 合成。
延後 DMA-BUF 讀取實驗與 renderer worker 測量
Renderer worker 現在預設啟用;--lazy-dmabuf 開啟實驗性的延後 DMA-BUF 讀取,必須搭配 --linear-dmabuf,預設關閉。啟用 linear DMA-BUF 匯入時,仍相容先前的 BUNINU_EXPERIMENT_DMABUF_LAZY=1 環境變數。在原生 Termux 中合併使用:
bun index.js -- --linear-dmabuf --lazy-dmabuf --poll-interval=-1延後讀取會持有 buffer 與其映射,直到截圖需要像素。尚未擷取就被新幀取代的畫面,直接釋放,不做 CPU 同步或複製;真正擷取的畫面則在 DMA-BUF 讀取同步期間完成複製,之後才釋放。Renderer worker 將 軟體合成與圖片編碼移出 Wayland socket 執行緒,仍然是軟體繪製;新快照送進 Worker 時會增加一次 structured clone 像素複製,並非零複製 GPU 合成。SHM 維持原本立即建立快照的行為。
原生 Adreno 830 實驗使用相同的 800×600、十秒 glmark2 build 場景、busy polling 與 Casty 真正的 JPEG 串流程式;接收端沒有在終端繪製圖片:
| 模式 | Client FPS | 反向順序重測 | | --- | ---: | ---: | | 原有路徑 | 258 | 197 | | 只延後讀取 | 482 | — | | 只使用 renderer worker | 631 | — | | 兩項一起使用 | 1536 | 1115 | | Termux X11,另外測量 | 1793 | 1262 |
Casty 每秒收到約 17–20 張完整尺寸圖片;client FPS 不等於顯示 FPS。主要收益是減少 client 等待並丟棄中間幀,而不是每秒輸出數千張圖片。溫度與排程會影響結果。原始計時記錄在 tests/dmabuf-experiment-results.json。
重跑方式:先在原生 Termux 執行 bun tests/dmabuf-perf.js,再於 Debian/proot 執行 bun tests/dmabuf-benchmark.js --x11。加上 --repeat 可依序測量兩項一起使用與原有路徑。控制程式使用 loopback port 19381 和獨立的 wayland-dmabuf-perf socket,不會連接既有 Land。測量會使用上述實驗性 KGSL launcher,因此需要符合其控制流程的 Mesa。測試後請停止控制程式。這些測量不代表通用桌面相容性或終端輸出速度保證。
授權
專案原始碼採 MIT,見 LICENSE。沿用的 browser 程式、CanvasKit WASM 及其內含元件、Wayland XML 各自的來源與授權,見 THIRD_PARTY_NOTICES.md、LICENSES/、SOURCES.md;第三方授權不被專案的 MIT 宣告取代。
Windows Wayland 傳輸(unstable)
unstable:Windows fd 替代傳輸尚待真機端到端驗證。
實驗性支援:bun index.js --win32 沿用 Casty 的 Windows 原生視窗。
只有 Windows 才載入 kernel32,建立 UUID 命名的 Local\buninu-wayland-...
pagefile mapping。socket 仍由 Bun 的普通 Unix socket API 監聽在原本 runtime
路徑。Browser 用 bun index.js --wayland URL,兩端使用相同的 runtime 目錄
(XDG_RUNTIME_DIR、TMPDIR、系統暫存目錄),並設定 Land 印出的 WAYLAND_DISPLAY。
原本 Wayland 訊息包在私有版本封裝內,以 mapping 名稱取代 SCM_RIGHTS;畫面仍用
commit/release 同步,ACK 確保接收端開好 mapping 才釋放傳送端持有的 handle。
Windows socket 名稱由獨占 lock 檔保護:關閉或崩潰後可回收殘留 AF_UNIX endpoint,
仍在運行的實例則保留名稱。退出後 .lock 檔可能仍存在,單憑檔案存在不代表占用。
鍵盤 keymap、剪貼板 pipe 也走 mapping;既有 xclip 優先、PowerShell fallback
維持不變。Linux/Android 保留原本 libc 傳輸,Windows 不支援 DMA-BUF。
尚待 Windows 真機端到端驗證;Browser 專案的 test/wayland-win32-resources.test.js
提供 mapping 與 socket 的原生測試。
