Pull gnome-specific portions of UI out so UI package can be reused for breezy-box
This commit is contained in:
parent
254fff37ff
commit
ece35f9af0
|
|
@ -97,7 +97,7 @@ popd
|
||||||
UI_BUILD_ARTIFACT=$UI_DIR/out/breezyUI-$ARCH.tar.gz
|
UI_BUILD_ARTIFACT=$UI_DIR/out/breezyUI-$ARCH.tar.gz
|
||||||
if [ ! -e "$UI_BUILD_ARTIFACT" ] || [ "$1" == "--rebuild-ui" ] || [ "$1" == "--rebuild-all" ]; then
|
if [ ! -e "$UI_BUILD_ARTIFACT" ] || [ "$1" == "--rebuild-ui" ] || [ "$1" == "--rebuild-all" ]; then
|
||||||
pushd $UI_DIR
|
pushd $UI_DIR
|
||||||
bin/package $ARCH
|
RUNTIME_DIR=$GNOME_DIR/ui bin/package $ARCH
|
||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
tar -xf $UI_BUILD_ARTIFACT -C $PACKAGE_DIR
|
tar -xf $UI_BUILD_ARTIFACT -C $PACKAGE_DIR
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
"""GNOME Shell runtime environment for Breezy Desktop.
|
||||||
|
|
||||||
|
This is the reference RuntimeEnvironment implementation. It is packaged into the
|
||||||
|
UI's ``runtimes`` subpackage by the GNOME package script (see bin/package_gnome
|
||||||
|
-> ui/bin/package), so its imports are relative to the installed
|
||||||
|
``breezydesktop`` package.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pydbus
|
||||||
|
|
||||||
|
from ..runtimeenvironment import RuntimeEnvironment
|
||||||
|
|
||||||
|
logger = logging.getLogger('breezy_ui')
|
||||||
|
|
||||||
|
BREEZY_DESKTOP_UUID = "breezydesktop@xronlinux.com"
|
||||||
|
EXTENSION_STATE_ENABLED = 1
|
||||||
|
|
||||||
|
|
||||||
|
class BreezyGNOMERuntimeEnvironment(RuntimeEnvironment):
|
||||||
|
"""Runs Breezy Desktop as a GNOME Shell extension.
|
||||||
|
|
||||||
|
Enablement is backed by the GNOME Shell extension state, verification runs
|
||||||
|
the breezy_gnome_verify binary, updates are checked against GitHub, and
|
||||||
|
virtual displays are created via the Mutter ScreenCast portal.
|
||||||
|
"""
|
||||||
|
|
||||||
|
APP_NAMESPACE = 'breezy_gnome'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.bus = pydbus.SessionBus()
|
||||||
|
self.gnome_shell_extensions = self.bus.get("org.gnome.Shell.Extensions")
|
||||||
|
self.gnome_shell_extensions.ExtensionStateChanged.connect(self._handle_extension_state_change)
|
||||||
|
|
||||||
|
self._breezy_enabled = self.is_enabled()
|
||||||
|
|
||||||
|
def _handle_extension_state_change(self, extension_uuid, state):
|
||||||
|
if extension_uuid == BREEZY_DESKTOP_UUID:
|
||||||
|
enabled = state.get('state') == EXTENSION_STATE_ENABLED
|
||||||
|
# update internal state first so do_set_property doesn't re-trigger
|
||||||
|
# an extension enable/disable; this just emits the notify
|
||||||
|
self._breezy_enabled = enabled
|
||||||
|
self.set_property('breezy-enabled', enabled)
|
||||||
|
|
||||||
|
# --- effect enablement ------------------------------------------------
|
||||||
|
|
||||||
|
def is_installed(self):
|
||||||
|
extensions_result = self.gnome_shell_extensions.ListExtensions()
|
||||||
|
for extension in extensions_result:
|
||||||
|
if extension == BREEZY_DESKTOP_UUID:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_enabled(self):
|
||||||
|
return self.gnome_shell_extensions.GetExtensionInfo(BREEZY_DESKTOP_UUID).get('state') == EXTENSION_STATE_ENABLED
|
||||||
|
|
||||||
|
def enable(self):
|
||||||
|
if not self.gnome_shell_extensions.UserExtensionsEnabled:
|
||||||
|
self.gnome_shell_extensions.UserExtensionsEnabled = True
|
||||||
|
self.gnome_shell_extensions.EnableExtension(BREEZY_DESKTOP_UUID)
|
||||||
|
self._breezy_enabled = True
|
||||||
|
|
||||||
|
def disable(self):
|
||||||
|
self.gnome_shell_extensions.DisableExtension(BREEZY_DESKTOP_UUID)
|
||||||
|
self._breezy_enabled = False
|
||||||
|
|
||||||
|
# --- verification / updates -------------------------------------------
|
||||||
|
|
||||||
|
def verify(self):
|
||||||
|
from .verify import verify_installation
|
||||||
|
return verify_installation()
|
||||||
|
|
||||||
|
def check_for_update(self, current_version, callback):
|
||||||
|
from .updatechecker import check_for_update
|
||||||
|
return check_for_update(current_version, callback)
|
||||||
|
|
||||||
|
# --- optional views ---------------------------------------------------
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shows_no_device_view(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# --- virtual displays -------------------------------------------------
|
||||||
|
|
||||||
|
def is_virtual_display_supported(self):
|
||||||
|
# wayland + the Mutter ScreenCast portal are required to create displays
|
||||||
|
from .virtualdisplay import is_screencast_available
|
||||||
|
return is_screencast_available() and "WAYLAND_DISPLAY" in os.environ
|
||||||
|
|
||||||
|
def _create_virtual_display_manager(self):
|
||||||
|
from .virtualdisplaymanager import VirtualDisplayManager
|
||||||
|
return VirtualDisplayManager.get_instance()
|
||||||
|
|
||||||
|
# --- GObject property plumbing ----------------------------------------
|
||||||
|
|
||||||
|
def do_set_property(self, prop, value):
|
||||||
|
if prop.name == 'breezy-enabled' and value != self._breezy_enabled:
|
||||||
|
self.enable() if value else self.disable()
|
||||||
|
|
||||||
|
def do_get_property(self, prop):
|
||||||
|
if prop.name == 'breezy-enabled':
|
||||||
|
return self._breezy_enabled
|
||||||
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from .files import get_bin_home
|
from ..files import get_bin_home
|
||||||
|
|
||||||
logger = logging.getLogger('breezy_ui')
|
logger = logging.getLogger('breezy_ui')
|
||||||
|
|
||||||
|
|
@ -12,7 +12,7 @@ logger = logging.getLogger('breezy_ui')
|
||||||
gi.require_version('GLib', '2.0')
|
gi.require_version('GLib', '2.0')
|
||||||
from gi.repository import GLib, GObject
|
from gi.repository import GLib, GObject
|
||||||
|
|
||||||
from .files import get_bin_home
|
from ..files import get_bin_home
|
||||||
|
|
||||||
class VirtualDisplayManager(GObject.GObject):
|
class VirtualDisplayManager(GObject.GObject):
|
||||||
__gproperties__ = {
|
__gproperties__ = {
|
||||||
|
|
@ -6,6 +6,18 @@ set -e
|
||||||
ARCH=${ARCH:-$(uname -m)}
|
ARCH=${ARCH:-$(uname -m)}
|
||||||
echo "Building Breezy UI for $ARCH"
|
echo "Building Breezy UI for $ARCH"
|
||||||
|
|
||||||
|
# Directory containing the RuntimeEnvironment implementation(s) to bundle. Every
|
||||||
|
# *.py in here is copied into the UI's runtimes/ package; the first
|
||||||
|
# RuntimeEnvironment subclass found at startup becomes the active environment.
|
||||||
|
# Defaults to the reference GNOME implementation so a standalone UI build (e.g.
|
||||||
|
# local dev) still has a runtime.
|
||||||
|
RUNTIME_DIR=${RUNTIME_DIR:-$(realpath "$(dirname "$0")/../../gnome/ui")}
|
||||||
|
echo "Bundling runtime environment from $RUNTIME_DIR"
|
||||||
|
if [ ! -d "$RUNTIME_DIR" ]; then
|
||||||
|
echo "Runtime directory $RUNTIME_DIR does not exist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
BUILD_PATH=build
|
BUILD_PATH=build
|
||||||
UI_BUILD_PATH=$BUILD_PATH/ui
|
UI_BUILD_PATH=$BUILD_PATH/ui
|
||||||
PACKAGE_DIR=$BUILD_PATH/breezy_ui
|
PACKAGE_DIR=$BUILD_PATH/breezy_ui
|
||||||
|
|
@ -38,6 +50,12 @@ mkdir -p $PACKAGE_SCHEMAS_DIR
|
||||||
|
|
||||||
cp src/*.py $PACKAGE_BREEZY_SRC_DIR
|
cp src/*.py $PACKAGE_BREEZY_SRC_DIR
|
||||||
cp -r lib $PACKAGE_BREEZY_SRC_DIR
|
cp -r lib $PACKAGE_BREEZY_SRC_DIR
|
||||||
|
|
||||||
|
# bundle the runtimes subpackage and the selected runtime implementation(s)
|
||||||
|
PACKAGE_RUNTIMES_DIR=$PACKAGE_BREEZY_SRC_DIR/runtimes
|
||||||
|
mkdir -p $PACKAGE_RUNTIMES_DIR
|
||||||
|
cp src/runtimes/__init__.py $PACKAGE_RUNTIMES_DIR
|
||||||
|
cp $RUNTIME_DIR/*.py $PACKAGE_RUNTIMES_DIR
|
||||||
cp -L modules/PyXRLinuxDriverIPC/xrdriveripc.py $PACKAGE_BREEZY_SRC_DIR
|
cp -L modules/PyXRLinuxDriverIPC/xrdriveripc.py $PACKAGE_BREEZY_SRC_DIR
|
||||||
cp $UI_BUILD_PATH/src/breezydesktop.gresource $PACKAGE_BREEZY_DIR
|
cp $UI_BUILD_PATH/src/breezydesktop.gresource $PACKAGE_BREEZY_DIR
|
||||||
cp -r po/mo/* $PACKAGE_LOCALE_DIR
|
cp -r po/mo/* $PACKAGE_LOCALE_DIR
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,12 @@ from gi.repository import Gio, GLib, Gtk, GObject
|
||||||
from .configmanager import ConfigManager
|
from .configmanager import ConfigManager
|
||||||
from .customresolutiondialog import CustomResolutionDialog
|
from .customresolutiondialog import CustomResolutionDialog
|
||||||
from .displaydistancedialog import DisplayDistanceDialog
|
from .displaydistancedialog import DisplayDistanceDialog
|
||||||
from .extensionsmanager import ExtensionsManager
|
|
||||||
from .files import get_state_dir
|
from .files import get_state_dir
|
||||||
from .license import BREEZY_GNOME_FEATURES
|
from .license import BREEZY_GNOME_FEATURES
|
||||||
|
from .runtimeenvironment import RuntimeEnvironment
|
||||||
from .settingsmanager import SettingsManager
|
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 .virtualdisplay import is_screencast_available
|
|
||||||
from .virtualdisplayrow import VirtualDisplayRow
|
from .virtualdisplayrow import VirtualDisplayRow
|
||||||
from .xrdriveripc import XRDriverIPC
|
from .xrdriveripc import XRDriverIPC
|
||||||
|
|
||||||
|
|
@ -115,8 +113,8 @@ class ConnectedDevice(Gtk.Box):
|
||||||
self.settings = SettingsManager.get_instance().settings
|
self.settings = SettingsManager.get_instance().settings
|
||||||
self.desktop_settings = SettingsManager.get_instance().desktop_settings
|
self.desktop_settings = SettingsManager.get_instance().desktop_settings
|
||||||
self.ipc = XRDriverIPC.get_instance()
|
self.ipc = XRDriverIPC.get_instance()
|
||||||
self.virtual_display_manager = VirtualDisplayManager.get_instance()
|
self.runtime = RuntimeEnvironment.get_instance()
|
||||||
self.extensions_manager = ExtensionsManager.get_instance()
|
self.virtual_display_manager = self.runtime.virtual_display_manager
|
||||||
|
|
||||||
self.settings.bind('disable-physical-displays', self.disable_physical_displays_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
|
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.connect('changed::display-distance', self._handle_display_distance)
|
||||||
|
|
@ -207,7 +205,7 @@ class ConnectedDevice(Gtk.Box):
|
||||||
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)
|
||||||
self._refresh_use_optimal_monitor_config(self.use_optimal_monitor_config_switch, 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.runtime.connect('notify::breezy-enabled', self._handle_enabled_config)
|
||||||
|
|
||||||
self._settings_displays_app_info = None
|
self._settings_displays_app_info = None
|
||||||
|
|
||||||
|
|
@ -229,9 +227,6 @@ class ConnectedDevice(Gtk.Box):
|
||||||
for id in self._custom_resolution_options:
|
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.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):
|
def _bind_scale_to_config(self, scale, config_key):
|
||||||
self.config_manager.bind_property(config_key, scale, 'value', Gio.SettingsBindFlags.DEFAULT)
|
self.config_manager.bind_property(config_key, scale, 'value', Gio.SettingsBindFlags.DEFAULT)
|
||||||
scale.set_value(self.config_manager.get_property(config_key))
|
scale.set_value(self.config_manager.get_property(config_key))
|
||||||
|
|
@ -285,7 +280,7 @@ class ConnectedDevice(Gtk.Box):
|
||||||
# self.widescreen_mode_row.set_subtitle(subtitle)
|
# self.widescreen_mode_row.set_subtitle(subtitle)
|
||||||
|
|
||||||
def _handle_enabled_config(self, object, val):
|
def _handle_enabled_config(self, object, val):
|
||||||
enabled = self.config_manager.get_property('breezy-desktop-enabled') and self.extensions_manager.get_property('breezy-enabled')
|
enabled = self.config_manager.get_property('breezy-desktop-enabled') and self.runtime.get_property('breezy-enabled')
|
||||||
if enabled != self.effect_enable_switch.get_active():
|
if enabled != self.effect_enable_switch.get_active():
|
||||||
self.effect_enable_switch.set_active(enabled)
|
self.effect_enable_switch.set_active(enabled)
|
||||||
|
|
||||||
|
|
@ -297,14 +292,14 @@ class ConnectedDevice(Gtk.Box):
|
||||||
|
|
||||||
# never turn off the extension, disabling the effect is done via configs only
|
# never turn off the extension, disabling the effect is done via configs only
|
||||||
if requesting_enabled:
|
if requesting_enabled:
|
||||||
self.extensions_manager.set_property('breezy-enabled', True)
|
self.runtime.set_property('breezy-enabled', True)
|
||||||
|
|
||||||
self.config_manager.set_property('breezy-desktop-enabled', requesting_enabled)
|
self.config_manager.set_property('breezy-desktop-enabled', requesting_enabled)
|
||||||
|
|
||||||
for widget in self.all_enabled_state_inputs:
|
for widget in self.all_enabled_state_inputs:
|
||||||
widget.set_sensitive(requesting_enabled)
|
widget.set_sensitive(requesting_enabled)
|
||||||
|
|
||||||
if not is_screencast_available() or not self.is_wayland:
|
if not self.runtime.is_virtual_display_supported():
|
||||||
self.virtual_displays_row.set_subtitle(
|
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."))
|
_("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_button.set_sensitive(False)
|
||||||
|
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
import pydbus
|
|
||||||
from gi.repository import GObject
|
|
||||||
|
|
||||||
BREEZY_DESKTOP_UUID = "breezydesktop@xronlinux.com"
|
|
||||||
EXTENSION_STATE_ENABLED = 1
|
|
||||||
|
|
||||||
class ExtensionsManager(GObject.GObject):
|
|
||||||
__gproperties__ = {
|
|
||||||
'breezy-enabled': (bool, 'Breezy Enabled', 'Whether the Breezy Desktop GNOME extension is enabled', False, GObject.ParamFlags.READWRITE)
|
|
||||||
}
|
|
||||||
|
|
||||||
_instance = None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_instance():
|
|
||||||
if ExtensionsManager._instance is None:
|
|
||||||
ExtensionsManager._instance = ExtensionsManager()
|
|
||||||
return ExtensionsManager._instance
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
GObject.GObject.__init__(self)
|
|
||||||
|
|
||||||
self.bus = pydbus.SessionBus()
|
|
||||||
self.gnome_shell_extensions = self.bus.get("org.gnome.Shell.Extensions")
|
|
||||||
self.gnome_shell_extensions.ExtensionStateChanged.connect(self._handle_extension_state_change)
|
|
||||||
|
|
||||||
self.remote_extension_state = self.is_enabled()
|
|
||||||
|
|
||||||
def _handle_extension_state_change(self, extension_uuid, state):
|
|
||||||
if extension_uuid == BREEZY_DESKTOP_UUID:
|
|
||||||
self.remote_extension_state = state.get('state') == EXTENSION_STATE_ENABLED
|
|
||||||
self.set_property('breezy-enabled', self.remote_extension_state)
|
|
||||||
|
|
||||||
def is_installed(self):
|
|
||||||
return self._is_installed(BREEZY_DESKTOP_UUID)
|
|
||||||
|
|
||||||
def enable(self):
|
|
||||||
self._enable_extension(BREEZY_DESKTOP_UUID)
|
|
||||||
|
|
||||||
def disable(self):
|
|
||||||
self._disable_extension(BREEZY_DESKTOP_UUID)
|
|
||||||
|
|
||||||
def is_enabled(self):
|
|
||||||
return self._is_enabled(BREEZY_DESKTOP_UUID)
|
|
||||||
|
|
||||||
def _is_installed(self, extension_uuid):
|
|
||||||
extensions_result = self.gnome_shell_extensions.ListExtensions()
|
|
||||||
for extension in extensions_result:
|
|
||||||
if extension == extension_uuid:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _enable_extension(self, extension_uuid):
|
|
||||||
if not self.gnome_shell_extensions.UserExtensionsEnabled:
|
|
||||||
self.gnome_shell_extensions.UserExtensionsEnabled = True
|
|
||||||
|
|
||||||
self.gnome_shell_extensions.EnableExtension(extension_uuid)
|
|
||||||
|
|
||||||
def _disable_extension(self, extension_uuid):
|
|
||||||
self.gnome_shell_extensions.DisableExtension(extension_uuid)
|
|
||||||
|
|
||||||
def _is_enabled(self, extension_uuid):
|
|
||||||
return self.gnome_shell_extensions.GetExtensionInfo(extension_uuid).get('state') == EXTENSION_STATE_ENABLED
|
|
||||||
|
|
||||||
def do_set_property(self, prop, value):
|
|
||||||
if prop.name == 'breezy-enabled' and value != self.remote_extension_state:
|
|
||||||
self.enable() if value == True else self.disable()
|
|
||||||
|
|
||||||
def do_get_property(self, prop):
|
|
||||||
if prop.name == 'breezy-enabled':
|
|
||||||
return self.remote_extension_state
|
|
||||||
|
|
@ -10,8 +10,11 @@ def get_config_dir():
|
||||||
return os.path.expanduser(config_home)
|
return os.path.expanduser(config_home)
|
||||||
|
|
||||||
def get_state_dir():
|
def get_state_dir():
|
||||||
|
# imported lazily to avoid an import cycle (runtime discovery imports
|
||||||
|
# modules that import this one)
|
||||||
|
from .runtime import runtime_namespace
|
||||||
state_home = os.environ.get('XDG_STATE_HOME', '~/.local/state')
|
state_home = os.environ.get('XDG_STATE_HOME', '~/.local/state')
|
||||||
return os.path.join(os.path.expanduser(state_home), 'breezy_gnome')
|
return os.path.join(os.path.expanduser(state_home), runtime_namespace())
|
||||||
|
|
||||||
def get_data_home():
|
def get_data_home():
|
||||||
data_home = os.environ.get('XDG_DATA_HOME', '~/.local/share')
|
data_home = os.environ.get('XDG_DATA_HOME', '~/.local/share')
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@ breezydesktop_sources = [
|
||||||
'connecteddevice.py',
|
'connecteddevice.py',
|
||||||
'customresolutiondialog.py',
|
'customresolutiondialog.py',
|
||||||
'customresolutiondialogcontent.py',
|
'customresolutiondialogcontent.py',
|
||||||
'extensionsmanager.py',
|
|
||||||
'displaydistancedialog.py',
|
'displaydistancedialog.py',
|
||||||
'displaydistancedialogcontent.py',
|
'displaydistancedialogcontent.py',
|
||||||
'failedverification.py',
|
'failedverification.py',
|
||||||
|
|
@ -57,16 +56,18 @@ breezydesktop_sources = [
|
||||||
'nodriver.py',
|
'nodriver.py',
|
||||||
'noextension.py',
|
'noextension.py',
|
||||||
'nolicense.py',
|
'nolicense.py',
|
||||||
|
'runtime.py',
|
||||||
|
'runtimeenvironment.py',
|
||||||
'settingsmanager.py',
|
'settingsmanager.py',
|
||||||
'shortcutdialog.py',
|
'shortcutdialog.py',
|
||||||
'statemanager.py',
|
'statemanager.py',
|
||||||
'time.py',
|
'time.py',
|
||||||
'updatechecker.py',
|
|
||||||
'virtualdisplay.py',
|
|
||||||
'virtualdisplaymanager.py',
|
|
||||||
'verify.py',
|
|
||||||
'window.py'
|
'window.py'
|
||||||
]
|
]
|
||||||
|
|
||||||
install_data(breezydesktop_sources, install_dir: moduledir)
|
install_data(breezydesktop_sources, install_dir: moduledir)
|
||||||
install_subdir('../lib', install_dir: moduledir)
|
install_subdir('../lib', install_dir: moduledir)
|
||||||
|
|
||||||
|
# the runtimes subpackage marker; a concrete RuntimeEnvironment implementation
|
||||||
|
# is copied in per-build by the package script (see ui/bin/package).
|
||||||
|
install_data('runtimes/__init__.py', install_dir: moduledir / 'runtimes')
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from gi.repository import Gio, Gtk
|
from gi.repository import Gio, Gtk
|
||||||
from .configmanager import ConfigManager
|
from .configmanager import ConfigManager
|
||||||
from .extensionsmanager import ExtensionsManager
|
from .runtimeenvironment import RuntimeEnvironment
|
||||||
from .settingsmanager import SettingsManager
|
from .settingsmanager import SettingsManager
|
||||||
from .statemanager import StateManager
|
from .statemanager import StateManager
|
||||||
from .xrdriveripc import XRDriverIPC
|
from .xrdriveripc import XRDriverIPC
|
||||||
|
|
@ -18,7 +18,7 @@ class NoDevice(Gtk.Box):
|
||||||
self.init_template()
|
self.init_template()
|
||||||
|
|
||||||
self.ipc = XRDriverIPC.get_instance()
|
self.ipc = XRDriverIPC.get_instance()
|
||||||
self.extensions_manager = ExtensionsManager.get_instance()
|
self.runtime = RuntimeEnvironment.get_instance()
|
||||||
self.settings = SettingsManager.get_instance().settings
|
self.settings = SettingsManager.get_instance().settings
|
||||||
self.config_manager = ConfigManager.get_instance()
|
self.config_manager = ConfigManager.get_instance()
|
||||||
self.config_manager.connect('notify::breezy-desktop-enabled', self._handle_enabled_config)
|
self.config_manager.connect('notify::breezy-desktop-enabled', self._handle_enabled_config)
|
||||||
|
|
@ -30,16 +30,16 @@ class NoDevice(Gtk.Box):
|
||||||
self._handle_enabled_config(self.config_manager, None)
|
self._handle_enabled_config(self.config_manager, None)
|
||||||
|
|
||||||
def _handle_enabled_config(self, config_manager, val):
|
def _handle_enabled_config(self, config_manager, val):
|
||||||
enabled = config_manager.get_property('breezy-desktop-enabled') and self.extensions_manager.get_property('breezy-enabled')
|
enabled = config_manager.get_property('breezy-desktop-enabled') and self.runtime.get_property('breezy-enabled')
|
||||||
if enabled != self.effect_enable_switch.get_active():
|
if enabled != self.effect_enable_switch.get_active():
|
||||||
self.effect_enable_switch.set_active(enabled)
|
self.effect_enable_switch.set_active(enabled)
|
||||||
|
|
||||||
def _handle_switch_enabled_state(self, switch, param):
|
def _handle_switch_enabled_state(self, switch, param):
|
||||||
requesting_enabled = switch.get_active()
|
requesting_enabled = switch.get_active()
|
||||||
|
|
||||||
# never turn off the extension, disabling the effect is done via configs only
|
# never turn off the extension, disabling the effect is done via configs only
|
||||||
if requesting_enabled:
|
if requesting_enabled:
|
||||||
self.extensions_manager.set_property('breezy-enabled', True)
|
self.runtime.set_property('breezy-enabled', True)
|
||||||
|
|
||||||
self.config_manager.set_property('breezy-desktop-enabled', requesting_enabled)
|
self.config_manager.set_property('breezy-desktop-enabled', requesting_enabled)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
"""Runtime environment discovery.
|
||||||
|
|
||||||
|
Exactly one concrete :class:`~breezydesktop.runtimeenvironment.RuntimeEnvironment`
|
||||||
|
implementation is bundled into the ``runtimes`` subpackage at package time. This
|
||||||
|
module finds it (the first one it sees) and exposes both the class and a cheap
|
||||||
|
way to read its namespace without constructing the (potentially side-effectful)
|
||||||
|
instance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import inspect
|
||||||
|
import logging
|
||||||
|
import pkgutil
|
||||||
|
|
||||||
|
from .runtimeenvironment import RuntimeEnvironment, DEFAULT_APP_NAMESPACE
|
||||||
|
|
||||||
|
logger = logging.getLogger('breezy_ui')
|
||||||
|
|
||||||
|
_runtime_class = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_runtime_class():
|
||||||
|
"""Return the active RuntimeEnvironment subclass.
|
||||||
|
|
||||||
|
Scans the bundled ``runtimes`` subpackage and returns the first concrete
|
||||||
|
RuntimeEnvironment subclass found. The result is cached. Raises
|
||||||
|
RuntimeError if no implementation is bundled.
|
||||||
|
"""
|
||||||
|
global _runtime_class
|
||||||
|
if _runtime_class is not None:
|
||||||
|
return _runtime_class
|
||||||
|
|
||||||
|
from . import runtimes
|
||||||
|
|
||||||
|
for module_info in pkgutil.iter_modules(runtimes.__path__, runtimes.__name__ + '.'):
|
||||||
|
try:
|
||||||
|
module = importlib.import_module(module_info.name)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to import runtime module %s: %s", module_info.name, e)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for _, obj in inspect.getmembers(module, inspect.isclass):
|
||||||
|
if issubclass(obj, RuntimeEnvironment) and obj is not RuntimeEnvironment \
|
||||||
|
and obj.__module__ == module_info.name:
|
||||||
|
logger.info("Using runtime environment %s", obj.__name__)
|
||||||
|
_runtime_class = obj
|
||||||
|
return _runtime_class
|
||||||
|
|
||||||
|
raise RuntimeError(
|
||||||
|
"No RuntimeEnvironment implementation was found in the 'runtimes' package. "
|
||||||
|
"A runtime implementation must be bundled at package time.")
|
||||||
|
|
||||||
|
|
||||||
|
def runtime_namespace():
|
||||||
|
"""Return the active runtime's application namespace.
|
||||||
|
|
||||||
|
Falls back to the default namespace if no runtime is bundled, so early
|
||||||
|
bootstrap paths (e.g. log directory setup) never fail.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return get_runtime_class().app_namespace()
|
||||||
|
except RuntimeError:
|
||||||
|
return DEFAULT_APP_NAMESPACE
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
import gettext
|
||||||
|
|
||||||
|
from gi.repository import GObject
|
||||||
|
|
||||||
|
_ = gettext.gettext
|
||||||
|
|
||||||
|
# Default namespace used for XDG directories, application identity, etc. A
|
||||||
|
# concrete RuntimeEnvironment should override APP_NAMESPACE.
|
||||||
|
DEFAULT_APP_NAMESPACE = 'breezy_gnome'
|
||||||
|
|
||||||
|
|
||||||
|
class NullVirtualDisplayManager(GObject.GObject):
|
||||||
|
"""A no-op virtual display manager.
|
||||||
|
|
||||||
|
Provides the same interface (the 'displays' property + change
|
||||||
|
notifications, plus create/destroy methods) that the UI binds to, but
|
||||||
|
never creates anything. Runtime environments that don't support virtual
|
||||||
|
displays can use this so the UI degrades gracefully.
|
||||||
|
"""
|
||||||
|
__gproperties__ = {
|
||||||
|
'displays': (object, 'Displays', 'A list of the displays', GObject.ParamFlags.READWRITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
GObject.GObject.__init__(self)
|
||||||
|
self._displays = []
|
||||||
|
|
||||||
|
def create_virtual_display(self, width, height, framerate):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def destroy_virtual_display(self, pid):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def do_set_property(self, prop, value):
|
||||||
|
if prop.name == 'displays':
|
||||||
|
self._displays = value
|
||||||
|
|
||||||
|
def do_get_property(self, prop):
|
||||||
|
if prop.name == 'displays':
|
||||||
|
return self._displays
|
||||||
|
|
||||||
|
|
||||||
|
class RuntimeEnvironment(GObject.GObject):
|
||||||
|
"""Abstraction over the host environment the UI is running in.
|
||||||
|
|
||||||
|
A RuntimeEnvironment encapsulates everything that differs between the
|
||||||
|
environments Breezy can run in (e.g. GNOME Shell vs. a headless Breezy Box):
|
||||||
|
how the effect is enabled, how the installation is verified, whether/how
|
||||||
|
updates are checked, how virtual displays are managed, and which optional
|
||||||
|
views and fields the UI should present.
|
||||||
|
|
||||||
|
The first concrete subclass discovered in the bundled ``runtimes`` package
|
||||||
|
(see :mod:`breezydesktop.runtime`) is instantiated as the active
|
||||||
|
environment, so a build can swap behavior simply by packaging a different
|
||||||
|
implementation.
|
||||||
|
|
||||||
|
Subclasses inherit the ``breezy-enabled`` GObject property; override
|
||||||
|
:meth:`enable`/:meth:`disable`/:meth:`is_enabled` (and, if needed,
|
||||||
|
``do_set_property``/``do_get_property``) to back it with real state.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The application/namespace identifier, e.g. 'breezy_gnome' or 'breezy_box'.
|
||||||
|
# Used for namespacing XDG directories and similar. Subclasses must set
|
||||||
|
# this.
|
||||||
|
APP_NAMESPACE = None
|
||||||
|
|
||||||
|
__gproperties__ = {
|
||||||
|
'breezy-enabled': (bool, 'Breezy Enabled', 'Whether the Breezy Desktop effect is enabled', False, GObject.ParamFlags.READWRITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_instance(cls):
|
||||||
|
if RuntimeEnvironment._instance is None:
|
||||||
|
from .runtime import get_runtime_class
|
||||||
|
RuntimeEnvironment._instance = get_runtime_class()()
|
||||||
|
return RuntimeEnvironment._instance
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
GObject.GObject.__init__(self)
|
||||||
|
self._breezy_enabled = False
|
||||||
|
self._virtual_display_manager = None
|
||||||
|
|
||||||
|
# --- identity ---------------------------------------------------------
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def app_namespace(cls):
|
||||||
|
return cls.APP_NAMESPACE or DEFAULT_APP_NAMESPACE
|
||||||
|
|
||||||
|
# --- effect enablement (backs the 'breezy-enabled' property) ----------
|
||||||
|
|
||||||
|
def is_installed(self):
|
||||||
|
"""Whether the supporting components for this environment are installed.
|
||||||
|
|
||||||
|
Environments with no separate component to install (e.g. a headless
|
||||||
|
box where the runtime is always present) should return True.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_enabled(self):
|
||||||
|
return self._breezy_enabled
|
||||||
|
|
||||||
|
def enable(self):
|
||||||
|
self._breezy_enabled = True
|
||||||
|
|
||||||
|
def disable(self):
|
||||||
|
self._breezy_enabled = False
|
||||||
|
|
||||||
|
# --- verification -----------------------------------------------------
|
||||||
|
|
||||||
|
def verify(self):
|
||||||
|
"""Verify the installation. Return True when verification passes (or
|
||||||
|
when the environment has no verification step)."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
# --- update checking --------------------------------------------------
|
||||||
|
|
||||||
|
def check_for_update(self, current_version, callback):
|
||||||
|
"""Asynchronously check for a newer version.
|
||||||
|
|
||||||
|
Implementations that support updates should invoke
|
||||||
|
``callback(latest_version_str)`` when a newer version is available.
|
||||||
|
The default is a no-op (no update prompt).
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
# --- optional views / fields ------------------------------------------
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shows_no_device_view(self):
|
||||||
|
"""Whether a dedicated "no device connected" view should be shown.
|
||||||
|
|
||||||
|
When False, the connected-device view is always shown and
|
||||||
|
:meth:`no_device_label` supplies the label used when no device is
|
||||||
|
actually connected.
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
|
def no_device_label(self):
|
||||||
|
"""Label shown in place of a device name when no device is connected
|
||||||
|
(only relevant when :attr:`shows_no_device_view` is False)."""
|
||||||
|
return _("No supported glasses connected")
|
||||||
|
|
||||||
|
# --- virtual displays -------------------------------------------------
|
||||||
|
|
||||||
|
def is_virtual_display_supported(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _create_virtual_display_manager(self):
|
||||||
|
"""Build the virtual display manager for this environment. Override to
|
||||||
|
provide a real implementation."""
|
||||||
|
return NullVirtualDisplayManager()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def virtual_display_manager(self):
|
||||||
|
if self._virtual_display_manager is None:
|
||||||
|
self._virtual_display_manager = self._create_virtual_display_manager()
|
||||||
|
return self._virtual_display_manager
|
||||||
|
|
||||||
|
# --- GObject property plumbing ----------------------------------------
|
||||||
|
|
||||||
|
def do_set_property(self, prop, value):
|
||||||
|
if prop.name == 'breezy-enabled' and value != self.is_enabled():
|
||||||
|
self.enable() if value else self.disable()
|
||||||
|
|
||||||
|
def do_get_property(self, prop):
|
||||||
|
if prop.name == 'breezy-enabled':
|
||||||
|
return self.is_enabled()
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
"""Bundled runtime environment implementations.
|
||||||
|
|
||||||
|
Exactly one concrete RuntimeEnvironment implementation module is copied into
|
||||||
|
this package at package time (see ui/bin/package). The implementation is
|
||||||
|
selected per-build from a runtime source directory (e.g. gnome/ui), so behavior
|
||||||
|
can be swapped without touching the core UI.
|
||||||
|
"""
|
||||||
|
|
@ -85,8 +85,8 @@ def create_display(width, height, framerate):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from breezydesktop import virtualdisplay
|
from breezydesktop.runtimes import virtualdisplay
|
||||||
from breezydesktop.virtualdisplay import VirtualDisplay
|
from breezydesktop.runtimes.virtualdisplay import VirtualDisplay
|
||||||
|
|
||||||
global virtual_display_instance
|
global virtual_display_instance
|
||||||
global loop
|
global loop
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from gi.repository import Adw, Gtk
|
from gi.repository import Adw, Gtk
|
||||||
from .virtualdisplaymanager import VirtualDisplayManager
|
from .runtimeenvironment import RuntimeEnvironment
|
||||||
|
|
||||||
import gettext
|
import gettext
|
||||||
|
|
||||||
|
|
@ -28,4 +28,4 @@ class VirtualDisplayRow(Adw.ActionRow):
|
||||||
self.remove_virtual_display_button.connect('clicked', self._remove_virtual_display)
|
self.remove_virtual_display_button.connect('clicked', self._remove_virtual_display)
|
||||||
|
|
||||||
def _remove_virtual_display(self, widget):
|
def _remove_virtual_display(self, widget):
|
||||||
VirtualDisplayManager.get_instance().destroy_virtual_display(self.pid)
|
RuntimeEnvironment.get_instance().virtual_display_manager.destroy_virtual_display(self.pid)
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from gi.repository import Gtk, GLib
|
from gi.repository import Gtk, GLib
|
||||||
from .extensionsmanager import ExtensionsManager
|
|
||||||
from .license import BREEZY_GNOME_FEATURES
|
from .license import BREEZY_GNOME_FEATURES
|
||||||
from .licensedialog import LicenseDialog
|
from .licensedialog import LicenseDialog
|
||||||
|
from .runtimeenvironment import RuntimeEnvironment
|
||||||
from .statemanager import StateManager
|
from .statemanager import StateManager
|
||||||
from .settingsmanager import SettingsManager
|
from .settingsmanager import SettingsManager
|
||||||
from .connecteddevice import ConnectedDevice
|
from .connecteddevice import ConnectedDevice
|
||||||
|
|
@ -10,8 +10,6 @@ from .nodevice import NoDevice
|
||||||
from .nodriver import NoDriver
|
from .nodriver import NoDriver
|
||||||
from .noextension import NoExtension
|
from .noextension import NoExtension
|
||||||
from .nolicense import NoLicense
|
from .nolicense import NoLicense
|
||||||
from .updatechecker import check_for_update
|
|
||||||
from .verify import verify_installation
|
|
||||||
|
|
||||||
@Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/window.ui')
|
@Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/window.ui')
|
||||||
class BreezydesktopWindow(Gtk.ApplicationWindow):
|
class BreezydesktopWindow(Gtk.ApplicationWindow):
|
||||||
|
|
@ -29,6 +27,7 @@ class BreezydesktopWindow(Gtk.ApplicationWindow):
|
||||||
def __init__(self, version, skip_verification, **kwargs):
|
def __init__(self, version, skip_verification, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.runtime = RuntimeEnvironment.get_instance()
|
||||||
self.connected_device = ConnectedDevice()
|
self.connected_device = ConnectedDevice()
|
||||||
self.failed_verification = FailedVerification()
|
self.failed_verification = FailedVerification()
|
||||||
self.no_device = NoDevice()
|
self.no_device = NoDevice()
|
||||||
|
|
@ -57,7 +56,7 @@ class BreezydesktopWindow(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
self.connect("destroy", self._on_window_destroy)
|
self.connect("destroy", self._on_window_destroy)
|
||||||
|
|
||||||
check_for_update(version, self._on_update_check_result)
|
self.runtime.check_for_update(version, self._on_update_check_result)
|
||||||
|
|
||||||
def _handle_settings_update(self, settings_manager, key):
|
def _handle_settings_update(self, settings_manager, key):
|
||||||
self._handle_state_update(self.state_manager, None)
|
self._handle_state_update(self.state_manager, None)
|
||||||
|
|
@ -82,19 +81,19 @@ class BreezydesktopWindow(Gtk.ApplicationWindow):
|
||||||
if self.settings.get_boolean('debug-no-device'):
|
if self.settings.get_boolean('debug-no-device'):
|
||||||
self.main_content.append(self.connected_device)
|
self.main_content.append(self.connected_device)
|
||||||
self.connected_device.set_device_name('Fake device')
|
self.connected_device.set_device_name('Fake device')
|
||||||
elif not self._skip_verification and not verify_installation():
|
elif not self._skip_verification and not self.runtime.verify():
|
||||||
self.main_content.append(self.failed_verification)
|
self.main_content.append(self.failed_verification)
|
||||||
elif not ExtensionsManager.get_instance().is_installed():
|
elif not self.runtime.is_installed():
|
||||||
self.main_content.append(self.no_extension)
|
self.main_content.append(self.no_extension)
|
||||||
elif not self.state_manager.driver_running:
|
elif not self.state_manager.driver_running:
|
||||||
self.main_content.append(self.no_driver)
|
self.main_content.append(self.no_driver)
|
||||||
elif not self.state_manager.license_present:
|
elif not self.state_manager.license_present:
|
||||||
self.main_content.append(self.no_license)
|
self.main_content.append(self.no_license)
|
||||||
elif not state_manager.connected_device_name:
|
elif not state_manager.connected_device_name and self.runtime.shows_no_device_view:
|
||||||
self.main_content.append(self.no_device)
|
self.main_content.append(self.no_device)
|
||||||
else:
|
else:
|
||||||
self.main_content.append(self.connected_device)
|
self.main_content.append(self.connected_device)
|
||||||
self.connected_device.set_device_name(state_manager.connected_device_name)
|
self.connected_device.set_device_name(state_manager.connected_device_name or self.runtime.no_device_label())
|
||||||
|
|
||||||
self.set_resizable(True)
|
self.set_resizable(True)
|
||||||
self.set_default_size(1, 1)
|
self.set_default_size(1, 1)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue