500 lines
27 KiB
Python
500 lines
27 KiB
Python
from gi.repository import Gio, GLib, Gtk, GObject
|
|
from .configmanager import ConfigManager
|
|
from .customresolutiondialog import CustomResolutionDialog
|
|
from .displaydistancedialog import DisplayDistanceDialog
|
|
from .extensionsmanager import ExtensionsManager
|
|
from .files import get_state_dir
|
|
from .license import BREEZY_GNOME_FEATURES
|
|
from .settingsmanager import SettingsManager
|
|
from .shortcutdialog import bind_shortcut_settings
|
|
from .statemanager import StateManager
|
|
from .virtualdisplaymanager import VirtualDisplayManager
|
|
from .virtualdisplay import is_screencast_available
|
|
from .virtualdisplayrow import VirtualDisplayRow
|
|
from .xrdriveripc import XRDriverIPC
|
|
|
|
import gettext
|
|
import json
|
|
import logging
|
|
import os
|
|
from pathlib import Path
|
|
|
|
_ = gettext.gettext
|
|
logger = logging.getLogger('breezy_ui')
|
|
|
|
@Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/connected-device.ui')
|
|
class ConnectedDevice(Gtk.Box):
|
|
__gtype_name__ = "ConnectedDevice"
|
|
|
|
widescreen_mode_subtitle = _("Switches your glasses into side-by-side mode and doubles the width of the display.")
|
|
widescreen_mode_not_supported_subtitle = _("This feature is not currently supported for your device.")
|
|
|
|
device_label = Gtk.Template.Child()
|
|
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()
|
|
curved_display_switch = Gtk.Template.Child()
|
|
horizon_lock_switch = Gtk.Template.Child()
|
|
top_features_group = Gtk.Template.Child()
|
|
virtual_displays_row = Gtk.Template.Child()
|
|
add_virtual_display_menu = Gtk.Template.Child()
|
|
add_virtual_display_button = Gtk.Template.Child()
|
|
remove_custom_resolution_button = Gtk.Template.Child()
|
|
launch_display_settings_row = Gtk.Template.Child()
|
|
launch_display_settings_button = Gtk.Template.Child()
|
|
all_displays_distance_label = Gtk.Template.Child()
|
|
change_all_displays_distance_button = Gtk.Template.Child()
|
|
focused_display_distance_label = Gtk.Template.Child()
|
|
change_focused_display_distance_button = Gtk.Template.Child()
|
|
reassign_toggle_xr_effect_shortcut_button = Gtk.Template.Child()
|
|
toggle_xr_effect_shortcut_label = Gtk.Template.Child()
|
|
reassign_recenter_display_shortcut_button = Gtk.Template.Child()
|
|
recenter_display_shortcut_label = Gtk.Template.Child()
|
|
reassign_toggle_display_distance_shortcut_button = Gtk.Template.Child()
|
|
toggle_display_distance_shortcut_label = Gtk.Template.Child()
|
|
reassign_toggle_follow_shortcut_button = Gtk.Template.Child()
|
|
toggle_follow_shortcut_label = Gtk.Template.Child()
|
|
reassign_cursor_to_focused_display_shortcut_button = Gtk.Template.Child()
|
|
cursor_to_focused_display_shortcut_label = Gtk.Template.Child()
|
|
headset_display_as_viewport_center_switch = Gtk.Template.Child()
|
|
headset_as_primary_switch = Gtk.Template.Child()
|
|
remove_virtual_displays_on_disable_switch = Gtk.Template.Child()
|
|
use_optimal_monitor_config_switch = Gtk.Template.Child()
|
|
use_highest_refresh_rate_switch = Gtk.Template.Child()
|
|
movement_look_ahead_scale = Gtk.Template.Child()
|
|
movement_look_ahead_adjustment = Gtk.Template.Child()
|
|
text_scaling_scale = Gtk.Template.Child()
|
|
text_scaling_adjustment = Gtk.Template.Child()
|
|
neck_saver_horizontal_scale = Gtk.Template.Child()
|
|
neck_saver_horizontal_adjustment = Gtk.Template.Child()
|
|
neck_saver_vertical_scale = Gtk.Template.Child()
|
|
neck_saver_vertical_adjustment = Gtk.Template.Child()
|
|
dead_zone_threshold_scale = Gtk.Template.Child()
|
|
dead_zone_threshold_adjustment = Gtk.Template.Child()
|
|
enable_multi_tap_switch = Gtk.Template.Child()
|
|
legacy_follow_mode_switch = Gtk.Template.Child()
|
|
follow_track_yaw_switch = Gtk.Template.Child()
|
|
follow_track_pitch_switch = Gtk.Template.Child()
|
|
follow_track_roll_switch = Gtk.Template.Child()
|
|
monitor_wrapping_scheme_menu = Gtk.Template.Child()
|
|
monitor_spacing_scale = Gtk.Template.Child()
|
|
monitor_spacing_adjustment = Gtk.Template.Child()
|
|
viewport_offset_x_scale = Gtk.Template.Child()
|
|
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__()
|
|
self.init_template()
|
|
self.active = True
|
|
self.all_enabled_state_inputs = [
|
|
self.display_zoom_on_focus_switch,
|
|
self.display_size_scale,
|
|
self.follow_mode_switch,
|
|
self.follow_threshold_scale,
|
|
self.curved_display_switch,
|
|
self.horizon_lock_switch,
|
|
self.add_virtual_display_menu,
|
|
self.add_virtual_display_button,
|
|
self.change_all_displays_distance_button,
|
|
self.change_focused_display_distance_button,
|
|
self.movement_look_ahead_scale,
|
|
self.monitor_wrapping_scheme_menu,
|
|
self.monitor_spacing_scale,
|
|
self.viewport_offset_x_scale,
|
|
self.viewport_offset_y_scale,
|
|
self.neck_saver_horizontal_scale,
|
|
self.neck_saver_vertical_scale
|
|
]
|
|
|
|
self.settings = SettingsManager.get_instance().settings
|
|
self.desktop_settings = SettingsManager.get_instance().desktop_settings
|
|
self.ipc = XRDriverIPC.get_instance()
|
|
self.virtual_display_manager = VirtualDisplayManager.get_instance()
|
|
self.extensions_manager = ExtensionsManager.get_instance()
|
|
|
|
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('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)
|
|
self.settings.bind('horizon-lock', self.horizon_lock_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
|
|
self.settings.bind('headset-display-as-viewport-center', self.headset_display_as_viewport_center_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
|
|
self.settings.bind('headset-as-primary', self.headset_as_primary_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
|
|
self.settings.bind('remove-virtual-displays-on-disable', self.remove_virtual_displays_on_disable_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
|
|
self.settings.bind('use-optimal-monitor-config', self.use_optimal_monitor_config_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
|
|
self.settings.bind('use-highest-refresh-rate', self.use_highest_refresh_rate_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
|
|
# self.settings.bind('fast-sbs-mode-switching', self.fast_sbs_mode_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
|
|
self.settings.bind('look-ahead-override', self.movement_look_ahead_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT)
|
|
self.settings.bind('legacy-follow-mode', self.legacy_follow_mode_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
|
|
self.settings.bind('monitor-spacing', self.monitor_spacing_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT)
|
|
self.settings.bind('viewport-offset-x', self.viewport_offset_x_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT)
|
|
self.settings.bind('viewport-offset-y', self.viewport_offset_y_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT)
|
|
self.settings.connect('changed::monitor-wrapping-scheme', self._handle_monitor_wrapping_scheme_setting_changed)
|
|
self.desktop_settings.bind('text-scaling-factor', self.text_scaling_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT)
|
|
self.display_zoom_on_focus_switch.connect('notify::active', self._handle_zoom_on_focus_switch_changed)
|
|
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],
|
|
[self.reassign_toggle_display_distance_shortcut_button, self.toggle_display_distance_shortcut_label],
|
|
[self.reassign_toggle_follow_shortcut_button, self.toggle_follow_shortcut_label],
|
|
[self.reassign_cursor_to_focused_display_shortcut_button, self.cursor_to_focused_display_shortcut_label]
|
|
])
|
|
|
|
self.change_focused_display_distance_button.connect('clicked',
|
|
self._on_display_distance_preset_change_button_clicked,
|
|
'toggle-display-distance-start',
|
|
self._on_set_focused_display_distance,
|
|
_('Set Focused Display Distance'),
|
|
_('Use a closer value so the display zooms in when you look at it.'),
|
|
0.2, 1.0
|
|
)
|
|
self.change_all_displays_distance_button.connect('clicked',
|
|
self._on_display_distance_preset_change_button_clicked,
|
|
'toggle-display-distance-end',
|
|
self._on_set_all_displays_distance,
|
|
_('Set All Displays Distance'),
|
|
_('Use a farther value so the displays are zoomed out when you look away.'),
|
|
1.0, 2.5
|
|
)
|
|
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'))
|
|
|
|
self.add_virtual_display_menu.set_active_id('create_1080p_display')
|
|
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)
|
|
self.state_manager.connect('notify::enabled-features-list', self._handle_enabled_features)
|
|
self.state_manager.connect('notify::device-supports-sbs', self._handle_device_supports_sbs)
|
|
|
|
self.follow_mode_switch.set_active(self.state_manager.get_property('follow-mode'))
|
|
self.follow_mode_switch.connect('notify::active', self._refresh_follow_mode)
|
|
self.effect_enable_switch.connect('notify::active', self._handle_switch_enabled_state)
|
|
|
|
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')
|
|
self._bind_switch_to_config(self.follow_track_roll_switch, 'follow-track-roll')
|
|
self._bind_switch_to_config(self.follow_track_pitch_switch, 'follow-track-pitch')
|
|
self._bind_switch_to_config(self.follow_track_yaw_switch, 'follow-track-yaw')
|
|
self._bind_scale_to_config(self.dead_zone_threshold_adjustment, 'dead-zone-threshold-deg')
|
|
self._bind_scale_to_config(self.neck_saver_horizontal_adjustment, 'neck-saver-horizontal-multiplier')
|
|
self._bind_scale_to_config(self.neck_saver_vertical_adjustment, 'neck-saver-vertical-multiplier')
|
|
|
|
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_display_distance(self.settings, self.settings.get_double('display-distance'))
|
|
self._handle_enabled_features(self.state_manager, None)
|
|
self._handle_device_supports_sbs(self.state_manager, None)
|
|
self._handle_enabled_config(None, None)
|
|
self._refresh_use_optimal_monitor_config(self.use_optimal_monitor_config_switch, None)
|
|
self.extensions_manager.connect('notify::breezy-enabled', self._handle_enabled_config)
|
|
|
|
self._settings_displays_app_info = None
|
|
|
|
for appinfo in Gio.AppInfo.get_all():
|
|
if appinfo.get_id() == 'gnome-display-panel.desktop':
|
|
self._settings_displays_app_info = appinfo
|
|
break
|
|
|
|
self.virtual_display_manager.connect('notify::displays', self._on_virtual_displays_update)
|
|
self.add_virtual_display_menu.connect('changed', self._on_add_virtual_display_menu_changed)
|
|
self.remove_custom_resolution_button.connect('clicked', self._on_custom_resolution_option_remove)
|
|
self._on_virtual_displays_update(self.virtual_display_manager, None)
|
|
self.virtual_displays_by_pid = {}
|
|
|
|
self._default_resolution_options_count = 2
|
|
self._custom_resolution_options = []
|
|
self._custom_resolutions_file_path = Path(os.path.join(get_state_dir(), 'custom_resolutions.json'))
|
|
self._load_custom_resolutions()
|
|
for id in self._custom_resolution_options:
|
|
self.add_virtual_display_menu.insert(self._default_resolution_options_count, id, id)
|
|
|
|
# wayland is required to create virtual displays
|
|
self.is_wayland = "WAYLAND_DISPLAY" in os.environ
|
|
|
|
def _bind_scale_to_config(self, scale, config_key):
|
|
self.config_manager.bind_property(config_key, scale, 'value', Gio.SettingsBindFlags.DEFAULT)
|
|
scale.set_value(self.config_manager.get_property(config_key))
|
|
scale.connect('value-changed', lambda widget: self.config_manager.set_property(config_key, widget.get_value()))
|
|
|
|
def _bind_switch_to_config(self, switch, config_key):
|
|
self.config_manager.bind_property(config_key, switch, 'active', Gio.SettingsBindFlags.DEFAULT)
|
|
switch.set_active(self.config_manager.get_property(config_key))
|
|
switch.connect('notify::active', lambda widget, param: self.config_manager.set_property(config_key, widget.get_active()))
|
|
|
|
def _handle_zoom_on_focus_switch_changed(self, widget, param):
|
|
display_distance = self.settings.get_double('display-distance')
|
|
toggle_display_distance_end = self.settings.get_double('toggle-display-distance-end')
|
|
toggle_display_distance_start = self.settings.get_double('toggle-display-distance-start')
|
|
is_zoom_on_focus_already_enabled = display_distance < toggle_display_distance_end
|
|
if widget.get_active() and not is_zoom_on_focus_already_enabled:
|
|
self.settings.set_double('display-distance', toggle_display_distance_start)
|
|
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._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._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_monitor_wrapping_scheme_setting_changed(self, settings, val):
|
|
self.monitor_wrapping_scheme_menu.set_active_id(val)
|
|
|
|
def _handle_monitor_wrapping_scheme_menu_changed(self, widget):
|
|
self.settings.set_string('monitor-wrapping-scheme', widget.get_active_id())
|
|
|
|
def _handle_enabled_features(self, state_manager, val):
|
|
enabled_breezy_features = [feature for feature in state_manager.get_property('enabled-features-list') if feature in BREEZY_GNOME_FEATURES]
|
|
breezy_features_granted = len(enabled_breezy_features) > 0
|
|
if not breezy_features_granted:
|
|
self.effect_enable_switch.set_active(False)
|
|
self.effect_enable_switch.set_sensitive(breezy_features_granted)
|
|
|
|
def _handle_device_supports_sbs(self, state_manager, val):
|
|
if not state_manager.get_property('device-supports-sbs'):
|
|
self.settings.set_boolean('widescreen-mode', False)
|
|
# self.widescreen_mode_switch.set_sensitive(state_manager.get_property('device-supports-sbs'))
|
|
# subtitle = self.widescreen_mode_subtitle if state_manager.get_property('device-supports-sbs') else self.widescreen_mode_not_supported_subtitle
|
|
# self.widescreen_mode_row.set_subtitle(subtitle)
|
|
|
|
def _handle_enabled_config(self, object, val):
|
|
enabled = self.config_manager.get_property('breezy-desktop-enabled') and self.extensions_manager.get_property('breezy-enabled')
|
|
if enabled != self.effect_enable_switch.get_active():
|
|
self.effect_enable_switch.set_active(enabled)
|
|
|
|
def _handle_switch_enabled_state(self, switch, param):
|
|
GLib.idle_add(self._handle_switch_enabled_state_gui, switch, param)
|
|
|
|
def _handle_switch_enabled_state_gui(self, switch, param):
|
|
requesting_enabled = switch.get_active()
|
|
|
|
# never turn off the extension, disabling the effect is done via configs only
|
|
if requesting_enabled:
|
|
self.extensions_manager.set_property('breezy-enabled', True)
|
|
|
|
self.config_manager.set_property('breezy-desktop-enabled', requesting_enabled)
|
|
|
|
for widget in self.all_enabled_state_inputs:
|
|
widget.set_sensitive(requesting_enabled)
|
|
|
|
if not is_screencast_available() or not self.is_wayland:
|
|
self.virtual_displays_row.set_subtitle(
|
|
_("Unable to add virtual displays on this machine. Wayland, xdg-desktop-portal, and the pipewire GStreamer plugin are required."))
|
|
self.add_virtual_display_button.set_sensitive(False)
|
|
self.add_virtual_display_menu.set_sensitive(False)
|
|
|
|
if requesting_enabled:
|
|
self._refresh_follow_mode(self.follow_mode_switch, None)
|
|
|
|
def _refresh_follow_mode(self, switch, param):
|
|
if (self.state_manager.get_property('follow-mode') == switch.get_active()):
|
|
return
|
|
|
|
self.ipc.write_control_flags({
|
|
'enable_breezy_desktop_smooth_follow': switch.get_active()
|
|
})
|
|
|
|
def _refresh_use_optimal_monitor_config(self, switch, param):
|
|
self.headset_as_primary_switch.set_sensitive(switch.get_active())
|
|
self.use_highest_refresh_rate_switch.set_sensitive(switch.get_active())
|
|
if not switch.get_active():
|
|
self.headset_as_primary_switch.set_active(False)
|
|
self.use_highest_refresh_rate_switch.set_active(False)
|
|
|
|
def set_device_name(self, name):
|
|
self.device_label.set_markup(f"<b>{name}</b>")
|
|
|
|
def _handle_display_distance(self, *args):
|
|
display_distance = self.settings.get_double('display-distance')
|
|
toggle_display_distance_end = self.settings.get_double('toggle-display-distance-end')
|
|
|
|
should_zoom_on_focus_be_enabled = display_distance < toggle_display_distance_end
|
|
if self.display_zoom_on_focus_switch.get_active() != should_zoom_on_focus_be_enabled:
|
|
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')}: <b>{self._format_distance(distance)}</b>")
|
|
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')}: <b>{self._format_distance(distance)}</b>")
|
|
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 _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))
|
|
dialog.present()
|
|
|
|
def _on_set_all_displays_distance(self, prev_distance, distance):
|
|
focused_display_distance = self.settings.get_double('toggle-display-distance-start')
|
|
if (distance < focused_display_distance):
|
|
self._set_focused_display_distance(distance)
|
|
|
|
all_displays_distance = self.settings.get_double('toggle-display-distance-end')
|
|
self._set_all_displays_distance(distance)
|
|
|
|
# if we were at the unfocused distance, put us at the new unfocused distance
|
|
if prev_distance == all_displays_distance:
|
|
self.settings.set_double('display-distance', distance)
|
|
|
|
def _on_set_focused_display_distance(self, prev_distance, distance):
|
|
all_displays_distance = self.settings.get_double('toggle-display-distance-end')
|
|
if (distance > all_displays_distance):
|
|
self._set_all_displays_distance(distance)
|
|
|
|
focused_display_distance = self.settings.get_double('toggle-display-distance-start')
|
|
self._set_focused_display_distance(distance)
|
|
|
|
# if we were at the focused distance, put us at the new focused distance
|
|
if prev_distance == focused_display_distance:
|
|
self.settings.set_double('display-distance', distance)
|
|
|
|
def _save_custom_resolutions(self):
|
|
with open(self._custom_resolutions_file_path, 'w') as f:
|
|
json.dump(self._custom_resolution_options, f)
|
|
|
|
def _load_custom_resolutions(self):
|
|
if self._custom_resolutions_file_path.exists():
|
|
try:
|
|
with open(self._custom_resolutions_file_path, 'r') as f:
|
|
self._custom_resolution_options = json.load(f)
|
|
except Exception:
|
|
self._custom_resolution_options = []
|
|
|
|
def _on_add_virtual_display(self, *args):
|
|
resolution = self.add_virtual_display_menu.get_active_id()
|
|
|
|
if resolution == 'create_1080p_display':
|
|
width = 1920
|
|
height = 1080
|
|
elif resolution == 'create_1440p_display':
|
|
width = 2560
|
|
height = 1440
|
|
else:
|
|
width, height = resolution.split('x')
|
|
width = int(width)
|
|
height = int(height)
|
|
|
|
logger.info(f"Adding virtual display {resolution}")
|
|
self.virtual_display_manager.create_virtual_display(width, height, 60)
|
|
|
|
def _on_custom_resolution_dialog_add(self, width, height):
|
|
width = int(round(width))
|
|
height = int(round(height))
|
|
|
|
id = f"{width}x{height}"
|
|
self._custom_resolution_options.append(id)
|
|
self._save_custom_resolutions()
|
|
|
|
self.add_virtual_display_menu.insert(self._default_resolution_options_count, id, id)
|
|
self.add_virtual_display_menu.set_active_id(id)
|
|
self._on_add_virtual_display_menu_changed(self.add_virtual_display_menu)
|
|
|
|
def _on_add_virtual_display_menu_changed(self, widget):
|
|
resolution = widget.get_active_id()
|
|
self.remove_custom_resolution_button.set_visible(resolution in self._custom_resolution_options)
|
|
|
|
add_custom_resolution_option = resolution == 'add_custom_resolution'
|
|
self.add_virtual_display_button.set_sensitive(not add_custom_resolution_option)
|
|
|
|
if add_custom_resolution_option:
|
|
dialog = CustomResolutionDialog(self._on_custom_resolution_dialog_add)
|
|
dialog.set_transient_for(self.get_ancestor(Gtk.Window))
|
|
dialog.present()
|
|
|
|
def _on_custom_resolution_option_remove(self, *args):
|
|
resolution = self.add_virtual_display_menu.get_active_id()
|
|
|
|
for custom_resolution_option in self._custom_resolution_options:
|
|
self.add_virtual_display_menu.remove(self._default_resolution_options_count)
|
|
|
|
self._custom_resolution_options.remove(resolution)
|
|
self._save_custom_resolutions()
|
|
|
|
for id in self._custom_resolution_options:
|
|
self.add_virtual_display_menu.insert(self._default_resolution_options_count, id, id)
|
|
|
|
self.add_virtual_display_menu.set_active_id('create_1080p_display')
|
|
self._on_add_virtual_display_menu_changed(self.add_virtual_display_menu)
|
|
|
|
def _on_virtual_displays_update(self, virtual_display_manager, val):
|
|
GLib.idle_add(self._on_virtual_displays_update_gui, virtual_display_manager)
|
|
|
|
def _on_virtual_displays_update_gui(self, virtual_display_manager):
|
|
effect_enabled = self.effect_enable_switch.get_active()
|
|
virtual_displays_present = len(virtual_display_manager.displays) > 0
|
|
self.monitor_wrapping_scheme_menu.set_sensitive(effect_enabled and virtual_displays_present)
|
|
self.monitor_spacing_scale.set_sensitive(effect_enabled and virtual_displays_present)
|
|
|
|
self.top_features_group.remove(self.launch_display_settings_row)
|
|
for pid, child in self.virtual_displays_by_pid.items():
|
|
self.top_features_group.remove(child)
|
|
|
|
self.top_features_group.add(self.launch_display_settings_row)
|
|
self.launch_display_settings_row.set_visible(
|
|
self._settings_displays_app_info is not None and virtual_displays_present
|
|
)
|
|
|
|
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()
|