Add virtual display management to the UI

This commit is contained in:
wheaney 2025-02-20 21:30:34 -08:00
parent 854d74d983
commit 2aa2de6009
6 changed files with 142 additions and 26 deletions

View File

@ -10,6 +10,7 @@
<file preprocess="xml-stripblanks">gtk/no-extension.ui</file> <file preprocess="xml-stripblanks">gtk/no-extension.ui</file>
<file preprocess="xml-stripblanks">gtk/no-license.ui</file> <file preprocess="xml-stripblanks">gtk/no-license.ui</file>
<file preprocess="xml-stripblanks">gtk/shortcut-dialog.ui</file> <file preprocess="xml-stripblanks">gtk/shortcut-dialog.ui</file>
<file preprocess="xml-stripblanks">gtk/virtual-display-row.ui</file>
<file preprocess="xml-stripblanks">gtk/window.ui</file> <file preprocess="xml-stripblanks">gtk/window.ui</file>
</gresource> </gresource>
</gresources> </gresources>

View File

@ -1,4 +1,4 @@
from gi.repository import Gio, Gtk, GObject from gi.repository import Gio, GLib, Gtk, GObject
from .configmanager import ConfigManager from .configmanager import ConfigManager
from .extensionsmanager import ExtensionsManager from .extensionsmanager import ExtensionsManager
from .license import BREEZY_GNOME_FEATURES from .license import BREEZY_GNOME_FEATURES
@ -6,6 +6,7 @@ from .settingsmanager import SettingsManager
from .shortcutdialog import bind_shortcut_settings from .shortcutdialog import bind_shortcut_settings
from .statemanager import StateManager from .statemanager import StateManager
from .virtualdisplaymanager import VirtualDisplayManager from .virtualdisplaymanager import VirtualDisplayManager
from .virtualdisplayrow import VirtualDisplayRow
from .xrdriveripc import XRDriverIPC from .xrdriveripc import XRDriverIPC
import gettext import gettext
import logging import logging
@ -32,8 +33,10 @@ class ConnectedDevice(Gtk.Box):
# widescreen_mode_switch = Gtk.Template.Child() # widescreen_mode_switch = Gtk.Template.Child()
# widescreen_mode_row = Gtk.Template.Child() # widescreen_mode_row = Gtk.Template.Child()
# curved_display_switch = Gtk.Template.Child() # curved_display_switch = Gtk.Template.Child()
add_virtual_display_button_1080p = Gtk.Template.Child() top_features_group = Gtk.Template.Child()
add_virtual_display_button_1440p = 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_start_button = Gtk.Template.Child()
set_toggle_display_distance_end_button = Gtk.Template.Child() set_toggle_display_distance_end_button = Gtk.Template.Child()
reassign_toggle_xr_effect_shortcut_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_mode_switch,
# self.follow_threshold_scale, # self.follow_threshold_scale,
# self.curved_display_switch, # self.curved_display_switch,
self.add_virtual_display_button_1080p, self.add_virtual_display_menu,
self.add_virtual_display_button_1440p, self.add_virtual_display_button,
self.set_toggle_display_distance_start_button, self.set_toggle_display_distance_start_button,
self.set_toggle_display_distance_end_button, self.set_toggle_display_distance_end_button,
self.movement_look_ahead_scale, 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_start_button,
self.set_toggle_display_distance_end_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_menu.set_active_id('1080p')
self.add_virtual_display_button_1440p.connect('clicked', lambda *args: self.on_add_virtual_display(2560, 1440)) 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 = StateManager.get_instance()
# self.state_manager.bind_property('follow-mode', self.follow_mode_switch, 'active', GObject.BindingFlags.DEFAULT) # 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.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_enabled_features(self.state_manager, None)
self._handle_device_supports_sbs(self.state_manager, None) self._handle_device_supports_sbs(self.state_manager, None)
self._handle_enabled_config(None, None) self._handle_enabled_config(None, None)
@ -144,6 +149,16 @@ class ConnectedDevice(Gtk.Box):
self.connect("destroy", self._on_widget_destroy) 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): def _handle_monitor_wrapping_scheme_setting_changed(self, settings, val):
self.monitor_wrapping_scheme_menu.set_active_id(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)) widget.connect('clicked', lambda *args, widget=widget: on_set_display_distance_toggle(widget))
reload_display_distance_toggle_button(widget) reload_display_distance_toggle_button(widget)
def on_add_virtual_display(self, width, height): def on_add_virtual_display(self, *args):
logger.info(f"Adding virtual display {width}x{height}") 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) self.virtual_display_manager.create_virtual_display(width, height, 60)
def _on_virtual_displays_update(self, virtual_display_manager, val): 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): def _on_widget_destroy(self, widget):
# self.state_manager.unbind_property('follow-mode', self.follow_mode_switch, 'active') # self.state_manager.unbind_property('follow-mode', self.follow_mode_switch, 'active')

View File

@ -37,7 +37,7 @@
<property name="margin-end">20</property> <property name="margin-end">20</property>
<property name="spacing">20</property> <property name="spacing">20</property>
<child> <child>
<object class="AdwPreferencesGroup"> <object class="AdwPreferencesGroup" id="top_features_group">
<property name="title" translatable="yes"><!-- section heading for switches that enable certain features -->Features</property> <property name="title" translatable="yes"><!-- section heading for switches that enable certain features -->Features</property>
<child> <child>
<object class="AdwActionRow"> <object class="AdwActionRow">
@ -54,28 +54,47 @@
<object class="AdwActionRow"> <object class="AdwActionRow">
<property name="title" translatable="yes"><!-- adjustment slider -->Virtual monitors</property> <property name="title" translatable="yes"><!-- adjustment slider -->Virtual monitors</property>
<property name="valign">2</property> <property name="valign">2</property>
<property name="valign">2</property>
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="spacing">30</property> <property name="spacing">30</property>
<property name="width-request">150</property> <property name="width-request">150</property>
<property name="margin-start">30</property> <property name="margin-start">30</property>
<child> <child>
<object class="GtkButton" id="add_virtual_display_button_1080p"> <object class="GtkButton" id="launch_display_settings_button">
<property name="name">add-virtual-display</property> <property name="visible">0</property>
<property name="name">launch-display-settings</property>
<property name="valign">3</property> <property name="valign">3</property>
<property name="label" translatable="yes">Add 1080p</property> <property name="label" translatable="yes">Rearrange displays</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="add_virtual_display_button_1440p"> <object class="GtkBox">
<property name="name">add-virtual-display</property> <property name="valign">center</property>
<property name="valign">3</property> <style>
<property name="label" translatable="yes">Add 1440p</property> <class name="flat"/>
</style>
<child>
<object class="GtkComboBoxText" id="add_virtual_display_menu">
<items>
<item translatable="yes" id="1080p">1080p</item>
<item translatable="yes" id="1440p">1440p</item>
</items>
</object>
</child>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
<child type="suffix">
<object class="GtkButton" id="add_virtual_display_button">
<property name="name">add-virtual-display</property>
<property name="icon-name">list-add-symbolic</property>
<property name="valign">center</property>
<style>
<class name="flat"/>
</style>
</object>
</child>
</object> </object>
</child> </child>
</object> </object>
@ -148,6 +167,10 @@
<property name="spacing">30</property> <property name="spacing">30</property>
<property name="width-request">150</property> <property name="width-request">150</property>
<property name="margin-start">30</property> <property name="margin-start">30</property>
<property name="valign">center</property>
<style>
<class name="flat"/>
</style>
<child> <child>
<object class="GtkComboBoxText" id="monitor_wrapping_scheme_menu"> <object class="GtkComboBoxText" id="monitor_wrapping_scheme_menu">
<items> <items>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<template class="VirtualDisplayRow" parent="AdwActionRow">
<property name="subtitle"></property>
<child type="suffix">
<object class="GtkButton" id="remove_virtual_display_button">
<property name="name">remove-virtual-display</property>
<property name="icon-name">user-trash-symbolic</property>
<property name="valign">center</property>
<style>
<class name="flat"/>
</style>
</object>
</child>
</template>
</interface>

View File

@ -45,20 +45,20 @@ class VirtualDisplayManager(GObject.GObject):
if (os.waitpid(pid, os.WNOHANG) == (pid, 0)): if (os.waitpid(pid, os.WNOHANG) == (pid, 0)):
return True return True
except ChildProcessError: 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 return False
def _prune_dead_display_processes(self): def _prune_dead_display_processes(self):
self.set_property('displays', [disp for disp in self.displays if not self._process_dead(disp['pid'])]) new_displays = [disp for disp in self.displays if not self._process_dead(disp['pid'])]
self._save_processes() if new_displays != self.displays:
self.set_property('displays', new_displays)
self._save_processes()
return GLib.SOURCE_CONTINUE return GLib.SOURCE_CONTINUE
def create_virtual_display(self, width, height, framerate) -> int: def create_virtual_display(self, width, height, framerate):
self._create_virtual_display(width, height, framerate)
def _create_virtual_display(self, width, height, framerate):
try: try:
process = subprocess.Popen( process = subprocess.Popen(
[f"{bindir}/virtualdisplay", "--width", str(width), "--height", str(height), "--framerate", str(framerate)], [f"{bindir}/virtualdisplay", "--width", str(width), "--height", str(height), "--framerate", str(framerate)],

View File

@ -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)