diff --git a/ui/data/com.xronlinux.BreezyDesktop.gschema.xml b/ui/data/com.xronlinux.BreezyDesktop.gschema.xml index 6704f6e..7a9fc9a 100644 --- a/ui/data/com.xronlinux.BreezyDesktop.gschema.xml +++ b/ui/data/com.xronlinux.BreezyDesktop.gschema.xml @@ -100,6 +100,15 @@ The size of the display + + + "cm" + + Measurement units + + Units to display for physical measurements: "cm" or "in" + + 0.0 diff --git a/ui/modules/PyXRLinuxDriverIPC b/ui/modules/PyXRLinuxDriverIPC index da173bd..1655c2b 160000 --- a/ui/modules/PyXRLinuxDriverIPC +++ b/ui/modules/PyXRLinuxDriverIPC @@ -1 +1 @@ -Subproject commit da173bd9e0392aaeb2cb68a332e5d4a20dd4dae1 +Subproject commit 1655c2bb03a4e75bf2c9a9815a96b71342396fbd diff --git a/ui/src/connecteddevice.py b/ui/src/connecteddevice.py index ee1442c..271cf76 100644 --- a/ui/src/connecteddevice.py +++ b/ui/src/connecteddevice.py @@ -33,6 +33,8 @@ class ConnectedDevice(Gtk.Box): effect_enable_switch = Gtk.Template.Child() disable_physical_displays_switch = Gtk.Template.Child() display_zoom_on_focus_switch = Gtk.Template.Child() + display_size_scale = Gtk.Template.Child() + display_size_adjustment = Gtk.Template.Child() follow_threshold_scale = Gtk.Template.Child() follow_threshold_adjustment = Gtk.Template.Child() follow_mode_switch = Gtk.Template.Child() @@ -83,6 +85,7 @@ class ConnectedDevice(Gtk.Box): viewport_offset_x_adjustment = Gtk.Template.Child() viewport_offset_y_scale = Gtk.Template.Child() viewport_offset_y_adjustment = Gtk.Template.Child() + units_menu = Gtk.Template.Child() def __init__(self): super(Gtk.Box, self).__init__() @@ -90,7 +93,7 @@ class ConnectedDevice(Gtk.Box): self.active = True self.all_enabled_state_inputs = [ self.display_zoom_on_focus_switch, - # self.display_size_scale, + self.display_size_scale, self.follow_mode_switch, self.follow_threshold_scale, self.curved_display_switch, @@ -115,7 +118,7 @@ class ConnectedDevice(Gtk.Box): self.settings.bind('disable-physical-displays', self.disable_physical_displays_switch, 'active', Gio.SettingsBindFlags.DEFAULT) self.settings.connect('changed::display-distance', self._handle_display_distance) - # self.settings.bind('display-size', self.display_size_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT) + self.settings.bind('display-size', self.display_size_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT) self.settings.bind('follow-threshold', self.follow_threshold_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT) # self.settings.bind('widescreen-mode', self.widescreen_mode_switch, 'active', Gio.SettingsBindFlags.DEFAULT) self.settings.bind('curved-display', self.curved_display_switch, 'active', Gio.SettingsBindFlags.DEFAULT) @@ -136,6 +139,10 @@ class ConnectedDevice(Gtk.Box): self.monitor_wrapping_scheme_menu.connect('changed', self._handle_monitor_wrapping_scheme_menu_changed) self._handle_monitor_wrapping_scheme_setting_changed(self.settings, self.settings.get_string('monitor-wrapping-scheme')) + current_units = self.settings.get_string('units') + self.units_menu.set_active_id(current_units) + self.units_menu.connect('changed', self._handle_units_menu_changed) + bind_shortcut_settings(self.get_parent(), [ [self.reassign_toggle_xr_effect_shortcut_button, self.toggle_xr_effect_shortcut_label], [self.reassign_recenter_display_shortcut_button, self.recenter_display_shortcut_label], @@ -176,6 +183,11 @@ class ConnectedDevice(Gtk.Box): self.follow_mode_switch.connect('notify::active', self._refresh_follow_mode) self.effect_enable_switch.connect('notify::active', self._handle_switch_enabled_state) + self.display_size_scale.set_format_value_func(lambda scale, val: self._format_size(val)) + self.state_manager.connect('notify::connected-device-full-size-cm', self._handle_metric_change) + self.state_manager.connect('notify::connected-device-full-distance-cm', self._handle_metric_change) + self.settings.connect('changed::units', self._handle_units_changed) + self.config_manager = ConfigManager.get_instance() self.config_manager.connect('notify::breezy-desktop-enabled', self._handle_enabled_config) self._bind_switch_to_config(self.enable_multi_tap_switch, 'multi-tap-enabled') @@ -238,6 +250,27 @@ class ConnectedDevice(Gtk.Box): elif not widget.get_active() and is_zoom_on_focus_already_enabled: self.settings.set_double('display-distance', toggle_display_distance_end) + def _handle_units_menu_changed(self, widget): + active_id = widget.get_active_id() or 'cm' + self.settings.set_string('units', active_id) + + def _handle_units_changed(self, *args): + self._refresh_display_size_scale_value() + self._set_all_displays_distance(self.settings.get_double('toggle-display-distance-end')) + self._set_focused_display_distance(self.settings.get_double('toggle-display-distance-start')) + + def _handle_metric_change(self, *args): + self._refresh_display_size_scale_value() + self._set_all_displays_distance(self.settings.get_double('toggle-display-distance-end')) + self._set_focused_display_distance(self.settings.get_double('toggle-display-distance-start')) + + def _refresh_display_size_scale_value(self): + if self.display_size_scale.get_draw_value(): + self.display_size_scale.set_draw_value(False) + self.display_size_scale.set_draw_value(True) + else: + self.display_size_scale.queue_draw() + def _handle_monitor_wrapping_scheme_setting_changed(self, settings, val): self.monitor_wrapping_scheme_menu.set_active_id(val) @@ -314,17 +347,45 @@ class ConnectedDevice(Gtk.Box): self.display_zoom_on_focus_switch.set_active(should_zoom_on_focus_be_enabled) def _set_focused_display_distance(self, distance): - self.focused_display_distance_label.set_markup(f"{_('Focused display')}: {distance}") + self.focused_display_distance_label.set_markup(f"{_('Focused display')}: {self._format_distance(distance)}") self.settings.set_double('toggle-display-distance-start', distance) self.display_zoom_on_focus_switch.set_sensitive(distance != self.settings.get_double('toggle-display-distance-end')) def _set_all_displays_distance(self, distance): - self.all_displays_distance_label.set_markup(f"{_('All displays')}: {distance}") + self.all_displays_distance_label.set_markup(f"{_('All displays')}: {self._format_distance(distance)}") self.settings.set_double('toggle-display-distance-end', distance) self.display_zoom_on_focus_switch.set_active(False) self.display_zoom_on_focus_switch.set_sensitive(distance != self.settings.get_double('toggle-display-distance-start')) + def _get_units(self): + units = self.settings.get_string('units') + return units if units in ['cm', 'in'] else 'cm' + + def _format_distance(self, normalized): + sm = getattr(self, 'state_manager', None) or StateManager.get_instance() + full_cm = float(sm.get_property('connected-device-full-distance-cm') or 0.0) + if full_cm <= 0: + # Fallback to normalized display if metric unknown + return f"{round(normalized, 2)}" + cm = normalized * full_cm + if self._get_units() == 'in': + inches = cm / 2.54 + return f"{inches:.2f} in" + return f"{cm:.1f} cm" + + def _format_size(self, normalized): + sm = getattr(self, 'state_manager', None) or StateManager.get_instance() + full_cm = float(sm.get_property('connected-device-full-size-cm') or 0.0) + if full_cm <= 0: + # Fallback to normalized display if metric unknown + return f"{round(normalized, 2)}" + cm = normalized * full_cm + if self._get_units() == 'in': + inches = cm / 2.54 + return f"{inches:.2f} in" + return f"{cm:.1f} cm" + def _on_display_distance_preset_change_button_clicked(self, widget, settings_key, on_save_callback, title, subtitle, lower_limit, upper_limit): dialog = DisplayDistanceDialog(settings_key, on_save_callback, title, subtitle, lower_limit, upper_limit) dialog.set_transient_for(widget.get_ancestor(Gtk.Window)) diff --git a/ui/src/displaydistancedialogcontent.py b/ui/src/displaydistancedialogcontent.py index 3b81e72..22555f9 100644 --- a/ui/src/displaydistancedialogcontent.py +++ b/ui/src/displaydistancedialogcontent.py @@ -1,5 +1,6 @@ from gi.repository import Gtk, Gio from .settingsmanager import SettingsManager +from .statemanager import StateManager import gettext @@ -22,6 +23,7 @@ class DisplayDistanceDialogContent(Gtk.Box): self.on_save_callback = on_save_callback self.settings = SettingsManager.get_instance().settings + self.state_manager = StateManager.get_instance() self.prev_distance = self.settings.get_double('display-distance') self.lower_limit_orig = self.display_distance_adjustment.get_lower() @@ -31,6 +33,10 @@ class DisplayDistanceDialogContent(Gtk.Box): self.settings.bind('display-distance', self.display_distance_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT) + self.display_distance_scale.set_format_value_func(lambda scale, val: self._format_distance(val)) + self.state_manager.connect('notify::connected-device-full-distance-cm', lambda *args: self.display_distance_scale.queue_draw()) + self.settings.connect('changed::units', lambda *args: self.display_distance_scale.queue_draw()) + show_full_scale_button.connect('clicked', self._on_show_full_scale_button_clicked) save_button.connect('clicked', self._on_save_button_clicked) @@ -55,4 +61,18 @@ class DisplayDistanceDialogContent(Gtk.Box): self.display_distance_adjustment.set_upper(self.upper_limit_orig) def _on_save_button_clicked(self, button): - self.on_save_callback(self.prev_distance, self.display_distance_adjustment.get_value()) \ No newline at end of file + self.on_save_callback(self.prev_distance, self.display_distance_adjustment.get_value()) + + def _get_units(self): + units = self.settings.get_string('units') + return units if units in ['cm', 'in'] else 'cm' + + def _format_distance(self, normalized): + full_cm = float(self.state_manager.get_property('connected-device-full-distance-cm') or 0.0) + if full_cm <= 0: + return f"{round(normalized, 2)}" + cm = normalized * full_cm + if self._get_units() == 'in': + inches = cm / 2.54 + return f"{inches:.2f} in" + return f"{cm:.1f} cm" \ No newline at end of file diff --git a/ui/src/gtk/connected-device.ui b/ui/src/gtk/connected-device.ui index daec778..8b25582 100644 --- a/ui/src/gtk/connected-device.ui +++ b/ui/src/gtk/connected-device.ui @@ -229,6 +229,36 @@ + + + Display size + Set how large you want the display to appear. + + + 3 + true + 0 + 2 + 350 + false + + + 0.1 + 2.5 + 0.01 + 1.0 + + + + 0.5× + full + 1.5× + 2.0× + + + + + Follow threshold @@ -554,6 +584,35 @@ Advanced Settings 450 + + + Units + Choose measurement units for size and distance displays. + + + 30 + 150 + 30 + + + 3 + + + + + Centimeters + Inches + + + + + + + + + Find optimal display config diff --git a/ui/src/gtk/display-distance-dialog-content.ui b/ui/src/gtk/display-distance-dialog-content.ui index 704b9c4..0160d84 100644 --- a/ui/src/gtk/display-distance-dialog-content.ui +++ b/ui/src/gtk/display-distance-dialog-content.ui @@ -25,7 +25,7 @@ false - 0.2 + 0.1 2.5 0.01 1.05 diff --git a/ui/src/statemanager.py b/ui/src/statemanager.py index efb03c6..d7dc688 100644 --- a/ui/src/statemanager.py +++ b/ui/src/statemanager.py @@ -21,6 +21,8 @@ class StateManager(GObject.GObject): 'license-present': (bool, 'License Present', 'Whether a license is present', False, GObject.ParamFlags.READWRITE), 'enabled-features-list': (object, 'Enabled Features List', 'A list of the enabled features', GObject.ParamFlags.READWRITE), 'device-supports-sbs': (bool, 'Device Supports SBS', 'Whether the connected device supports SBS', False, GObject.ParamFlags.READWRITE), + 'connected-device-full-distance-cm': (float, 'Full Distance (cm)', 'Device full distance in cm', 0.0, 10000.0, 0.0, GObject.ParamFlags.READWRITE), + 'connected-device-full-size-cm': (float, 'Full Size (cm)', 'Device full display size in cm', 0.0, 10000.0, 0.0, GObject.ParamFlags.READWRITE), } _instance = None @@ -59,6 +61,8 @@ class StateManager(GObject.GObject): self.license_present = False self.enabled_features = [] self.device_supports_sbs = False + self.connected_device_full_distance_cm = 0.0 + self.connected_device_full_size_cm = 0.0 self._running = True self._refresh_state() @@ -98,6 +102,14 @@ class StateManager(GObject.GObject): self.set_property('device-supports-sbs', self.state.get('sbs_mode_supported', False)) self.set_property('widescreen-mode', self.state.get('sbs_mode_enabled', False)) + full_distance = self.state.get('connected_device_full_distance_cm') or 0.0 + if full_distance != self.connected_device_full_distance_cm: + self.set_property('connected-device-full-distance-cm', full_distance) + + full_size = self.state.get('connected_device_full_size_cm') or 0.0 + if full_size != self.connected_device_full_size_cm: + self.set_property('connected-device-full-size-cm', full_size) + if self._running: threading.Timer(1.0, self._refresh_state).start() def do_set_property(self, prop, value): @@ -115,6 +127,10 @@ class StateManager(GObject.GObject): self.enabled_features = value if prop.name == 'device-supports-sbs': self.device_supports_sbs = value + if prop.name == 'connected-device-full-distance-cm': + self.connected_device_full_distance_cm = value + if prop.name == 'connected-device-full-size-cm': + self.connected_device_full_size_cm = value def do_get_property(self, prop): if prop.name == 'driver-running': @@ -130,4 +146,8 @@ class StateManager(GObject.GObject): if prop.name == 'enabled-features-list': return self.enabled_features if prop.name == 'device-supports-sbs': - return self.device_supports_sbs \ No newline at end of file + return self.device_supports_sbs + if prop.name == 'connected-device-full-distance-cm': + return self.connected_device_full_distance_cm + if prop.name == 'connected-device-full-size-cm': + return self.connected_device_full_size_cm \ No newline at end of file