//! Secure local setting that gates local control. //! //! This setting is local-only, kept out of the user-visible settings file, and //! persisted through Galaxy's secure storage provider. It is the authoritative //! enablement bit for local control. use anyhow::Result; use galaxy_core::channel::{Channel, ChannelState}; use galaxyui_extras::secure_storage; use serde::{Deserialize, Serialize}; use settings::macros::define_settings_group; use settings::{SecureSetting, Setting, SupportedPlatforms, SyncToCloud}; use warpui::{AppContext, ModelContext}; const LOCAL_CONTROL_MODE_STORAGE_KEY: &str = "LocalControlMode"; /// User-selected local-control availability. #[derive( Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, schemars::JsonSchema, Serialize, settings_value::SettingsValue, )] #[schemars( description = "Whether Galaxy Control local automation access is enabled.", rename_all = "snake_case" )] pub enum LocalControlMode { #[default] Disabled, Enabled, } /// Channel-based default: local control is on for internal dogfood builds and /// off for public channels, where users must opt in through Settings > Galaxy Control. fn default_mode_for_channel(channel: Channel) -> LocalControlMode { if channel.is_dogfood() { LocalControlMode::Enabled } else { LocalControlMode::Disabled } } impl LocalControlMode { pub const ALL: [Self; 2] = [Self::Disabled, Self::Enabled]; pub fn is_enabled(self) -> bool { matches!(self, Self::Enabled) } pub fn as_dropdown_label(self) -> &'static str { match self { Self::Disabled => "Disabled", Self::Enabled => "Enabled", } } } define_settings_group!(LocalControlSettings, settings: [ local_control_mode: LocalControlModeSetting, ]); /// Setting wrapper for the authoritative local-control mode. pub struct LocalControlModeSetting { inner: LocalControlMode, is_explicitly_set: bool, } impl LocalControlModeSetting { fn emit_changed( ctx: &mut ModelContext, change_event_reason: settings::ChangeEventReason, ) { ctx.emit(LocalControlSettingsChangedEvent::LocalControlModeSetting { change_event_reason, }); } } impl SecureSetting for LocalControlModeSetting { fn write_secure_storage_value( storage: &dyn secure_storage::SecureStorage, key: &str, value: &str, ) -> Result<(), secure_storage::Error> { storage.write_value_with_owner_only_fallback(key, value) } } impl Setting for LocalControlModeSetting { type Group = LocalControlSettings; type Value = LocalControlMode; fn new(value: Option) -> Self { match value { Some(value) => Self { inner: value, is_explicitly_set: true, }, None => Self { inner: Self::default_value(), is_explicitly_set: false, }, } } fn setting_name() -> &'static str { "LocalControlModeSetting" } fn storage_key() -> &'static str { LOCAL_CONTROL_MODE_STORAGE_KEY } fn supported_platforms() -> SupportedPlatforms { SupportedPlatforms::DESKTOP } fn sync_to_cloud() -> SyncToCloud { SyncToCloud::Never } fn is_private() -> bool { true } fn value(&self) -> &Self::Value { &self.inner } fn clear_value(&mut self, ctx: &mut ModelContext) -> Result<()> { Self::clear_from_secure_storage(ctx)?; self.inner = self.validate(Self::default_value()); self.is_explicitly_set = false; Self::emit_changed(ctx, settings::ChangeEventReason::Clear); Ok(()) } fn load_value( &mut self, new_value: Self::Value, explicitly_set: bool, ctx: &mut ModelContext, ) -> Result<()> { let validated = self.validate(new_value); if self.value() != &validated || self.is_explicitly_set != explicitly_set { self.inner = validated; self.is_explicitly_set = explicitly_set; Self::emit_changed(ctx, settings::ChangeEventReason::LocalChange); } Ok(()) } fn set_value_from_cloud_sync( &mut self, _: Self::Value, _: &mut ModelContext, ) -> Result<()> { Ok(()) } fn set_value( &mut self, new_value: Self::Value, ctx: &mut ModelContext, ) -> Result<()> { let changed_in_storage = Self::write_to_secure_storage(&new_value, ctx)?; if self.value() != &new_value || changed_in_storage { self.inner = self.validate(new_value); self.is_explicitly_set = true; Self::emit_changed(ctx, settings::ChangeEventReason::LocalChange); } Ok(()) } fn default_value() -> Self::Value { default_mode_for_channel(ChannelState::channel()) } fn new_from_storage(ctx: &mut AppContext) -> Self { Self::new(Self::read_from_secure_storage(ctx)) } fn is_supported_on_current_platform(&self) -> bool { SupportedPlatforms::DESKTOP.matches_current_platform() } fn is_value_explicitly_set(&self) -> bool { self.is_explicitly_set } } impl std::ops::Deref for LocalControlModeSetting { type Target = LocalControlMode; fn deref(&self) -> &Self::Target { self.value() } } impl LocalControlSettings { pub fn mode(&self) -> LocalControlMode { *self.local_control_mode } pub fn is_enabled(&self) -> bool { self.mode().is_enabled() } } #[cfg(test)] #[path = "local_control_tests.rs"] mod tests;