はじめに
Ubuntu 26.04をインストールした初代GPD Pocketをいろいろ触っています。GPD Pocketは7インチという小さな画面が特徴ですが、実際に使っていると、「もう少し画面が広ければ使いやすいのにな」と思うことがあります。
そこで、手元にあるiPadをGPD Pocketの外部モニターとして使えないか試してみることにしました。有線であればHDMIキャプチャなどを使う方法もありますが、せっかくの小型PCなのでケーブルは増やしたくありません。今回の目標は次のようにしました。
- GPD PocketとiPadを無線接続する
- iPadをミラーリングではなく第2モニターとして使う
- HDMIダミープラグなどは使用しない
- UbuntuのWayland環境をそのまま使用する
最終的には、Mutterで仮想モニターを作成 → DeskreenでiPadへ配信という方法で実現できました。
今回の環境
GPD Pocket側の環境は以下です。
$ echo $XDG_SESSION_TYPE
wayland
$ gnome-shell --version
GNOME Shell 50.1
$ echo $XDG_CURRENT_DESKTOP
ubuntu:GNOMEGPD PocketのCPUアーキテクチャはx86_64です。今回はUbuntu 26.04のWayland環境で試しています。
まずDeskreenを試してみる
iPadへ画面を無線転送するソフトとして、今回はDeskreen Community Editionを使用しました。Deskreen Community Edition(Deskreen CE)は公式配布ページからAppImage版を入手しました。
https://deskreen.com/ja/downloads-ce/
DeskreenをGPD Pocketで起動し、iPadから接続すると、GPD Pocketの画面をiPadへ表示すること自体は比較的簡単にできました。しかし、この状態では基本的に画面のミラーリングです。





今回作成したい構成は以下のイメージです。
GPD Pocket
┌──────────┐
│ 画面1 │
└──────────┘
┌──────────┐
│ iPad │
│ 画面2 │
└──────────┘そこで、Ubuntuに仮想的な第2モニターを作ることにしました。
Wayland上で仮想モニターを作れるか調べる
Ubuntu 26.04ではWaylandを使用していることは確認済みです。当初はX11へ切り替えて仮想ディスプレイを作る方法も考えましたが、できればWaylandのまま実現したいところです。調べていくと、GNOMEのウィンドウマネージャーであるMutterにはScreenCast用のD-Busインターフェースがあります。
さっそく、確認してみます。
gdbus introspect \
--session \
--dest org.gnome.Mutter.ScreenCast \
--object-path /org/gnome/Mutter/ScreenCastこの環境では、CreateSessionが利用できました。さらにScreenCast Sessionを作成して調べてみると、
RecordMonitor
RecordWindow
RecordArea
RecordVirtualといったメソッドが存在しました。この中の RecordVirtual を利用します。

PythonからMutterの仮想モニターを作る
PythonのPyGObjectからD-Bus経由でMutterへアクセスします。PyGObjectが利用できることを確認しました。
python3 -c 'from gi.repository import Gio, GLib; print("PyGObject OK")'結果、「PyGObject OK」となれば準備OKです。
今回はホームディレクトリに、以下のスクリプトを作成しました。
~/mutter-virtual-monitor-test.pyスクリプトの内容は以下です。
#!/usr/bin/env python3
from gi.repository import Gio, GLib
BUS_NAME = "org.gnome.Mutter.ScreenCast"
ROOT_PATH = "/org/gnome/Mutter/ScreenCast"
ROOT_IFACE = "org.gnome.Mutter.ScreenCast"
SESSION_IFACE = "org.gnome.Mutter.ScreenCast.Session"
STREAM_IFACE = "org.gnome.Mutter.ScreenCast.Stream"
WIDTH = 1280
HEIGHT = 960
REFRESH_RATE = 60.0
def on_stream_signal(proxy, sender_name, signal_name, parameters):
if signal_name == "PipeWireStreamAdded":
node_id = parameters.unpack()[0]
print()
print("================================")
print(f"PipeWire node ID: {node_id}")
print("================================")
print()
def main():
bus = Gio.bus_get_sync(
Gio.BusType.SESSION,
None
)
root = Gio.DBusProxy.new_sync(
bus,
Gio.DBusProxyFlags.NONE,
None,
BUS_NAME,
ROOT_PATH,
ROOT_IFACE,
None,
)
result = root.call_sync(
"CreateSession",
GLib.Variant("(a{sv})", ({},)),
Gio.DBusCallFlags.NONE,
-1,
None,
)
session_path = result.unpack()[0]
print(f"Session: {session_path}")
session = Gio.DBusProxy.new_sync(
bus,
Gio.DBusProxyFlags.NONE,
None,
BUS_NAME,
session_path,
SESSION_IFACE,
None,
)
mode = {
"size": GLib.Variant(
"(uu)",
(WIDTH, HEIGHT)
),
"refresh-rate": GLib.Variant(
"d",
REFRESH_RATE
),
"is-preferred": GLib.Variant(
"b",
True
),
}
modes = GLib.Variant(
"aa{sv}",
[mode]
)
properties = {
"is-platform": GLib.Variant(
"b",
True
),
"modes": modes,
}
result = session.call_sync(
"RecordVirtual",
GLib.Variant(
"(a{sv})",
(properties,)
),
Gio.DBusCallFlags.NONE,
-1,
None,
)
stream_path = result.unpack()[0]
print(f"Stream: {stream_path}")
stream = Gio.DBusProxy.new_sync(
bus,
Gio.DBusProxyFlags.NONE,
None,
BUS_NAME,
stream_path,
STREAM_IFACE,
None,
)
stream.connect(
"g-signal",
on_stream_signal
)
session.call_sync(
"Start",
None,
Gio.DBusCallFlags.NONE,
-1,
None,
)
print()
print("Virtual monitor started.")
print("Ctrl+C で終了します。")
print()
loop = GLib.MainLoop()
try:
loop.run()
except KeyboardInterrupt:
print()
print("Stopping virtual monitor...")
try:
session.call_sync(
"Stop",
None,
Gio.DBusCallFlags.NONE,
-1,
None,
)
except Exception as e:
print(f"Stop failed: {e}")
print("Done.")
if __name__ == "__main__":
main()今回はiPad側の仮想モニターを、1280 × 960の解像度で設定しています。解像度を変更したい場合は、スクリプト冒頭の WIDTH と HEIGHT を変更します。
仮想モニターを起動する
作成したスクリプトを実行します。
python3 ~/mutter-virtual-monitor-test.py正常に動作すると、以下が表示されます。「PipeWire node ID」の番号は毎回変わります。
Session: /org/gnome/Mutter/ScreenCast/Session/...
Stream: /org/gnome/Mutter/ScreenCast/Stream/...
Virtual monitor started.
Ctrl+C で終了します。
================================
PipeWire node ID: ...
================================このターミナルは閉じず、そのままにしておきます。現在の起動方法では、ターミナルを閉じるとスクリプトも終了し、仮想モニターが削除されます。ここは今後要改善ですね。
本当に第2モニターができた
「設定」→「ディスプレイ」を開いて確認すると、内蔵ディスプレイとMetaVendorの2台のディスプレイが認識されているじゃないですか。


今回作成した仮想モニターは、Ubuntuからは以下のように認識されています。

コマンドでも確認できます。
gdbus call \
--session \
--dest org.gnome.Mutter.DisplayConfig \
--object-path /org/gnome/Mutter/DisplayConfig \
--method org.gnome.Mutter.DisplayConfig.GetCurrentState以下が返ってくるはずです。
Meta-0
MetaVendor
1280x960@60.000ここまで来れば、Ubuntuには物理モニターと同じように扱える仮想第2画面が存在しています。
DeskreenからMetaVendorを共有する
ここまで来たら仮想第2画面をiPadに表示することは目前です。
まずは、Deskreen CEを起動します。iPadからDeskreenへ接続し、画面共有を開始すると、Waylandの画面共有ダイアログに、以下の2つのモニターが表示されます。


今回は拡張した仮想第2モニターである、MetaVendorを選択します。すると、iPadにはGPD Pocketの内蔵画面ではなく、仮想第2モニターの内容が表示されました。あとはGPD Pocket上でウィンドウを画面右側へドラッグしていくと、そのままiPad側へ移動できます。これで、iPadをGPD Pocketの無線第2モニターとして使用できました。

仕組み
今回の構成を簡単にまとめると以下のようになります。
GPD Pocket
Ubuntu 26.04 / Wayland
│
├── DSI-1
│ └── 内蔵ディスプレイ
│
└── Mutter RecordVirtual()
│
└── Meta-0
1280×960
│
PipeWire
│
Deskreen
│
WebRTC
│
Wi-Fi
│
▼
iPadポイントは、Deskreenが仮想モニターを作っているわけではないことです。Mutterで先に Meta-0 という仮想モニターを作り、その映像をDeskreenでiPadへ送っています。そのため、HDMIダミープラグなども必要ありません。
今回構成はVPN接続中でも使えるのか
GPD PocketではVPN接続も利用していますが、VPN接続中でもiPadの無線サブモニターは問題なく使用できました。iPadとの通信は同一LAN内で行われており、今回のVPN設定ではローカルLAN宛の通信までVPNへ送っていないため、そのまま併用できています。
まとめ
今回、Ubuntu 26.04をインストールしたGPD Pocketで、iPadを無線の第2モニターとして利用できました。
最終的には、以下の要素の組み合わせになりました。特に嬉しいのは、HDMIダミープラグを使用せず、Waylandのまま実現できたことですね。
- Ubuntu 26.04
- Wayland
- Mutterの仮想モニター
- PipeWire
- Deskreen CE
- iPad
GPD PocketとiPadだけ持って無線LANがある環境にいけば、iPadを用いてデュアルモニター構成が作れます。とても嬉しいですね。
ただし現状では、Ubuntuを起動するたびに以下のコマンドを実行し、仮想モニターを起動する必要があります。毎回コマンドを実行するのは少し面倒なので、次回はこの仮想モニターをsystemdで自動起動できるようにしてみたいと思います。
python3 ~/mutter-virtual-monitor-test.py
その他
初代GPD Pocket + Ubuntu 26.04 関連の記事はこちら!!
「 初代GPD Pocket + Ubuntu 26.04 関連の記事」 一覧
