Hyprland専用のRDPサーバーhypr-rdpを作った

10 min read

はじめに

以前、HyprlandにWindows標準のRDP経由で接続する方法を書きました。

https://www.munenick.me/blog/hyprland-rdp

その記事では、xrdpとWayVNCを組み合わせて、Windows標準のRDPクライアントからHyprlandへ接続する方法を紹介しました。

しかし、HyprlandにはそのままRDPサーバーとして動作する仕組みがありません。 そのため、既存のRDPサーバーであるxrdpを入口にして、内部ではWayVNCへブリッジするという複雑な構成にしていました。

RDPで接続したいだけなのに、RDP、VNC、Wayland、HyprlandのHeadless出力をそれぞれ意識する必要があります。 また、xrdp側とWayVNC側の互換性や、動的解像度変更などの問題もありました。

それなら、Hyprland専用のRDPサーバーを作れば良いのではないかと思いました。

そこで hypr-rdp を作りました。

https://github.com/MuNeNiCK/hypr-rdp

hypr-rdpとは

hypr-rdp は、Hyprland向けのネイティブRDPサーバーです。

Windows標準のリモートデスクトップクライアントなど、通常のRDPクライアントからHyprlandへ接続するために作っています。

hypr-rdp 自体がRDPサーバーとして動作し、Hyprlandの画面取得、入力、音声、クリップボードなどを扱います。

主な機能は以下です。

機能 内容
画面転送 H.264 / EGFX
Codec AVC420、実験的なAVC444
エンコード VA-API、利用不可時はソフトウェアエンコードへフォールバック
画面キャプチャ wlr-screencopy-v1ext-image-copy-capture-v1
音声 PipeWire経由でRDPクライアントへ転送
クリップボード テキスト・画像の双方向同期
入力 キーボード・マウス入力
TLS 自己署名証明書の自動生成、任意証明書指定
設定ファイル ~/.config/hypr-rdp/config.toml

使い方

要件

hypr-rdp を使うには、Hyprland 0.54以降が必要です。

実行時には、主に以下の依存関係が必要です。(インストール時に解決されます。)

- ffmpeg / libavcodec
- libva
- PipeWire
- libxkbcommon

VA-APIによるハードウェアエンコードを使う場合は、GPUに応じたVA-API driverも必要です。

Intel GPUの場合は intel-media-driver、AMD GPUの場合は libva-mesa-driver などを入れます。

インストール

Arch LinuxではAURからインストールできます。

yay -S hypr-rdp

Nixを使う場合は以下でインストールできます。

nix run github:MuNeNICK/hypr-rdp#hypr-rdp -- --help

GitHub Releasesからprebuilt binaryを取得して使うこともできます。

tar xzf hypr-rdp-v*.tar.gz
sudo install -Dm755 hypr-rdp /usr/local/bin/hypr-rdp

起動する

別マシンから接続したい場合は、bind addressを指定し、起動します。

hypr-rdp -u user -p pass --bind 0.0.0.0:3389

解像度やフレームレートを指定する

解像度やフレームレートを指定する場合は、--resolution--fps を使います。

hypr-rdp -u user -p pass --resolution 2560x1440 --fps 60

RDP用の出力を固定サイズで使いたい場合に指定します。

特定の出力を使う

特定の出力をキャプチャしたい場合は、--output を指定します。

hypr-rdp -u user -p pass --output DP-1

RDP用のHeadless出力ではなく、既存の物理出力や特定のHyprland出力を使いたい場合に指定します。

キャプチャ方式を指定する

画面キャプチャ方式は --capture-mode で指定できます。

wlr-screencopy-v1 を使う場合は以下です。

hypr-rdp -u user -p pass --capture-mode wlr

ext-image-copy-capture-v1 を使う場合は以下です。

hypr-rdp -u user -p pass --capture-mode ext

エンコード設定

画面転送にはRDPのEGFXを使っています。

デフォルトではAVC420を使います。

hypr-rdp -u user -p pass --egfx-codec avc420

実験的にAVC444も指定できます。

hypr-rdp -u user -p pass --egfx-codec avc444

H.264のビットレートを指定する場合は --bitrate を使います。

hypr-rdp -u user -p pass --bitrate 20000000

品質を指定する場合は --quality を使います。

hypr-rdp -u user -p pass --quality 23

VA-APIが利用できる環境ではハードウェアエンコードを利用します。 利用できない場合はソフトウェアエンコードへフォールバックします。

設定ファイル

毎回CLI引数を指定するのが面倒な場合は、設定ファイルを使えます。

設定ファイルは以下です。

~/.config/hypr-rdp/config.toml

例です。

bind = "0.0.0.0:3389"
username = "user"
password = "pass"

# resolution = "1920x1080"
capture_mode = "wlr"
bitrate = 10000000
quality = 23
fps = 30
egfx_codec = "avc420"

# output = "DP-1"

CLI引数で指定した値は、設定ファイルより優先されます。

普段使う設定は config.toml に書いておき、必要な時だけCLI引数で上書きできます。

TLSと認証

RDPではTLSを使います。

hypr-rdp は、証明書を指定しない場合は自己署名証明書を自動生成します。

自分で証明書を指定したい場合は、--cert--key を使います。

hypr-rdp \
  -u user \
  -p pass \
  --cert /path/to/cert.pem \
  --key /path/to/key.pem

ユーザー名とパスワードは -u / -p で指定できます。

hypr-rdp -u user -p pass

設定ファイルにも書けます。

username = "user"
password = "pass"

0.0.0.0:3389 で待ち受ける場合は、必ず認証情報を設定してください。

内部の仕組み

全体構成

hypr-rdp は、RDPサーバー部分にIronRDPを使っています。

そこに、Hyprland向けの処理を組み合わせています。

大まかな構成は以下です。

RDP Client

IronRDP

hypr-rdp
  ├─ Hyprland IPC
  ├─ Wayland capture
  ├─ H.264 / EGFX
  ├─ PipeWire audio
  ├─ Clipboard
  └─ Input

RDP接続を受け付け、Hyprlandの画面を取得し、H.264でエンコードしてRDPクライアントへ送ります。

同時に、RDPクライアントからのキーボード・マウス入力をHyprlandへ渡し、音声やクリップボードもRDP channelとして扱います。

セッション用の出力を用意する

RDPで接続するには、クライアントへ送るための画面が必要です。

既存の出力を使う場合は --output で指定できます。 指定しない場合、hypr-rdp はRDPセッション用のHeadless出力を用意します。

内部では、Hyprland IPCへ直接接続します。

HyprlandのIPC socketは、HYPRLAND_INSTANCE_SIGNATUREXDG_RUNTIME_DIR からパスを組み立てています。

$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket.sock

イベント用にはsocket2を使います。

$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock

出力作成時は、以下のようなHyprland IPCコマンドを送ります。

output create headless hypr-rdp

Hyprland側で出力が追加されると、socket2に monitoradded イベントが流れます。

hypr-rdp はこのイベントを待ち、実際に作成された hypr-rdp-* の出力名を取得します。

出力名が取得できたら、keyword monitor で解像度や配置を設定します。

hypr-rdp-1,1920x1080@60,-9999x0,1

RDPクライアントが初期サイズを要求した場合や、DisplayControlでサイズ変更を要求した場合は、Headless出力をリサイズします。

セッション終了時には、作成したHeadless出力を削除します。

output remove hypr-rdp-1

また、前回のセッションで hypr-rdp-* のHeadless出力が残っていた場合は、再利用できるようにしています。

画面を取得してRDPへ送る

出力が用意できたら、その出力をWaylandのプロトコルでキャプチャします。

対応しているキャプチャ方式は以下です。

- wlr-screencopy-v1
- ext-image-copy-capture-v1

取得したフレームはH.264でエンコードし、RDPのEGFXとしてクライアントへ送信します。

VA-APIが使える場合はハードウェアエンコードを使います。 使えない場合はソフトウェアエンコードへフォールバックします。

ソフトウェアエンコード時は、H.264の扱いやすさを考慮して、長辺3840、短辺2160を上限にしています。 それを超えるサイズが要求された場合は、偶数サイズに丸めつつ上限内に収まるように縮小します。

音声を送る

音声転送にはPipeWireを使います。

PipeWireから音声を取得し、RDPのRDPSND channel経由でクライアントへ送ります。

これにより、Hyprland環境で再生される音をRDPクライアント側で聞けるようにしています。

クリップボードを同期する

クリップボードは双方向同期に対応しています。

テキストだけでなく、画像クリップボードも扱えるようにしています。

Wayland側のクリップボードと、RDP側のCLIPRDR channelをつなぐことで、クライアントとHyprland側の間でクリップボードを同期します。

リモートデスクトップとして使う場合、クリップボード同期はかなり重要です。

画面が見えるだけではなく、普段の作業として使えるかどうかに効いてきます。

入力を渡す

入力は、RDPクライアントから受け取ったキーボード・マウスイベントをHyprlandへ渡します。

Wayland側では、virtual keyboardやvirtual pointer系の仕組みを使います。

画面転送だけであれば比較的単純ですが、リモートデスクトップとして使うには入力処理が必要です。

hypr-rdp では、キーボード、ポインタ、クリック、スクロールなどをRDP側から受け取り、Hyprland側へ渡すようにしています。

xdg-desktop-portalではなくHyprland IPCを使っている理由

Wayland環境で画面共有やリモートデスクトップを扱う場合、本来はxdg-desktop-portal経由で扱えるのが自然です。

xdg-desktop-portalは、アプリケーションとcompositorの間で、画面共有やリモートデスクトップなどのセッションを扱うための標準的な入口になります。

Waylandでは、アプリケーションが勝手に画面を取得したり入力を注入したりするのではなく、portalを経由してユーザーの許可やcompositor側の制御を通す、という考え方があります。

そのため、リモートデスクトップや画面共有の仕組みとしては、xdg-desktop-portal経由で完結できるのが理想です。

ただし、現時点ではHyprland側でRDPサーバー用途に必要なAPIがまだ揃っていません。

関連するIssueやPRもあります。

種別 番号 内容 状態
Issue #252 org.freedesktop.portal.RemoteDesktop 対応要望 Open
PR #308 RemoteDesktop portal実装 Draft
PR #402 RemoteDesktop portal + ConnectToEIS対応 Open
PR #268 Input Capture Desktop Portal実装 Open

特に #402 では、Hyprland向けに org.freedesktop.impl.portal.RemoteDesktop を実装し、ConnectToEIS、wlr-virtual-pointer、virtual-keyboardを使ったリモート入力注入が進められています。

つまり、xdg-desktop-portal経由のRemoteDesktop対応は進んでいますが、まだ正式に利用できる状態ではありません。

そのため、hypr-rdp では現時点で実用できる方式として、Hyprland IPCを直接使っています。

具体的には以下のような流れです。

Hyprland IPCへ接続

output create headless

socket2でmonitoraddedを待つ

keyword monitorで解像度・配置を設定

wlr-screencopy-v1 / ext-image-copy-capture-v1で画面取得

RDP EGFXでクライアントへ転送

将来的にxdg-desktop-portal側のRemoteDesktop対応が正式に入り、Hyprlandでも安定して使えるようになった場合は、そちらにも対応したいです。

おわりに

hypr-rdp は、Hyprland専用のRDPサーバーです。

Windows標準のRDPクライアントからHyprlandへ接続するために作っています。

H.264/EGFXによる画面転送、PipeWireによる音声転送、双方向クリップボード、キーボード・マウス入力、TLS、設定ファイルなどに対応しています。

現時点では、Headless出力の作成や管理にHyprland IPCを直接使っています。

本来はxdg-desktop-portal経由で扱えるのが理想ですが、Hyprland側ではRemoteDesktop関連の対応がまだ進行中です。 そのため、今はHyprland IPCを使った実装にしています。

将来的にxdg-desktop-portalのRemoteDesktop対応が正式に入り、Hyprlandでも安定して使えるようになった場合は、そちらにも対応したいです。

HyprlandにWindows標準RDPから接続したい場合は、試してみてください。