npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

buninu-land

v0.4.1

Published

A Wayland compositor in JavaScript and WebAssembly for Bun

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 --help

Land launcher 會自動加上 --no-shortcuts,將頁面上的全部按鍵傳給 Wayland client。點網址列左側 @ 開啟空白 cmd>,輸入 q 再按 Enter 退出 casty。點網址仍可編輯,編輯時保留快捷鍵;需搭配新版 casty。

也可自行從 Casty 啟動:

export CASTY_BROWSER="$PWD/compositor.js"
bunx @drxiaozhi/casty --no-shortcuts about:blank

Socket 目錄依序採用明確設定的 $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 konsole

Konsole 的啟動、畫面顯示,以及鍵盤與滑鼠輸入已驗證。

桌布與桌面 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
firefox

Firefox 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=9222

Headless 啟動後立即提供代表現有桌面的單一 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 的原生測試。