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-license.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>
</gresource>
</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 .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')

View File

@ -37,7 +37,7 @@
<property name="margin-end">20</property>
<property name="spacing">20</property>
<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>
<child>
<object class="AdwActionRow">
@ -54,28 +54,47 @@
<object class="AdwActionRow">
<property name="title" translatable="yes"><!-- adjustment slider -->Virtual monitors</property>
<property name="valign">2</property>
<property name="valign">2</property>
<child>
<object class="GtkBox">
<property name="spacing">30</property>
<property name="width-request">150</property>
<property name="margin-start">30</property>
<child>
<object class="GtkButton" id="add_virtual_display_button_1080p">
<property name="name">add-virtual-display</property>
<object class="GtkButton" id="launch_display_settings_button">
<property name="visible">0</property>
<property name="name">launch-display-settings</property>
<property name="valign">3</property>
<property name="label" translatable="yes">Add 1080p</property>
<property name="label" translatable="yes">Rearrange displays</property>
</object>
</child>
<child>
<object class="GtkButton" id="add_virtual_display_button_1440p">
<property name="name">add-virtual-display</property>
<property name="valign">3</property>
<property name="label" translatable="yes">Add 1440p</property>
<object class="GtkBox">
<property name="valign">center</property>
<style>
<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>
</child>
</object>
</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>
</child>
</object>
@ -148,6 +167,10 @@
<property name="spacing">30</property>
<property name="width-request">150</property>
<property name="margin-start">30</property>
<property name="valign">center</property>
<style>
<class name="flat"/>
</style>
<child>
<object class="GtkComboBoxText" id="monitor_wrapping_scheme_menu">
<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)):
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)],

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)