在 Gnome 中创建一个虚拟显示器,然后将其它电脑变成副屏
事情是这样的.我希望在教师机上用机房管理软件播放自己 ubuntu 笔记本上的内容,最好是作为副屏,方便我放课件的时候看备注(pympress).然后就有了这么个邪修 workflow.
首先是创建一块虚拟显示器.这一步我折腾了好久.因为我用的是 wayland,查到的好多资料都只在 x11 上可用.ai 向我推荐了内核级的虚拟显示器,但是都不太好用,gnome 压根不识别.
查找了 100 年,我发现 一篇文章 介绍了 ubuntu 远程桌面可以创建一块虚拟屏幕,让其他电脑通过 rdp 连接.但是很遗憾,我这里不能显示鼠标光标.
但是这说明 gnome 肯定是支持在用户侧创建一个虚拟显示器的.我选择直接询问 ai 是怎么实现的,原来是有一个专门的 dbus 接口.由于我不熟悉操作 dbus,直接让 ai 给我写了个脚本.
#!/usr/bin/env /usr/bin/python3
import sys
import argparse
import dbus
from gi.repository import GLib
from dbus.mainloop.glib import DBusGMainLoop
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst
DBusGMainLoop(set_as_default=True)
Gst.init(None)
loop = GLib.MainLoop()
pipeline = None
def main():
global pipeline
parser = argparse.ArgumentParser(description="Create a virtual monitor in GNOME Wayland")
parser.add_argument("-W", "--width", type=int, default=1920, help="Width of the virtual monitor")
parser.add_argument("-H", "--height", type=int, default=1080, help="Height of the virtual monitor")
parser.add_argument("-f", "--fps", type=int, default=60, help="Framerate of the virtual monitor")
parser.add_argument("-v", "--view", action="store_true", help="Show the virtual monitor in a local window (useful for testing)")
args = parser.parse_args()
bus = dbus.SessionBus()
screen_cast_iface = 'org.gnome.Mutter.ScreenCast'
screen_cast_session_iface = 'org.gnome.Mutter.ScreenCast.Session'
# 1. 连接到 Mutter 的 ScreenCast API
try:
screen_cast = bus.get_object(screen_cast_iface, '/org/gnome/Mutter/ScreenCast')
except dbus.exceptions.DBusException as e:
print(f"Failed to connect to Mutter ScreenCast API: {e}")
print("Are you running GNOME on Wayland?")
sys.exit(1)
print("1. Creating screen cast session...")
session_path = screen_cast.CreateSession([], dbus_interface=screen_cast_iface)
print(f" Session path: {session_path}")
session = bus.get_object(screen_cast_iface, session_path)
print(f"2. Requesting virtual monitor creation ({args.width}x{args.height}@{args.fps})...")
# 属性配置:is-platform 表示捕获整个 stage 渲染,cursor-mode 1 表示嵌入鼠标指针
properties = dbus.Dictionary({
'is-platform': dbus.Boolean(True, variant_level=1),
'cursor-mode': dbus.UInt32(1, variant_level=1)
}, signature='sv')
# 核心 API:RecordVirtual
stream_path = session.RecordVirtual(properties, dbus_interface=screen_cast_session_iface)
print(f" Stream path: {stream_path}")
stream = bus.get_object(screen_cast_iface, stream_path)
def terminate():
global pipeline
if pipeline is not None:
print("\nStopping GStreamer pipeline...")
pipeline.send_event(Gst.Event.new_eos())
pipeline.set_state(Gst.State.NULL)
print("Stopping Mutter session...")
session.Stop(dbus_interface=screen_cast_session_iface)
loop.quit()
def on_message(bus, message):
t = message.type
if t == Gst.MessageType.EOS or t == Gst.MessageType.ERROR:
terminate()
def on_pipewire_stream_added(node_id):
global pipeline
print(f"3. PipeWire stream added! Node ID: {node_id}")
# 如果指定了 --view,则在本地打开一个窗口显示虚拟屏幕内容;否则使用 fakesink 仅保持连接
sink = "autovideosink" if args.view else "fakesink"
# 关键点:通过 video/x-raw 的 caps 限制,告诉 PipeWire (及 Mutter) 我们需要的分辨率
pipeline_str = (
f'pipewiresrc path={node_id} ! '
f'video/x-raw,max-framerate={args.fps}/1,width={args.width},height={args.height} ! '
f'videoconvert ! {sink}'
)
print(f" Starting pipeline: {pipeline_str}")
pipeline = Gst.parse_launch(pipeline_str)
pipeline.set_state(Gst.State.PLAYING)
pipeline.get_bus().connect('message', on_message)
print("✅ Virtual monitor is now active! Check your GNOME Display Settings.")
# 监听 PipeWire 流就绪信号
stream.connect_to_signal("PipeWireStreamAdded", on_pipewire_stream_added)
print("4. Starting session...")
session.Start(dbus_interface=screen_cast_session_iface)
print("Press Ctrl+C to destroy the virtual monitor and exit.")
try:
loop.run()
except KeyboardInterrupt:
print("\nInterrupted by user.")
terminate()
if __name__ == '__main__':
main()
然后就是共享桌面了.我选择的是 deskreen,在浏览器里就能直接使用.
如果你愿意花十块钱买一个 HDMI 欺骗器就可以省去第一步.反正我不愿意.