zDesk
繁體中文

指南

設定 wlshare

在 sway 或 labwc 等 wlroots 合成器上設定 wlshare,也就是 zDesk 用來連線 Linux 桌面的 VNC 伺服器:它需要什麼、誰可以連線、不需要任何人登入就在開機時啟動的桌面,以及它傳送的內容。所有設定項都在附註解的 wlshare.example.toml中,運作方式則在 Architecture 中說明。

本頁內容

需要什麼

wlshare 以擁有該 Wayland 工作階段的使用者身分,在它所共享的工作階段中執行,並透過 wlroots 的公開協定與合成器溝通。任何提供這些協定的 wlroots 合成器都是同一類對等端:

協定用途
wlr-screencopy畫面,版本 2 或更新;必要
ext-image-copy-capture指標的影像,與畫面分開擷取;必要
wlr-output-management為用戶端調整無頭輸出的大小和縮放比例
虛擬鍵盤和指標用戶端的鍵盤和滑鼠
wlr-data-control雙向剪貼簿

指標永遠不在擷取的像素中:wlshare 以輸出的密度另外傳送它的影像,用戶端移動指標時不必等待影格。在無頭輸出上,這需要 wlroots 0.19,它是第一個讓無頭後端把游標放在獨立平面上的版本;0.18 會把游標畫進擷取的畫面中。聲音、攝影機和麥克風透過 PipeWire 傳遞,套件相依於它。

在 Chrome 中以 Mac 的 Retina 密度透過 wlshare 顯示 Sway:wlr-randr 回報無頭輸出的縮放比例為 2,wpctl 則在瀏覽器收聽時顯示 wlshare 自己的音訊輸出裝置。

安裝

不保證回溯相容。wlshare 的任何版本都可能變更或移除某個設定項、私有擴充的傳輸格式或某項功能,且不再讀取舊的寫法。升級時,請對照 /usr/share/doc/wlshare/wlshare.example.toml 檢查你的設定。

Debian trixie 隨附以 wlroots 0.18 為基礎的 sway 1.10 和 labwc 0.8.3。wlshare 自己的已簽署 APT 套件庫提供 sway 1.11、labwc 0.9.7 和 wlroots 0.19,以 Debian 的打包方式為 trixie 重新建置,支援 amd64 和 arm64,各自的用途如下:

套件用途
wlroots 0.19wlshare 缺少就無法啟動的指標影像擷取(ext-image-copy-capture),以及在無頭輸出上讓指標不進入畫面,0.18 會把指標畫進每一次擷取。wlshare 的套件相依於它。
wlroots 的修補在有兩個輸出的 labwc 上:labwc 重新載入設定(labwc --reconfigure)之後,讓用戶端的指標仍落在它指定的位置。labwc 屆時會把每個指標重新對應到它所指名的輸出,而 wlshare 的指標沒有指名任何輸出,於是它的位置被拉伸到整個版面配置上,兩個輸出都受影響,直到下一次重新排列輸出為止;wlshare 在任何 wlroots 上都會在版面配置每次變更時重新建立它的指標,但重新載入設定並不會變更版面配置。只有一個輸出的桌面以及 sway 都不需要這個修補:原版的 wlroots 0.19 或更新版本就足夠了。
sway 1.11、labwc 0.9.7以 wlroots 0.19 建置的版本,原樣重新建置:trixie 的 sway 1.10 和 labwc 0.8.3 連結的是 0.18,wlshare 無法在其上啟動。

它們的版本號排序高於 trixie 的版本,因此不需要設定釘選(pin):

sudo mkdir -p /etc/apt/keyrings
sudo curl -fsSL -o /etc/apt/keyrings/wlshare.gpg https://andrewtheguy.github.io/wlshare/wlshare.gpg
sudo tee /etc/apt/sources.list.d/wlshare.sources <<'EOF'
Types: deb
URIs: https://andrewtheguy.github.io/wlshare
Suites: trixie
Components: main
Signed-By: /etc/apt/keyrings/wlshare.gpg
EOF
sudo apt update && sudo apt install sway  # or labwc

wlshare 本身是放在其發行版清單中的套件,不在套件庫裡。以版本號碼取代 0.0.x,在 arm64 主機上則以 arm64 取代 amd64:

curl -fsSLO https://github.com/andrewtheguy/wlshare/releases/download/v0.0.x/wlshare-trixie-amd64.deb
sudo apt install ./wlshare-trixie-amd64.deb

它相依於上述套件庫提供的 libwlroots-0.19,以及 pipewire、wireplumber 和 pipewire-pulse。它建議安裝 xdg-desktop-portal-gtk,讓桌面上的瀏覽器能找到用戶端的攝影機,以及 va-driver-all,用於主機的硬體 H.264 編碼器。

zDesk 閘道和 wlshare 必須使用同一個版本的 wlshare 協定,wlshare --version 會列出這個版本。連線到另一個版本的 wlshare 時,工作階段會在連線時失敗,並指明兩個版本;把較舊的那一個升級即可。不變更協定的發行會維持其版本不變。

執行

設定是一個 TOML 檔案,預設為 $XDG_CONFIG_HOME/wlshare/config.toml,空檔案即採用所有預設值。套件會安裝一個 systemd 使用者單元,在圖形工作階段把 WAYLAND_DISPLAY 交給使用者管理員後,隨該工作階段啟動:

mkdir -p ~/.config/wlshare
cp /usr/share/doc/wlshare/wlshare.example.toml ~/.config/wlshare/config.toml
systemctl --user enable --now wlshare.service
journalctl --user -u wlshare -f

手動執行時,請指明工作階段的通訊端:WAYLAND_DISPLAY=wayland-1 wlshare。它監聽 listen(未設定時為 127.0.0.1:5900),並共享合成器的第一個輸出,或 output 指明的那一個。

同一時間只有一個用戶端在桌面上。完成交握(包括登入)的連線會從目前持有桌面的一方接管桌面,就像 Windows 遠端桌面那樣,被接管的用戶端會得知是誰接管了它。RFB 的 shared 旗標不會改變任何事。因此中斷的用戶端不會留下任何東西:再次連線就能取回桌面,舊用戶端按住的按鍵和按鈕也會被放開。唯一的例外是持有者自己的第二個連線,它會顯示另一個輸出(請見輸出)。

誰可以連線

一個設定,三種答案。三者都不提供傳統 VNC 認證。

不要求登入

兩個表格都不設定。

登入
無
加密
否
適用於
位於回送位址、VPN 位址或 SSH 通道之後的 listen

系統登入

[pam]

登入
wlshare 執行時所用帳戶的使用者名稱和密碼,由 PAM 檢查
加密
RSA-AES
適用於
以帳戶密碼作為進入方式、並能用它解鎖金鑰圈的桌面

專屬密碼

[password]

登入
一組密碼,與設定中的 Argon2 雜湊比對
加密
RSA-AES
適用於
不應以任何系統帳戶的密碼作為進入方式的主機
listen = "[::]:5900"

[pam]
# service = "wlshare"   # /etc/pam.d/wlshare, from the package

在 [pam] 下,除了 wlshare 執行時所用的帳戶之外,任何使用者名稱都會在詢問 PAM 之前被拒絕,因為這個連接埠背後的桌面只屬於那一個使用者。使用 [password] 時,wlshare hash-password 會詢問密碼並輸出雜湊,作為 hash 貼上;密碼本身永遠不會寫下來。無論哪一種,伺服器的 RSA 金鑰都會在第一次啟動時於設定旁產生,並記錄其指紋,供你與用戶端顯示的指紋比對:zDesk 閘道會記錄它看到的指紋。

由登入解鎖的金鑰圈

由 systemd 啟動的無頭桌面沒有顯示管理員把登入密碼交給金鑰圈,因此登入金鑰圈會一直鎖住,直到有應用程式要求、有人回應提示為止。在 [pam] 下,PAM 剛檢查過的密碼可以完成這件事:/etc/pam.d/wlshare 的 auth 堆疊中,在 common-auth 之後加一行 pam_exec,把密碼交給一個指令碼,由它先後啟動 gnome-keyring-daemon --login 和 --start,也就是 pam_gnome_keyring 原本會做的兩半,因為 wlshare 不開啟 PAM 工作階段,它的 session 掛鉤無從執行:

@include common-auth
auth    optional    pam_exec.so expose_authtok quiet /usr/local/lib/wlshare/unlock-keyring
@include common-account

wlshare 及其套件都不提供這個指令碼,內容如下:

#!/bin/sh
# /usr/local/lib/wlshare/unlock-keyring: run by pam_exec from /etc/pam.d/wlshare
# with the password PAM has just checked on stdin. It starts the keyring daemon
# with it, both halves of what pam_gnome_keyring does, since wlshare opens no
# PAM session.
set -eu
XDG_RUNTIME_DIR="/run/user/$(id -u)"
DBUS_SESSION_BUS_ADDRESS="unix:path=$XDG_RUNTIME_DIR/bus"
export XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS

# A daemon already holding the Secret Service: an earlier login unlocked it.
owner=$(busctl --user list --no-legend 2>/dev/null |
    awk '$1 == "org.freedesktop.secrets" && $2 != "-" { print $2 }')
[ -z "$owner" ] || exit 0

# In a scope of its own, so that restarting wlshare does not take it down.
systemd-run --user --scope --quiet --collect --unit=gnome-keyring-login \
    /usr/bin/gnome-keyring-daemon --daemonize --login --components=pkcs11,secrets

# --start only once that daemon listens, or it starts a second one.
i=0
while [ ! -S "$XDG_RUNTIME_DIR/keyring/control" ] && [ "$i" -lt 50 ]; do
    i=$((i + 1)); sleep 0.1
done
[ -S "$XDG_RUNTIME_DIR/keyring/control" ] || exit 0
exec /usr/bin/gnome-keyring-daemon --start >/dev/null 2>&1

把它安裝到上面那一行指定的位置,同時安裝金鑰圈的套件,並遮蔽守護程式的按需啟用:

sudo apt install gnome-keyring libpam-gnome-keyring
sudo install -D -m 755 unlock-keyring /usr/local/lib/wlshare/unlock-keyring
systemctl --user mask gnome-keyring-daemon.socket gnome-keyring-daemon.service

設為 optional,因此打不開的金鑰圈只會在工作階段中多一次提示,永遠不會讓 VNC 登入失敗。請遮蔽(mask)gnome-keyring-daemon.socket 和 .service,讓任何經由啟用機制啟動的常駐程式都不會搶在持有密碼的那一個之前占用 Secret Service。請用 passwd 變更帳戶的密碼,它會透過 PAM 重新加密金鑰圈;以其他方式寫入 /etc/shadow 的雜湊會讓金鑰圈仍使用舊密碼。

開機即啟動的無頭桌面

wlroots 的無頭後端不驅動任何顯示器,也不開啟任何輸入裝置,因此合成器不需要 seat 或 logind 工作階段:它可以是一個 systemd 使用者服務,搭配 lingering 就能在開機時啟動,不需要任何人登入。它建立一個輸出 HEADLESS-1,由 wlshare 共享,並為每個用戶端調整大小。示範中的 sway 和 LXQt 桌面就是這樣執行的。

  1. 使用者管理員

    由 loginctl enable-linger 在開機時啟動,並啟動合成器的單元

  2. 開機時,透過 lingering
  3. 合成器

    設定 WLR_BACKENDS=headless 的 sway 或 labwc;其通訊端建立後,便啟動它的工作階段 target

  4. 隨工作階段
  5. wlshare

    wlshare.service,由工作階段 target 所綁定的 graphical-session.target 帶起

# ~/.config/systemd/user/sway-headless.service
[Unit]
Description=sway (headless)
After=dbus.socket

[Service]
Environment=WLR_BACKENDS=headless
Environment=WLR_LIBINPUT_NO_DEVICES=1
ExecStart=/usr/bin/sway
# graphical-session.target is what wlshare is reached through: stop it with the compositor
ExecStopPost=-/usr/bin/systemctl --user stop graphical-session.target
Restart=on-failure

[Install]
WantedBy=default.target
# ~/.config/systemd/user/sway-session.target
[Unit]
Description=sway session
BindsTo=graphical-session.target

合成器在其通訊端建立後,把環境交給使用者管理員並啟動 target,寫在 sway 設定的最後(labwc 則從 ~/.config/labwc/autostart 執行同樣的兩行):

exec systemctl --user import-environment WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP \
    && systemctl --user start sway-session.target
loginctl enable-linger "$USER"
systemctl --user daemon-reload
systemctl --user enable --now sway-headless.service wlshare.service

結束時若只停止工作階段 target,wlshare 會對著已經不存在的合成器不斷重新啟動,因此合成器的單元會自行停止 graphical-session.target。請在合成器的設定中把 HEADLESS-1 固定在原點,讓為第二個顯示器建立的輸出落在它的右側。

labwc(一個堆疊式合成器)上的 LXQt,在使用者管理員下以無頭方式執行,透過同一個 wlshare,縮放比例為 2。

輸出

每個連線顯示一個輸出;輸出永遠不會合併成同一個影格緩衝區。像 zDesk 這樣會要求的用戶端,會收到工作階段的輸出清單並在其間切換,而同一個用戶端的第二個連線會在第一個旁邊顯示另一個輸出,有自己的尺寸和密度:同時兩個,不會更多。一般 VNC 用戶端只看到一個,即 output 或上一個用戶端所選的那一個。

只有 HEADLESS-* 輸出會為用戶端調整大小或縮放比例;顯示器維持其本身的模式。output 指明的是偏好而不是要求:合成器沒有該名稱的輸出時,會共享另一個輸出,等該輸出出現時,桌面再移到它上面。

兩個虛擬顯示器 beta

用戶端可以要求一個或兩個虛擬顯示器,就像 RDP 用戶端向 Windows 主機要求其顯示器那樣。在 sway 上,wlshare 透過 sway 的 IPC(在 SWAYSOCK 中找到)建立和拔除無頭輸出,直到數量相符為止,不需要任何設定:設定 virtual_displays = 2 的 zDesk 目標會得到第二個,下一個只要求一個的工作階段則會將它拔除。在其他合成器上,輸出就是合成器既有的那些。

無頭 sway 的兩個輸出分別顯示在兩個 Chromium 視窗中,每個視窗都是其輸出的尺寸:一段影片以 $mod+Shift+→ 移到第二個輸出再移回來。

主控台上的桌面也可共享

真實顯示器上的桌面,可以作為符合用戶端尺寸和密度的無頭輸出交給用戶端,在別處觀看時顯示器變暗,離開時再恢復。wlshare 在 state_socket 上告知是否有用戶端在桌面上,wlshare watch 則從獨立的處理程序追蹤這個狀態,並在每次變化時執行一個命令:

# config.toml
output = "HEADLESS-1"
state_socket = "wlshare/state.sock"
output_wait_secs = 5
wlshare watch \
    --held "swaymsg 'output HEADLESS-1 enable, output DP-1 disable'" \
    --free "swaymsg 'output DP-1 enable, output HEADLESS-1 disable'"
  • --held 在用戶端接管桌面時執行,--free 則在桌面持續 --free-after-secs(5 秒)無人持有時執行,因此在等待時間內重新連線的用戶端不會觸發任何一個。接管不算變化。
  • sway 會把停用的輸出上的工作區移到啟用的輸出上,因此視窗會跟著移到無頭輸出。設定 output_wait_secs 時,接管桌面的用戶端會等待 HEADLESS-1 啟用,並從它開始,而不是從顯示器開始。
  • 常駐程式結束時,無論以何種方式結束,通訊端都會關閉,監看程式會將此視為桌面已被離開:即使 wlshare 在有用戶端時被終止,顯示器仍會恢復。監看程式每次啟動時都會針對桌面目前的狀態執行對應的命令,因此兩個命令都必須能安全地執行兩次。
  • 在工作階段開始時建立 HEADLESS-1(swaymsg create_output),放在不與任何顯示器相鄰、遠在它們左側的位置,讓指標無法從主控台誤移到它上面。

變暗的桌面可以從主控台或透過 SSH 恢復,需設定 SWAYSOCK:swaymsg 'output * enable'。請把監看程式作為使用者單元執行在 wlshare 的單元旁,屬於 graphical-session.target,讓它隨工作階段啟動和停止。

聲音、攝影機和麥克風

一律開啟,不需要任何設定:由用戶端要求每一項,zDesk 在勾選它們啟動的工作階段中就會要求。

在桌面上存在期間
聲音一個 PipeWire 音訊輸出裝置 wlshare remote audio,設為預設;其監聽訊號依用戶端的要求以 FLAC 或 Opus 傳送有用戶端收聽時
攝影機一個 PipeWire 視訊來源 wlshare remote camera,來自用戶端的 H.264,以系統的 libavcodec 解碼有應用程式開啟它時
麥克風一個 PipeWire 音訊來源 wlshare remote microphone,來自用戶端的 16 位元 PCM有應用程式錄音時

有用戶端收聽時,所有跟隨預設的串流都會播放到 wlshare 的音訊輸出裝置,在主機上不發出聲音;固定到主機某個音訊輸出裝置的應用程式仍會在那裡發聲。最後一個收聽者離開時,該音訊輸出裝置隨之移除,主機自己的又成為預設。無頭桌面不需要音效卡。

桌面上的瀏覽器透過 xdg-desktop-portal 的 Camera 介面存取攝影機,這個介面只在搭配實作 Access 的後端(例如 xdg-desktop-portal-gtk)時才提供;單獨的 xdg-desktop-portal-wlr 不行。Chrome 還需要開啟 chrome://flags/#enable-webrtc-pipewire-camera。直接開啟 /dev/video* 的應用程式看不到它。麥克風兩者都不需要:它是一個一般的 PipeWire 音訊來源。

鍵盤

用戶端傳送 keysym,wlshare 透過它作為虛擬鍵盤的鍵盤配置上傳給合成器的 keymap,把 keysym 轉換為 keycode,因此這個 keymap 也就是工作階段輸入時使用的配置。自訂的配置放在 [xkb] 下,並在單元的環境中以 XKB_CONFIG_EXTRA_PATH 指明存放它的目錄:

[xkb]
layout = "swayvnc"
systemctl --user edit wlshare.service
# [Service]
# Environment=XKB_CONFIG_EXTRA_PATH=/etc/xkb

每個按鍵都必須保留其 keysym,只能增加動作,因為傳入的 keysym 是在同一個 keymap 中查找的。sway 的修飾鍵就是這樣為遠端桌面移到右 Alt:用一個配置,而不是一行 xkb_options,因為 sway 對虛擬鍵盤會忽略後者。鎖定鍵不能作為修飾鍵:Caps Lock 會在任何內容傳送之前先鎖定用戶端自己的狀態。

畫面

wlshare 會告訴有要求的用戶端影格緩衝區是以什麼密度繪製的,這是標準 RFB 做不到的,並把用戶端的密度當作輸出的縮放比例:2x 的瀏覽器會得到縮放比例 2 的輸出,以真實尺寸清晰顯示。一般 VNC 用戶端收到同樣的像素,但沒有密度資訊,並以 1x 顯示。

zDesk 工作階段:縮放比例 2 的輸出以 2x 顯示,完整呈現在視窗中。
同一個桌面以一般 VNC 顯示,不設定 subtype = "wlshare":其像素以 1x 顯示,尺寸變為兩倍,視窗中只看得到四分之一。

除了標準的 Raw 和 ZRLE,有要求的用戶端會收到 wlshare 自己的其中一種畫面,zDesk 把它們作為選擇器的 Picture 群組提供(請見zDesk 指南中的 wlshare):配合連線調整的整個桌面 H.264;RemoteFX Progressive 圖塊;每個移動區域各有一路視訊串流的無損 PNG 圖塊;或無論靜止或移動都無損的 planar 圖塊。

無論是整個桌面還是移動區域的 H.264,只要 VA-API 找得到主機的硬體編碼器就由它編碼,否則以最快預設的 libx264 編碼;hardware_h264 = false 會略過硬體。啟動時的探測會先嘗試 Intel 的低功耗編碼器,讓 GPU 的執行單元留給桌面使用,並記錄它找到的結果:

journalctl --user -b -u wlshare | grep h264
# hardware H.264: /dev/dri/renderD128 through VA-API, its low-power encoder

沒有視訊編碼器的虛擬機器(例如使用 VirGL 的虛擬機器)會以軟體編碼每個影格:輸出越大,佔用主機處理器越多。max_fps(預設 30)限制擷取的影格數;zDesk 頁面最多繪製 30 個影格。

用戶端

zDesk 以設定 subtype = "wlshare" 的 vnc 目標連線 wlshare,這會讓閘道向 wlshare 要求密度、輸出、畫面、聲音、攝影機和麥克風:

[[targets]]
name = "wayland"
protocol = "vnc"
subtype = "wlshare"
host = "192.0.2.12"
username = "me"          # leave out under [password]
password_ref = "wayland"
# virtual_displays = 2   # on sway, a second headless output
wlshare 目標上的選擇器:尺寸跟隨視窗、聲音可選 Opus 或 FLAC、攝影機和麥克風,以及 wlshare 的四種畫面。

任何其他列出 Cursor 偽編碼並能繪製 Raw 矩形的 RFB 3.8 用戶端都可以連線,以輸出本身的像素顯示,沒有聲音:例如支援 RSA-AES 的 TigerVNC。之所以需要 Cursor,是因為指標永遠不在影格緩衝區中。沒有列出 wlshare 擴充的用戶端不會收到它們。工作階段以哪些選項啟動,請見 zDesk 指南中的 wlshare。