diff --git a/ui/src/connecteddevice.py b/ui/src/connecteddevice.py index a1883ba..3693da2 100644 --- a/ui/src/connecteddevice.py +++ b/ui/src/connecteddevice.py @@ -4,10 +4,13 @@ from .license import BREEZY_GNOME_FEATURES from .settingsmanager import SettingsManager from .shortcutdialog import bind_shortcut_settings from .statemanager import StateManager +from .virtualdisplay import VirtualMonitor from .xrdriveripc import XRDriverIPC import gettext +import logging _ = gettext.gettext +logger = logging.getLogger('breezy_ui') @Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/connected-device.ui') class ConnectedDevice(Gtk.Box): @@ -28,6 +31,7 @@ class ConnectedDevice(Gtk.Box): widescreen_mode_switch = Gtk.Template.Child() widescreen_mode_row = Gtk.Template.Child() curved_display_switch = Gtk.Template.Child() + add_virtual_display_button = Gtk.Template.Child() set_toggle_display_distance_start_button = Gtk.Template.Child() set_toggle_display_distance_end_button = Gtk.Template.Child() reassign_recenter_display_shortcut_button = Gtk.Template.Child() @@ -55,6 +59,7 @@ class ConnectedDevice(Gtk.Box): self.follow_mode_switch, self.follow_threshold_scale, self.curved_display_switch, + # self.add_virtual_display_button, self.set_toggle_display_distance_start_button, self.set_toggle_display_distance_end_button, self.movement_look_ahead_scale @@ -87,6 +92,7 @@ class ConnectedDevice(Gtk.Box): self.set_toggle_display_distance_start_button, self.set_toggle_display_distance_end_button ]) + self.add_virtual_display_button.connect('clicked', self.on_add_virtual_display) self.state_manager = StateManager.get_instance() self.state_manager.bind_property('follow-mode', self.follow_mode_switch, 'active', GObject.BindingFlags.DEFAULT) @@ -166,6 +172,12 @@ class ConnectedDevice(Gtk.Box): for widget in widgets: widget.connect('clicked', lambda *args, widget=widget: on_set_display_distance_toggle(widget)) reload_display_distance_toggle_button(widget) + + def on_add_virtual_display(self, widget): + VirtualMonitor(1920, 1080, self.on_virtual_display_ready).create() + + def on_virtual_display_ready(self): + logger.info("Virtual display ready") def _on_widget_destroy(self, widget): self.state_manager.unbind_property('follow-mode', self.follow_mode_switch, 'active') diff --git a/ui/src/gtk/connected-device.ui b/ui/src/gtk/connected-device.ui index 3a73559..2cec2c6 100644 --- a/ui/src/gtk/connected-device.ui +++ b/ui/src/gtk/connected-device.ui @@ -84,6 +84,19 @@ + + + Virtual monitors + 2 + + + add-virtual-display + 3 + Add + + + + diff --git a/ui/src/meson.build b/ui/src/meson.build index cb1ecbc..8272bfa 100644 --- a/ui/src/meson.build +++ b/ui/src/meson.build @@ -46,6 +46,7 @@ breezydesktop_sources = [ 'shortcutdialog.py', 'statemanager.py', 'time.py', + 'virtualdisplay.py', 'verify.py', 'window.py' ] diff --git a/ui/src/virtualdisplay.py b/ui/src/virtualdisplay.py new file mode 100644 index 0000000..b57f473 --- /dev/null +++ b/ui/src/virtualdisplay.py @@ -0,0 +1,71 @@ +#!/usr/bin/python3 + +import logging +import sys +import signal +import pydbus +import gi +gi.require_version('Gst', '1.0') +from gi.repository import GLib, GObject, Gst + +logger = logging.getLogger('breezy_ui') + +screen_cast_iface = 'org.gnome.Mutter.ScreenCast' +screen_cast_session_iface = 'org.gnome.Mutter.ScreenCast.Session' +screen_cast_stream_iface = 'org.gnome.Mutter.ScreenCast.Session' +gst_pipeline_format = "pipewiresrc path=%u ! video/x-raw,max-framerate=120/1,width=%d,height=%d ! videoconvert ! fakesink sync=false" + + +def _screen_cast_session(): + bus = pydbus.SessionBus() + screen_cast = bus.get(screen_cast_iface, '/org/gnome/Mutter/ScreenCast') + session_path = screen_cast.CreateSession([]) + logger.info("session path: %s" % session_path) + screen_cast_session = bus.get(screen_cast_iface, session_path) + + return screen_cast_session + +class VirtualMonitor: + def __init__(self, width, height, on_ready_cb): + self.width = width + self.height = height + self.on_ready_cb = on_ready_cb + + Gst.init(None) + + def create(self): + session = _screen_cast_session() + stream_path = session.RecordVirtual({ + 'is-platform': GLib.Variant.new_boolean(True), + }) + logger.info("stream path: %s" % stream_path) + bus = pydbus.SessionBus() + self.stream = bus.get(screen_cast_iface, stream_path) + + self.stream.onPipeWireStreamAdded = self._on_pipewire_stream_added + + session.Start() + + def terminate(self): + if self.stream is not None: + self.stream.Stop() + + if self.pipeline is not None: + self.pipeline.send_event(Gst.Event.new_eos()) + self.pipeline.set_state(Gst.State.NULL) + + def _on_message(self, bus, message): + type = message.type + logger.info("message type: %s" % type) + if type == Gst.MessageType.EOS or type == Gst.MessageType.ERROR: + self.terminate() + + def _on_pipewire_stream_added(self, node_id): + logger.info("pipe wire stream added: %u" % node_id) + + self.pipeline = Gst.parse_launch(gst_pipeline_format % (node_id, self.width, self.height)) + self.pipeline.set_state(Gst.State.PLAYING) + self.pipeline.get_bus().connect('message', self._on_message) + self.pipeline.set_state(Gst.State.PAUSED) + + self.on_ready_cb() \ No newline at end of file