diff --git a/ui/src/breezydesktop.gresource.xml b/ui/src/breezydesktop.gresource.xml index 843487c..e8d4f5c 100644 --- a/ui/src/breezydesktop.gresource.xml +++ b/ui/src/breezydesktop.gresource.xml @@ -10,6 +10,7 @@ gtk/no-extension.ui gtk/no-license.ui gtk/shortcut-dialog.ui + gtk/virtual-display-row.ui gtk/window.ui diff --git a/ui/src/connecteddevice.py b/ui/src/connecteddevice.py index a695415..23587ba 100644 --- a/ui/src/connecteddevice.py +++ b/ui/src/connecteddevice.py @@ -1,4 +1,4 @@ -from gi.repository import Gio, Gtk, GObject +from gi.repository import Gio, GLib, Gtk, GObject from .configmanager import ConfigManager from .extensionsmanager import ExtensionsManager from .license import BREEZY_GNOME_FEATURES @@ -6,6 +6,7 @@ from .settingsmanager import SettingsManager from .shortcutdialog import bind_shortcut_settings from .statemanager import StateManager from .virtualdisplaymanager import VirtualDisplayManager +from .virtualdisplayrow import VirtualDisplayRow from .xrdriveripc import XRDriverIPC import gettext import logging @@ -32,8 +33,10 @@ 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_1080p = Gtk.Template.Child() - add_virtual_display_button_1440p = Gtk.Template.Child() + top_features_group = Gtk.Template.Child() + add_virtual_display_menu = Gtk.Template.Child() + add_virtual_display_button = Gtk.Template.Child() + launch_display_settings_button = Gtk.Template.Child() set_toggle_display_distance_start_button = Gtk.Template.Child() set_toggle_display_distance_end_button = Gtk.Template.Child() reassign_toggle_xr_effect_shortcut_button = Gtk.Template.Child() @@ -70,8 +73,8 @@ class ConnectedDevice(Gtk.Box): # self.follow_mode_switch, # self.follow_threshold_scale, # self.curved_display_switch, - self.add_virtual_display_button_1080p, - self.add_virtual_display_button_1440p, + self.add_virtual_display_menu, + self.add_virtual_display_button, self.set_toggle_display_distance_start_button, self.set_toggle_display_distance_end_button, self.movement_look_ahead_scale, @@ -116,8 +119,9 @@ class ConnectedDevice(Gtk.Box): self.set_toggle_display_distance_start_button, self.set_toggle_display_distance_end_button ]) - self.add_virtual_display_button_1080p.connect('clicked', lambda *args: self.on_add_virtual_display(1920, 1080)) - self.add_virtual_display_button_1440p.connect('clicked', lambda *args: self.on_add_virtual_display(2560, 1440)) + self.add_virtual_display_menu.set_active_id('1080p') + self.add_virtual_display_button.connect('clicked', self.on_add_virtual_display) + self.launch_display_settings_button.connect('clicked', self._launch_display_settings) self.state_manager = StateManager.get_instance() # self.state_manager.bind_property('follow-mode', self.follow_mode_switch, 'active', GObject.BindingFlags.DEFAULT) @@ -133,6 +137,7 @@ class ConnectedDevice(Gtk.Box): self.use_optimal_monitor_config_switch.connect('notify::active', self._refresh_use_optimal_monitor_config) + self._handle_switch_enabled_state(self.effect_enable_switch, None) self._handle_enabled_features(self.state_manager, None) self._handle_device_supports_sbs(self.state_manager, None) self._handle_enabled_config(None, None) @@ -144,6 +149,16 @@ class ConnectedDevice(Gtk.Box): self.connect("destroy", self._on_widget_destroy) + self.virtual_displays_by_pid = {} + + self._settings_displays_app_info = None + + # use Gio.AppInfo.get_all() and find the one where appinfo.get_id() == 'gnome-display-panel.desktop' + for appinfo in Gio.AppInfo.get_all(): + if appinfo.get_id() == 'gnome-display-panel.desktop': + self._settings_displays_app_info = appinfo + break + def _handle_monitor_wrapping_scheme_setting_changed(self, settings, val): self.monitor_wrapping_scheme_menu.set_active_id(val) @@ -208,12 +223,41 @@ class ConnectedDevice(Gtk.Box): 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, width, height): - logger.info(f"Adding virtual display {width}x{height}") + def on_add_virtual_display(self, *args): + resolution = self.add_virtual_display_menu.get_active_id() + logger.info(f"Adding virtual display {resolution}") + + width = 1920 + height = 1080 + if resolution == '1440p': + width = 2560 + height = 1440 + self.virtual_display_manager.create_virtual_display(width, height, 60) def _on_virtual_displays_update(self, virtual_display_manager, val): - logger.info(f"Found {len(virtual_display_manager.displays)} virtual displays") + GLib.idle_add(self._on_virtual_displays_update_gui, virtual_display_manager) + + def _on_virtual_displays_update_gui(self, virtual_display_manager): + self.launch_display_settings_button.set_visible( + self._settings_displays_app_info is not None and len(virtual_display_manager.displays) > 0 + ) + + for pid, child in self.virtual_displays_by_pid.items(): + self.top_features_group.remove(child) + + new_displays_by_pid = {} + for display in virtual_display_manager.displays: + child = self.virtual_displays_by_pid.get( + display['pid'], + VirtualDisplayRow(display['pid'], display['width'], display['height'], 60)) + self.top_features_group.add(child) + new_displays_by_pid[display['pid']] = child + + self.virtual_displays_by_pid = new_displays_by_pid + + def _launch_display_settings(self, *args): + self._settings_displays_app_info.launch() 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 d120fd2..3dbc084 100644 --- a/ui/src/gtk/connected-device.ui +++ b/ui/src/gtk/connected-device.ui @@ -37,7 +37,7 @@ 20 20 - + Features @@ -54,28 +54,47 @@ Virtual monitors 2 - 2 30 150 30 - - add-virtual-display + + 0 + launch-display-settings 3 - Add 1080p + Rearrange displays - - add-virtual-display - 3 - Add 1440p + + center + + + + + 1080p + 1440p + + + + + + add-virtual-display + list-add-symbolic + center + + + @@ -148,6 +167,10 @@ 30 150 30 + center + diff --git a/ui/src/gtk/virtual-display-row.ui b/ui/src/gtk/virtual-display-row.ui new file mode 100644 index 0000000..acb2c2f --- /dev/null +++ b/ui/src/gtk/virtual-display-row.ui @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/ui/src/virtualdisplaymanager.py b/ui/src/virtualdisplaymanager.py index 0b0a1e0..dd02bc1 100644 --- a/ui/src/virtualdisplaymanager.py +++ b/ui/src/virtualdisplaymanager.py @@ -45,20 +45,20 @@ class VirtualDisplayManager(GObject.GObject): if (os.waitpid(pid, os.WNOHANG) == (pid, 0)): return True except ChildProcessError: - return True + # process isn't tied to the current process, it's not dead if it's still open + return False return False def _prune_dead_display_processes(self): - self.set_property('displays', [disp for disp in self.displays if not self._process_dead(disp['pid'])]) - self._save_processes() + new_displays = [disp for disp in self.displays if not self._process_dead(disp['pid'])] + if new_displays != self.displays: + self.set_property('displays', new_displays) + self._save_processes() return GLib.SOURCE_CONTINUE - def create_virtual_display(self, width, height, framerate) -> int: - self._create_virtual_display(width, height, framerate) - - def _create_virtual_display(self, width, height, framerate): + def create_virtual_display(self, width, height, framerate): try: process = subprocess.Popen( [f"{bindir}/virtualdisplay", "--width", str(width), "--height", str(height), "--framerate", str(framerate)], diff --git a/ui/src/virtualdisplayrow.py b/ui/src/virtualdisplayrow.py new file mode 100644 index 0000000..ef4593a --- /dev/null +++ b/ui/src/virtualdisplayrow.py @@ -0,0 +1,31 @@ +from gi.repository import Adw, Gtk +from .virtualdisplaymanager import VirtualDisplayManager + +import gettext + +_ = gettext.gettext + +@Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/virtual-display-row.ui') +class VirtualDisplayRow(Adw.ActionRow): + __gtype_name__ = "VirtualDisplayRow" + + remove_virtual_display_button = Gtk.Template.Child() + + def __init__(self, pid, width, height, framerate): + super(Adw.ActionRow, self).__init__() + self.init_template() + self.pid = pid + + icon = Gtk.Image.new_from_icon_name("video-display-symbolic") + + # padding around the icon + self.add_prefix(Gtk.Label(label=" ")) + self.add_prefix(icon) + self.add_prefix(Gtk.Label(label=" ")) + + self.set_subtitle(f"{width} x {height}") + + self.remove_virtual_display_button.connect('clicked', self._remove_virtual_display) + + def _remove_virtual_display(self, widget): + VirtualDisplayManager.get_instance().destroy_virtual_display(self.pid) \ No newline at end of file