public ApplicationUpdaterViewModel( [NotNull] IApplicationUpdaterModel updaterModel, [NotNull] IConfigProvider <UpdateSettingsConfig> configProvider, [NotNull][Dependency(WellKnownSchedulers.UI)] IScheduler uiScheduler, [NotNull][Dependency(WellKnownSchedulers.Background)] IScheduler bgScheduler) { Guard.ArgumentNotNull(updaterModel, nameof(updaterModel)); Guard.ArgumentNotNull(uiScheduler, nameof(uiScheduler)); Guard.ArgumentNotNull(bgScheduler, nameof(bgScheduler)); Guard.ArgumentNotNull(configProvider, nameof(configProvider)); this.updaterModel = updaterModel; CheckForUpdatesCommand = CommandWrapper .Create(CheckForUpdatesCommandExecuted); CheckForUpdatesCommand .ThrownExceptions .Subscribe(ex => SetError($"Update error: {ex.Message}")) .AddTo(Anchors); this.RaiseWhenSourceValue(x => x.UpdatedVersion, updaterModel, x => x.UpdatedVersion, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.LatestVersion, updaterModel, x => x.LatestVersion, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.ProgressPercent, updaterModel, x => x.ProgressPercent, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.IsBusy, updaterModel, x => x.IsBusy, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.UpdateSource, updaterModel, x => x.UpdateSource, uiScheduler).AddTo(Anchors); RestartCommand = CommandWrapper .Create(RestartCommandExecuted); RestartCommand .ThrownExceptions .Subscribe(ex => SetError($"Restart error: {ex.Message}")) .AddTo(Anchors); configProvider .ListenTo(x => x.UpdateSource) .Subscribe(x => updaterModel.UpdateSource = x) .AddTo(Anchors); Observable.Merge( configProvider .ListenTo(x => x.AutoUpdateTimeout) .WithPrevious((prev, curr) => new { prev, curr }) .Do(timeout => Log.Debug($"[ApplicationUpdaterViewModel] AutoUpdate timeout changed: {timeout.prev} => {timeout.curr}")) .Select( timeout => timeout.curr <= TimeSpan.Zero ? Observable.Never <long>() : Observable.Timer(DateTimeOffset.MinValue, timeout.curr, bgScheduler)) .Switch() .ToUnit(), updaterModel.WhenAnyProperty(x => x.UpdateSource).ToUnit()) .ObserveOn(uiScheduler) .Subscribe(() => CheckForUpdatesCommand.Execute(null), Log.HandleException) .AddTo(Anchors); ApplyUpdate = CommandWrapper.Create( ApplyUpdateCommandExecuted, this.updaterModel.WhenAnyValue(x => x.LatestVersion).ObserveOn(uiScheduler).Select(x => x != null)); }
public OverlayAuraViewModel( OverlayAuraProperties initialProperties, [NotNull] IFactory <IPropertyEditorViewModel> propertiesEditorFactory, [NotNull] IFactory <IOverlayAuraModel> auraModelFactory) { this.auraModelFactory = auraModelFactory; loadedModelAnchors.AddTo(Anchors); RenameCommand = new DelegateCommand <string>(RenameCommandExecuted); this.RaiseWhenSourceValue(x => x.TabName, tabName, x => x.Value).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.DefaultTabName, tabName, x => x.DefaultValue).AddTo(Anchors); GeneralEditor = propertiesEditorFactory.Create(); Properties = initialProperties; IsEnabled = properties.IsEnabled; Id = properties.Id; tabName.SetValue(properties.Name); tabName.SetDefaultValue(properties.Name); this.WhenAnyValue(x => x.IsEnabled) .Subscribe(() => Model = ReloadModel()) .AddTo(Anchors); EnableCommand = CommandWrapper.Create(() => IsEnabled = true); }
public MessageBoxViewModel( [NotNull] IClipboardManager clipboardManager ) { CloseMessageBoxCommand = CommandWrapper.Create(() => IsOpen = false); CopyAllCommand = CommandWrapper.Create(() => clipboardManager.SetText(Content)); }
public ExceptionDialog() { activeWindowAnchors.AddTo(Anchors); this.RaiseWhenSourceValue(x => x.Title, this, x => x.Config).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.AppName, this, x => x.AppName).AddTo(Anchors); CloseCommand = CommandWrapper.Create(() => activeWindowAnchors.Disposable = null); }
public void TestSerializeCommandWrapped() { var c = new C { X = 1 }; var w = CommandWrapper.Create(c); var json = w.ToJson(); var expected = "{ 'X' : 1 }".Replace("'", "\""); Assert.Equal(expected, json); }
public PropertyEditorViewModel( [NotNull] IAuraRepository auraRepository) { this.auraRepository = auraRepository; activeValueEditorAnchors.AddTo(Anchors); closeCommand = CommandWrapper.Create(CloseCommandExecuted, CloseCommandCanExecute); this.WhenAnyValue(x => x.Value) .Subscribe(Reinitialize) .AddTo(Anchors); }
public PrismModuleStatusViewModel( IModuleCatalog moduleCatalog, IModuleManager moduleManager) { IsVisible = AppArguments.Instance.IsDebugMode; this.moduleManager = moduleManager; moduleList .Connect() .Bind(out var modules) .Subscribe() .AddTo(Anchors); Modules = modules; Observable.FromEventPattern<EventHandler<LoadModuleCompletedEventArgs>, LoadModuleCompletedEventArgs>( h => moduleManager.LoadModuleCompleted += h, h => moduleManager.LoadModuleCompleted -= h) .StartWithDefault() .Select(() => moduleCatalog.Modules.Select(x => new PrismModuleStatus(x)).ToArray()) .DistinctUntilChanged() .Subscribe( items => { moduleList.Clear(); moduleList.AddRange(items); }) .AddTo(Anchors); allModulesLoaded = modules.ToObservableChangeSet() .Select(() => modules.Any() && modules.All(x => x.IsLoaded)) .ToPropertyHelper(this, x => x.AllModulesLoaded) .AddTo(Anchors); LoadModuleCommand = CommandWrapper.Create<PrismModuleStatus>(LoadModuleCommandExecuted, LoadModuleCommandCanExecute); }
public MainWindowViewModel( [NotNull] IAppArguments appArguments, [NotNull] IFactory <IStartupManager, StartupManagerArgs> startupManagerFactory, [NotNull] IMicrophoneControllerEx microphoneController, [NotNull] IMicSwitchOverlayViewModel overlay, [NotNull] IAudioNotificationsManager audioNotificationsManager, [NotNull] IFactory <IAudioNotificationSelectorViewModel> audioSelectorFactory, [NotNull] IApplicationUpdaterViewModel appUpdater, [NotNull][Dependency(WellKnownWindows.MainWindow)] IWindowTracker mainWindowTracker, [NotNull] IConfigProvider <MicSwitchConfig> configProvider, [NotNull] IComplexHotkeyTracker hotkeyTracker, [NotNull] IMicrophoneProvider microphoneProvider, [NotNull] IImageProvider imageProvider, [NotNull] IViewController viewController, [NotNull][Dependency(WellKnownSchedulers.UI)] IScheduler uiScheduler) { var startupManagerArgs = new StartupManagerArgs { UniqueAppName = $"{appArguments.AppName}{(appArguments.IsDebugMode ? "-debug" : string.Empty)}", ExecutablePath = appUpdater.GetLatestExecutable().FullName, CommandLineArgs = appArguments.StartupArgs, AutostartFlag = appArguments.AutostartFlag }; this.startupManager = startupManagerFactory.Create(startupManagerArgs); this.appArguments = appArguments; this.microphoneController = microphoneController; ApplicationUpdater = appUpdater; this.mainWindowTracker = mainWindowTracker; this.configProvider = configProvider; this.imageProvider = imageProvider; this.RaiseWhenSourceValue(x => x.IsActive, mainWindowTracker, x => x.IsActive).AddTo(Anchors); AudioSelectorWhenMuted = audioSelectorFactory.Create(); AudioSelectorWhenUnmuted = audioSelectorFactory.Create(); Observable.Merge( AudioSelectorWhenMuted.ObservableForProperty(x => x.SelectedValue, skipInitial: true), AudioSelectorWhenUnmuted.ObservableForProperty(x => x.SelectedValue, skipInitial: true)) .Subscribe(() => this.RaisePropertyChanged(nameof(AudioNotification)), Log.HandleException) .AddTo(Anchors); configProvider.ListenTo(x => x.Notification) .ObserveOn(uiScheduler) .Subscribe(cfg => { Log.Debug($"Applying new notification configuration: {cfg.DumpToTextRaw()} (current: {AudioNotification.DumpToTextRaw()})"); AudioNotification = cfg; }, Log.HandleException) .AddTo(Anchors); configProvider.ListenTo(x => x.IsPushToTalkMode) .ObserveOn(uiScheduler) .Subscribe(x => { IsPushToTalkMode = x; if (isPushToTalkMode) { MuteMicrophoneCommand.Execute(true); } }, Log.HandleException) .AddTo(Anchors); configProvider.ListenTo(x => x.SuppressHotkey) .ObserveOn(uiScheduler) .Subscribe(x => SuppressHotkey = x, Log.HandleException) .AddTo(Anchors); Observable.Merge(configProvider.ListenTo(x => x.MicrophoneHotkey), configProvider.ListenTo(x => x.MicrophoneHotkeyAlt)) .Select(x => new { Hotkey = (HotkeyGesture) new HotkeyConverter().ConvertFrom(configProvider.ActualConfig.MicrophoneHotkey ?? string.Empty), HotkeyAlt = (HotkeyGesture) new HotkeyConverter().ConvertFrom(configProvider.ActualConfig.MicrophoneHotkeyAlt ?? string.Empty), }) .ObserveOn(uiScheduler) .Subscribe(cfg => { Log.Debug($"Setting new hotkeys configuration: {cfg.DumpToTextRaw()} (current: {hotkey}, alt: {hotkeyAlt})"); Hotkey = cfg.Hotkey; HotkeyAlt = cfg.HotkeyAlt; }, Log.HandleException) .AddTo(Anchors); Overlay = overlay; this.RaiseWhenSourceValue(x => x.RunAtLogin, startupManager, x => x.IsRegistered, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.MicrophoneVolume, microphoneController, x => x.VolumePercent, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.MicrophoneMuted, microphoneController, x => x.Mute, uiScheduler).AddTo(Anchors); ImageProvider = imageProvider; microphoneProvider.Microphones .ToObservableChangeSet() .ObserveOn(uiScheduler) .Bind(out var microphones) .Subscribe() .AddTo(Anchors); Microphones = microphones; this.ObservableForProperty(x => x.MicrophoneMuted, skipInitial: true) .DistinctUntilChanged() .Where(x => !MicrophoneLine.IsEmpty) .Skip(1) // skip initial setup .Subscribe(x => { var cfg = configProvider.ActualConfig.Notification; var notificationToPlay = x.Value ? cfg.On : cfg.Off; Log.Debug($"Playing notification {notificationToPlay} (cfg: {cfg.DumpToTextRaw()})"); audioNotificationsManager.PlayNotification(notificationToPlay); }, Log.HandleUiException) .AddTo(Anchors); this.WhenAnyValue(x => x.MicrophoneLine) .DistinctUntilChanged() .Subscribe(x => microphoneController.LineId = x, Log.HandleUiException) .AddTo(Anchors); Observable.Merge( configProvider.ListenTo(x => x.MicrophoneLineId).ToUnit(), Microphones.ToObservableChangeSet().ToUnit()) .Select(_ => configProvider.ActualConfig.MicrophoneLineId) .ObserveOn(uiScheduler) .Subscribe(configLineId => { Log.Debug($"Microphone line configuration changed, lineId: {configLineId}, known lines: {Microphones.DumpToTextRaw()}"); var micLine = Microphones.FirstOrDefault(line => line.Equals(configLineId)); if (micLine.IsEmpty) { Log.Debug($"Selecting first one of available microphone lines, known lines: {Microphones.DumpToTextRaw()}"); micLine = Microphones.FirstOrDefault(); } MicrophoneLine = micLine; MuteMicrophoneCommand.ResetError(); }, Log.HandleUiException) .AddTo(Anchors); hotkeyTracker .WhenAnyValue(x => x.IsActive) .ObserveOn(uiScheduler) .Subscribe(async isActive => { if (isPushToTalkMode) { await MuteMicrophoneCommandExecuted(!isActive); } else { await MuteMicrophoneCommandExecuted(!MicrophoneMuted); } }, Log.HandleUiException) .AddTo(Anchors); ToggleOverlayLockCommand = CommandWrapper.Create( () => { if (overlay.IsLocked && overlay.UnlockWindowCommand.CanExecute(null)) { overlay.UnlockWindowCommand.Execute(null); } else if (!overlay.IsLocked && overlay.LockWindowCommand.CanExecute(null)) { overlay.LockWindowCommand.Execute(null); } }); ExitAppCommand = CommandWrapper.Create( () => { Log.Debug("Closing application"); configProvider.Save(configProvider.ActualConfig); Application.Current.Shutdown(); }); this.WhenAnyValue(x => x.WindowState) .Subscribe(x => ShowInTaskbar = x != WindowState.Minimized, Log.HandleUiException) .AddTo(Anchors); ShowAppCommand = CommandWrapper.Create( () => { if (Visibility != Visibility.Visible) { viewController.Show(); } else { viewController.Hide(); } }); OpenAppDataDirectoryCommand = CommandWrapper.Create(OpenAppDataDirectory); ResetOverlayPositionCommand = CommandWrapper.Create(ResetOverlayPositionCommandExecuted); RunAtLoginToggleCommand = CommandWrapper.Create <bool>(RunAtLoginCommandExecuted); MuteMicrophoneCommand = CommandWrapper.Create <bool>(MuteMicrophoneCommandExecuted); SelectMicrophoneIconCommand = CommandWrapper.Create(SelectMicrophoneIconCommandExecuted); SelectMutedMicrophoneIconCommand = CommandWrapper.Create(SelectMutedMicrophoneIconCommandExecuted); ResetMicrophoneIconsCommand = CommandWrapper.Create(ResetMicrophoneIconsCommandExecuted); var executingAssemblyName = Assembly.GetExecutingAssembly().GetName(); Title = $"{(appArguments.IsDebugMode ? "[D]" : "")} {executingAssemblyName.Name} v{executingAssemblyName.Version}"; WindowState = WindowState.Minimized; viewController .WhenLoaded .Take(1) .Select(() => configProvider.ListenTo(y => y.StartMinimized)) .Switch() .Take(1) .ObserveOn(uiScheduler) .Subscribe( x => { if (x) { Log.Debug($"StartMinimized option is active - minimizing window, current state: {WindowState}"); StartMinimized = true; viewController.Hide(); } else { Log.Debug($"StartMinimized option is not active - showing window as Normal, current state: {WindowState}"); StartMinimized = false; viewController.Show(); } }, Log.HandleUiException) .AddTo(Anchors); // config processing Observable.Merge( this.ObservableForProperty(x => x.MicrophoneLine, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.IsPushToTalkMode, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.AudioNotification, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.HotkeyAlt, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.Hotkey, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.SuppressHotkey, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.StartMinimized, skipInitial: true).ToUnit()) .Throttle(ConfigThrottlingTimeout) .ObserveOn(uiScheduler) .Subscribe(() => { var config = configProvider.ActualConfig.CloneJson(); config.IsPushToTalkMode = IsPushToTalkMode; config.MicrophoneHotkey = (Hotkey ?? new HotkeyGesture()).ToString(); config.MicrophoneHotkeyAlt = (HotkeyAlt ?? new HotkeyGesture()).ToString(); config.MicrophoneLineId = MicrophoneLine; config.Notification = AudioNotification; config.SuppressHotkey = SuppressHotkey; config.StartMinimized = StartMinimized; configProvider.Save(config); }, Log.HandleUiException) .AddTo(Anchors); }
public MainWindowViewModel( IAppArguments appArguments, IApplicationAccessor applicationAccessor, IFactory <IStartupManager, StartupManagerArgs> startupManagerFactory, IMicSwitchOverlayViewModel overlay, IMicrophoneControllerViewModel microphoneControllerViewModel, IOverlayWindowController overlayWindowController, IWaveOutDeviceSelectorViewModel waveOutDeviceSelector, IAudioNotificationsManager audioNotificationsManager, IFactory <IAudioNotificationSelectorViewModel> audioSelectorFactory, IApplicationUpdaterViewModel appUpdater, [Dependency(WellKnownWindows.MainWindow)] IWindowTracker mainWindowTracker, IConfigProvider <MicSwitchConfig> configProvider, IConfigProvider <MicSwitchOverlayConfig> overlayConfigProvider, IImageProvider imageProvider, IErrorMonitorViewModel errorMonitor, IAudioNotificationsManager notificationsManager, IWindowViewController viewController, [Dependency(WellKnownSchedulers.UI)] IScheduler uiScheduler) { Title = $"{(appArguments.IsDebugMode ? "[D]" : "")} {appArguments.AppName} v{appArguments.Version}"; this.appArguments = appArguments; this.applicationAccessor = applicationAccessor; this.MicrophoneController = microphoneControllerViewModel.AddTo(Anchors); this.mainWindowTracker = mainWindowTracker; this.configProvider = configProvider; this.overlayConfigProvider = overlayConfigProvider; this.notificationsManager = notificationsManager; this.viewController = viewController; ApplicationUpdater = appUpdater.AddTo(Anchors); WaveOutDeviceSelector = waveOutDeviceSelector; ImageProvider = imageProvider; ErrorMonitor = errorMonitor; AudioSelectorWhenMuted = audioSelectorFactory.Create().AddTo(Anchors); AudioSelectorWhenUnmuted = audioSelectorFactory.Create().AddTo(Anchors); WindowState = WindowState.Minimized; Overlay = overlay.AddTo(Anchors); try { var startupManagerArgs = new StartupManagerArgs { UniqueAppName = $"{appArguments.AppName}{(appArguments.IsDebugMode ? "-debug" : string.Empty)}", ExecutablePath = appUpdater.LauncherExecutable.FullName, CommandLineArgs = appArguments.StartupArgs, AutostartFlag = appArguments.AutostartFlag }; this.startupManager = startupManagerFactory.Create(startupManagerArgs); RunAtLoginToggleCommand = CommandWrapper.Create <bool>(RunAtLoginCommandExecuted, Observable.Return(startupManager?.IsReady ?? false)); } catch (Exception e) { Log.Warn("Failed to initialize startup manager", e); } this.RaiseWhenSourceValue(x => x.IsActive, mainWindowTracker, x => x.IsActive, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.RunAtLogin, startupManager, x => x.IsRegistered, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.ShowOverlaySettings, Overlay, x => x.OverlayVisibilityMode).AddTo(Anchors); audioNotificationSource = Observable.Merge( AudioSelectorWhenMuted.ObservableForProperty(x => x.SelectedValue, skipInitial: true), AudioSelectorWhenUnmuted.ObservableForProperty(x => x.SelectedValue, skipInitial: true)) .Select(x => new TwoStateNotification { On = AudioSelectorWhenUnmuted.SelectedValue, Off = AudioSelectorWhenMuted.SelectedValue }) .ToProperty(this, x => x.AudioNotification) .AddTo(Anchors); this.WhenAnyValue(x => x.AudioNotificationVolume) .Subscribe(x => { AudioSelectorWhenUnmuted.Volume = AudioSelectorWhenMuted.Volume = x; }) .AddTo(Anchors); MicrophoneController.ObservableForProperty(x => x.MicrophoneMuted, skipInitial: true) .DistinctUntilChanged() .Where(x => !MicrophoneController.MicrophoneLine.IsEmpty) .Select(isMuted => (isMuted.Value ? AudioNotification.Off : AudioNotification.On) ?? default(AudioNotificationType).ToString()) .Where(notificationToPlay => !string.IsNullOrEmpty(notificationToPlay)) .Select(notificationToPlay => Observable.FromAsync(async token => { Log.Debug($"Playing notification {notificationToPlay}, volume: {audioNotificationVolume}"); try { await audioNotificationsManager.PlayNotification(notificationToPlay, audioNotificationVolume, waveOutDeviceSelector.SelectedItem, token); Log.Debug($"Played notification {notificationToPlay}"); } catch (Exception ex) { Log.Debug($"Failed to play notification {notificationToPlay}", ex); } })) .Switch() .SubscribeToErrors(Log.HandleUiException) .AddTo(Anchors); this.WhenAnyValue(x => x.WindowState) .SubscribeSafe(x => ShowInTaskbar = x != WindowState.Minimized, Log.HandleUiException) .AddTo(Anchors); viewController .WhenClosing .SubscribeSafe(x => HandleWindowClosing(viewController, x), Log.HandleUiException) .AddTo(Anchors); ToggleOverlayLockCommand = CommandWrapper.Create(ToggleOverlayCommandExecuted); ExitAppCommand = CommandWrapper.Create(ExitAppCommandExecuted); ShowAppCommand = CommandWrapper.Create(ShowAppCommandExecuted); OpenAppDataDirectoryCommand = CommandWrapper.Create(OpenAppDataDirectory); ResetOverlayPositionCommand = CommandWrapper.Create(ResetOverlayPositionCommandExecuted); RunAtLoginToggleCommand = CommandWrapper.Create <bool>(RunAtLoginCommandExecuted, startupManager.WhenAnyValue(x => x.IsReady)); SelectMicrophoneIconCommand = CommandWrapper.Create(SelectMicrophoneIconCommandExecuted); SelectMutedMicrophoneIconCommand = CommandWrapper.Create(SelectMutedMicrophoneIconCommandExecuted); ResetMicrophoneIconsCommand = CommandWrapper.Create(ResetMicrophoneIconsCommandExecuted); AddSoundCommand = CommandWrapper.Create(AddSoundCommandExecuted); Observable.Merge(configProvider.ListenTo(x => x.Notifications).ToUnit(), configProvider.ListenTo(x => x.NotificationVolume).ToUnit()) .Select(_ => new { configProvider.ActualConfig.Notifications, configProvider.ActualConfig.NotificationVolume }) .ObserveOn(uiScheduler) .SubscribeSafe(cfg => { Log.Debug($"Applying new notification configuration: {cfg.DumpToTextRaw()} (current: {AudioNotification.DumpToTextRaw()}, volume: {AudioNotificationVolume})"); AudioSelectorWhenMuted.SelectedValue = cfg.Notifications.Off; AudioSelectorWhenUnmuted.SelectedValue = cfg.Notifications.On; AudioNotificationVolume = cfg.NotificationVolume; }, Log.HandleException) .AddTo(Anchors); configProvider.ListenTo(x => x.MinimizeOnClose) .ObserveOn(uiScheduler) .SubscribeSafe(x => MinimizeOnClose = x, Log.HandleException) .AddTo(Anchors); configProvider.ListenTo(x => x.OutputDeviceId) .ObserveOn(uiScheduler) .SubscribeSafe(x => WaveOutDeviceSelector.SelectById(x), Log.HandleException) .AddTo(Anchors); viewController .WhenLoaded .Take(1) .Select(_ => configProvider.ListenTo(y => y.StartMinimized)) .Switch() .Take(1) .ObserveOn(uiScheduler) .SubscribeSafe( x => { if (x) { Log.Debug($"StartMinimized option is active - minimizing window, current state: {WindowState}"); StartMinimized = true; viewController.Hide(); } else { Log.Debug($"StartMinimized option is not active - showing window as Normal, current state: {WindowState}"); StartMinimized = false; viewController.Show(); } }, Log.HandleUiException) .AddTo(Anchors); Observable.Merge( microphoneControllerViewModel.ObservableForProperty(x => x.MuteMode, skipInitial: true).ToUnit(), waveOutDeviceSelector.ObservableForProperty(x => x.SelectedItem, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.AudioNotification, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.MinimizeOnClose, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.Width, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.Height, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.Top, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.Left, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.AudioNotificationVolume, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.StartMinimized, skipInitial: true).ToUnit()) .Throttle(ConfigThrottlingTimeout) .ObserveOn(uiScheduler) .SubscribeSafe(() => { var config = configProvider.ActualConfig.CloneJson(); config.Notifications = AudioNotification; config.NotificationVolume = AudioNotificationVolume; config.StartMinimized = StartMinimized; config.MinimizeOnClose = MinimizeOnClose; config.OutputDeviceId = waveOutDeviceSelector.SelectedItem?.Id; config.MainWindowBounds = new Rect(Left, Top, Width, Height); configProvider.Save(config); }, Log.HandleUiException) .AddTo(Anchors); viewController.WhenLoaded .SubscribeSafe(() => { Log.Debug($"Main window loaded - loading overlay, current process({CurrentProcess.ProcessName} 0x{CurrentProcess.Id:x8}) main window: {CurrentProcess.MainWindowHandle} ({CurrentProcess.MainWindowTitle})"); overlayWindowController.RegisterChild(Overlay).AddTo(Anchors); Log.Debug("Overlay loaded successfully"); }, Log.HandleUiException) .AddTo(Anchors); configProvider.ListenTo(x => x.MainWindowBounds) .WithPrevious() .ObserveOn(uiScheduler) .SubscribeSafe(x => { Log.Debug($"Main window config bounds updated: {x}"); Rect bounds; if (x.Current == null) { var monitorBounds = UnsafeNative.GetMonitorBounds(Rectangle.Empty).ScaleToWpf(); var monitorCenter = monitorBounds.Center(); bounds = new Rect( monitorCenter.X - DefaultSize.Width / 2f, monitorCenter.Y - DefaultSize.Height / 2f, DefaultSize.Width, DefaultSize.Height); } else { bounds = x.Current.Value; } Left = bounds.Left; Top = bounds.Top; Width = bounds.Width; Height = bounds.Height; }, Log.HandleUiException) .AddTo(Anchors); var theme = Theme.Create( Theme.Light, primary: SwatchHelper.Lookup[(MaterialDesignColor)PrimaryColor.BlueGrey], accent: SwatchHelper.Lookup[(MaterialDesignColor)SecondaryColor.LightBlue]); var paletteHelper = new PaletteHelper(); paletteHelper.SetTheme(theme); }
public MicrophoneControllerViewModel( IMicrophoneControllerEx microphoneController, IMicrophoneProvider microphoneProvider, IComplexHotkeyTracker hotkeyTracker, IFactory <IHotkeyTracker> hotkeyTrackerFactory, IFactory <IHotkeyEditorViewModel> hotkeyEditorFactory, IConfigProvider <MicSwitchConfig> configProvider, IConfigProvider <MicSwitchHotkeyConfig> hotkeyConfigProvider, [Dependency(WellKnownSchedulers.UI)] IScheduler uiScheduler) { microphoneProvider.Microphones .ToObservableChangeSet() .ObserveOn(uiScheduler) .Bind(out var microphones) .SubscribeToErrors(Log.HandleUiException) .AddTo(Anchors); Microphones = microphones; this.microphoneController = microphoneController; this.hotkeyTrackerFactory = hotkeyTrackerFactory; this.hotkeyEditorFactory = hotkeyEditorFactory; this.hotkeyConfigProvider = hotkeyConfigProvider; this.uiScheduler = uiScheduler; MuteMicrophoneCommand = CommandWrapper.Create <object>(MuteMicrophoneCommandExecuted); Hotkey = PrepareHotkey("Mute/Un-mute microphone", x => x.Hotkey, (config, hotkeyConfig) => config.Hotkey = hotkeyConfig); HotkeyToggle = PrepareHotkey("Toggle microphone state", x => x.HotkeyForToggle, (config, hotkeyConfig) => config.HotkeyForToggle = hotkeyConfig); HotkeyMute = PrepareHotkey("Mute microphone", x => x.HotkeyForMute, (config, hotkeyConfig) => config.HotkeyForMute = hotkeyConfig); HotkeyUnmute = PrepareHotkey("Un-mute microphone", x => x.HotkeyForUnmute, (config, hotkeyConfig) => config.HotkeyForUnmute = hotkeyConfig); HotkeyPushToMute = PrepareHotkey("Push-To-Mute", x => x.HotkeyForPushToMute, (config, hotkeyConfig) => config.HotkeyForPushToMute = hotkeyConfig); HotkeyPushToTalk = PrepareHotkey("Push-To-Talk", x => x.HotkeyForPushToTalk, (config, hotkeyConfig) => config.HotkeyForPushToTalk = hotkeyConfig); PrepareTracker(HotkeyMode.Click, HotkeyToggle) .ObservableForProperty(x => x.IsActive, skipInitial: true) .SubscribeSafe(x => { Log.Debug($"[{x.Sender}] Toggling microphone state: {microphoneController}"); microphoneController.Mute = !microphoneController.Mute; }, Log.HandleUiException) .AddTo(Anchors); PrepareTracker(HotkeyMode.Hold, HotkeyMute) .ObservableForProperty(x => x.IsActive, skipInitial: true) .Where(x => x.Value) .SubscribeSafe(x => { Log.Debug($"[{x.Sender}] Muting microphone: {microphoneController}"); microphoneController.Mute = true; }, Log.HandleUiException) .AddTo(Anchors); PrepareTracker(HotkeyMode.Hold, HotkeyUnmute) .ObservableForProperty(x => x.IsActive, skipInitial: true) .Where(x => x.Value) .SubscribeSafe(x => { Log.Debug($"[{x.Sender}] Un-muting microphone: {microphoneController}"); microphoneController.Mute = false; }, Log.HandleUiException) .AddTo(Anchors); PrepareTracker(HotkeyMode.Hold, HotkeyPushToTalk) .ObservableForProperty(x => x.IsActive, skipInitial: true) .SubscribeSafe(x => { Log.Debug($"[{x.Sender}] Processing push-to-talk hotkey for microphone: {microphoneController}"); microphoneController.Mute = !x.Value; }, Log.HandleUiException) .AddTo(Anchors); PrepareTracker(HotkeyMode.Hold, HotkeyPushToMute) .ObservableForProperty(x => x.IsActive, skipInitial: true) .SubscribeSafe(x => { Log.Debug($"[{x.Sender}] Processing push-to-mute hotkey for microphone: {microphoneController}"); microphoneController.Mute = x.Value; }, Log.HandleUiException) .AddTo(Anchors); this.RaiseWhenSourceValue(x => x.MicrophoneVolume, microphoneController, x => x.VolumePercent, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.MicrophoneMuted, microphoneController, x => x.Mute, uiScheduler).AddTo(Anchors); this.WhenAnyValue(x => x.MicrophoneLine) .DistinctUntilChanged() .SubscribeSafe(x => microphoneController.LineId = x, Log.HandleUiException) .AddTo(Anchors); hotkeyConfigProvider.ListenTo(x => x.MuteMode) .ObserveOn(uiScheduler) .Subscribe(x => { Log.Debug($"Mute mode loaded from config: {x}"); MuteMode = x; }) .AddTo(Anchors); hotkeyConfigProvider.ListenTo(x => x.EnableAdvancedHotkeys) .ObserveOn(uiScheduler) .Subscribe(x => EnableAdditionalHotkeys = x) .AddTo(Anchors); hotkeyConfigProvider.ListenTo(x => x.InitialMicrophoneState) .ObserveOn(uiScheduler) .Subscribe(x => InitialMicrophoneState = x) .AddTo(Anchors); configProvider.ListenTo(x => x.VolumeControlEnabled) .ObserveOn(uiScheduler) .SubscribeSafe(x => MicrophoneVolumeControlEnabled = x, Log.HandleException) .AddTo(Anchors); Observable.Merge( configProvider.ListenTo(x => x.MicrophoneLineId).ToUnit(), Microphones.ToObservableChangeSet().ToUnit()) .Select(_ => configProvider.ActualConfig.MicrophoneLineId) .ObserveOn(uiScheduler) .SubscribeSafe(configLineId => { Log.Debug($"Microphone line configuration changed, lineId: {configLineId}, known lines: {Microphones.DumpToTextRaw()}"); var micLine = Microphones.FirstOrDefault(line => line.Equals(configLineId)); if (micLine.IsEmpty) { Log.Debug($"Selecting first one of available microphone lines, known lines: {Microphones.DumpToTextRaw()}"); micLine = Microphones.FirstOrDefault(); } MicrophoneLine = micLine; MuteMicrophoneCommand.ResetError(); }, Log.HandleUiException) .AddTo(Anchors); this.WhenAnyValue(x => x.MuteMode, x => x.InitialMicrophoneState) .ObserveOn(uiScheduler) .SubscribeSafe(_ => { Log.Debug($"Processing muteMode: {muteMode}, {microphoneController}.Mute: {microphoneController.Mute}"); switch (muteMode) { case MuteMode.PushToTalk: Log.Debug($"{muteMode} mute mode is enabled, un-muting microphone"); microphoneController.Mute = true; break; case MuteMode.PushToMute: microphoneController.Mute = false; Log.Debug($"{muteMode} mute mode is enabled, muting microphone"); break; case MuteMode.ToggleMute when initialMicrophoneState == MicrophoneState.Mute: Log.Debug($"{muteMode} enabled, muting microphone"); microphoneController.Mute = true; break; case MuteMode.ToggleMute when initialMicrophoneState == MicrophoneState.Unmute: Log.Debug($"{muteMode} enabled, un-muting microphone"); microphoneController.Mute = false; break; default: Log.Debug($"{muteMode} enabled, action is not needed"); break; } }, Log.HandleUiException) .AddTo(Anchors); hotkeyTracker .WhenAnyValue(x => x.IsActive) .Skip(1) .ObserveOn(uiScheduler) .SubscribeSafe(async isActive => { Log.Debug($"Handling hotkey press (isActive: {isActive}), mute mode: {muteMode}"); switch (muteMode) { case MuteMode.PushToTalk: microphoneController.Mute = !isActive; break; case MuteMode.PushToMute: microphoneController.Mute = isActive; break; case MuteMode.ToggleMute: microphoneController.Mute = !microphoneController.Mute; break; default: throw new ArgumentOutOfRangeException(nameof(muteMode), muteMode, @"Unsupported mute mode"); } }, Log.HandleUiException) .AddTo(Anchors); Observable.Merge( this.ObservableForProperty(x => x.MuteMode, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.EnableAdditionalHotkeys, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.InitialMicrophoneState, skipInitial: true).ToUnit(), Hotkey.ObservableForProperty(x => x.Properties, skipInitial: true).ToUnit()) .Throttle(ConfigThrottlingTimeout) .ObserveOn(uiScheduler) .SubscribeSafe(() => { var hotkeyConfig = hotkeyConfigProvider.ActualConfig.CloneJson(); hotkeyConfig.Hotkey = Hotkey.Properties; hotkeyConfig.MuteMode = muteMode; hotkeyConfig.EnableAdvancedHotkeys = enableAdvancedHotkeys; hotkeyConfig.InitialMicrophoneState = initialMicrophoneState; hotkeyConfigProvider.Save(hotkeyConfig); }, Log.HandleUiException) .AddTo(Anchors); Observable.Merge( this.ObservableForProperty(x => x.MicrophoneLine, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.MicrophoneVolumeControlEnabled, skipInitial: true).ToUnit()) .Throttle(ConfigThrottlingTimeout) .ObserveOn(uiScheduler) .SubscribeSafe(() => { var config = configProvider.ActualConfig.CloneJson(); config.MicrophoneLineId = microphoneLine; config.VolumeControlEnabled = microphoneVolumeControlEnabled; configProvider.Save(config); }, Log.HandleUiException) .AddTo(Anchors); }
public MicSwitchOverlayViewModel( IOverlayWindowController overlayWindowController, IMicrophoneControllerEx microphoneController, IConfigProvider<MicSwitchOverlayConfig> configProvider, IImageProvider imageProvider, [Dependency(WellKnownSchedulers.UI)] IScheduler uiScheduler) { this.overlayWindowController = overlayWindowController; this.microphoneController = microphoneController; this.configProvider = configProvider; this.imageProvider = imageProvider; OverlayMode = OverlayMode.Transparent; MinSize = new Size(40, 40); MaxSize = new Size(300, 300); DefaultSize = new Size(120, 120); SizeToContent = SizeToContent.Manual; TargetAspectRatio = MinSize.Width / MinSize.Height; IsUnlockable = true; Title = "MicSwitch"; IsEnabled = true; this.WhenAnyValue(x => x.IsLocked) .SubscribeSafe(isLocked => OverlayMode = isLocked ? OverlayMode.Transparent : OverlayMode.Layered, Log.HandleUiException) .AddTo(Anchors); this.RaiseWhenSourceValue(x => x.IsEnabled, overlayWindowController, x => x.IsEnabled).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.Mute, microphoneController, x => x.Mute, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.MicrophoneImage, imageProvider, x => x.MicrophoneImage, uiScheduler).AddTo(Anchors); ToggleLockStateCommand = CommandWrapper.Create( () => { if (IsLocked && UnlockWindowCommand.CanExecute(null)) { UnlockWindowCommand.Execute(null); } else if (!IsLocked && LockWindowCommand.CanExecute(null)) { LockWindowCommand.Execute(null); } else { throw new ApplicationException($"Something went wrong - invalid Overlay Lock state: {new {IsLocked, IsUnlockable, CanUnlock = UnlockWindowCommand.CanExecute(null), CanLock = LockWindowCommand.CanExecute(null) }}"); } }); this.WhenAnyValue(x => x.IsEnabled) .Where(x => !IsEnabled && !IsLocked) .ObserveOn(uiScheduler) .SubscribeSafe(() => LockWindowCommand.Execute(null), Log.HandleUiException) .AddTo(Anchors); this.WhenAnyValue(x => x.OverlayWindow) .Where(x => x != null) .SubscribeSafe(x => x.LogWndProc("MicOverlay").AddTo(Anchors), Log.HandleUiException) .AddTo(Anchors); this.WhenAnyValue(x => x.OverlayVisibilityMode, x => x.Mute) .Select(_ => { return OverlayVisibilityMode switch { OverlayVisibilityMode.Always => true, OverlayVisibilityMode.Never => false, OverlayVisibilityMode.WhenMuted => Mute, OverlayVisibilityMode.WhenUnmuted => !Mute, _ => throw new ArgumentOutOfRangeException(nameof(overlayVisibilityMode), overlayVisibilityMode, "Unknown visibility mode") }; }) .DistinctUntilChanged() .ObserveOn(uiScheduler) .SubscribeSafe(x => IsEnabled = x, Log.HandleUiException) .AddTo(Anchors); WhenLoaded .Take(1) .SubscribeSafe(_ => { configProvider .WhenChanged .ObserveOn(uiScheduler) .SubscribeSafe(LoadConfig, Log.HandleUiException) .AddTo(Anchors); Observable.Merge( this.ObservableForProperty(x => x.NativeBounds, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.Opacity, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.OverlayVisibilityMode, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.IsEnabled, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.IsLocked, skipInitial: true).ToUnit()) .SkipUntil(WhenLoaded) .Throttle(ConfigThrottlingTimeout) .ObserveOn(uiScheduler) .SubscribeSafe(SaveConfig, Log.HandleUiException) .AddTo(Anchors); }, Log.HandleUiException) .AddTo(Anchors); }
public MainWindowViewModel( IAppArguments appArguments, IFactory <IStartupManager, StartupManagerArgs> startupManagerFactory, IMicSwitchOverlayViewModel overlay, IMicrophoneControllerViewModel microphoneControllerViewModel, IOverlayWindowController overlayWindowController, IAudioNotificationsManager audioNotificationsManager, IFactory <IAudioNotificationSelectorViewModel> audioSelectorFactory, IApplicationUpdaterViewModel appUpdater, [Dependency(WellKnownWindows.MainWindow)] IWindowTracker mainWindowTracker, IConfigProvider <MicSwitchConfig> configProvider, IConfigProvider <MicSwitchOverlayConfig> overlayConfigProvider, IImageProvider imageProvider, IAudioNotificationsManager notificationsManager, IWindowViewController viewController, [Dependency(WellKnownSchedulers.UI)] IScheduler uiScheduler) { Title = $"{(appArguments.IsDebugMode ? "[D]" : "")} {appArguments.AppName} v{appArguments.Version}"; this.appArguments = appArguments; this.MicrophoneController = microphoneControllerViewModel.AddTo(Anchors); this.mainWindowTracker = mainWindowTracker; this.configProvider = configProvider; this.overlayConfigProvider = overlayConfigProvider; this.notificationsManager = notificationsManager; this.viewController = viewController; ApplicationUpdater = appUpdater.AddTo(Anchors); ImageProvider = imageProvider; AudioSelectorWhenMuted = audioSelectorFactory.Create().AddTo(Anchors); AudioSelectorWhenUnmuted = audioSelectorFactory.Create().AddTo(Anchors); WindowState = WindowState.Minimized; Overlay = overlay.AddTo(Anchors); var startupManagerArgs = new StartupManagerArgs { UniqueAppName = $"{appArguments.AppName}{(appArguments.IsDebugMode ? "-debug" : string.Empty)}", ExecutablePath = appUpdater.GetLatestExecutable().FullName, CommandLineArgs = appArguments.StartupArgs, AutostartFlag = appArguments.AutostartFlag }; this.startupManager = startupManagerFactory.Create(startupManagerArgs); this.RaiseWhenSourceValue(x => x.IsActive, mainWindowTracker, x => x.IsActive, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.RunAtLogin, startupManager, x => x.IsRegistered, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.ShowOverlaySettings, Overlay, x => x.OverlayVisibilityMode).AddTo(Anchors); audioNotificationSource = Observable.Merge( AudioSelectorWhenMuted.ObservableForProperty(x => x.SelectedValue, skipInitial: true), AudioSelectorWhenUnmuted.ObservableForProperty(x => x.SelectedValue, skipInitial: true)) .Select(x => new TwoStateNotification { On = AudioSelectorWhenUnmuted.SelectedValue, Off = AudioSelectorWhenMuted.SelectedValue }) .ToPropertyHelper(this, x => x.AudioNotification) .AddTo(Anchors); this.WhenAnyValue(x => x.AudioNotificationVolume) .Subscribe(x => { AudioSelectorWhenUnmuted.Volume = AudioSelectorWhenMuted.Volume = x; }) .AddTo(Anchors); MicrophoneController.ObservableForProperty(x => x.MicrophoneMuted, skipInitial: true) .DistinctUntilChanged() .Where(x => !MicrophoneController.MicrophoneLine.IsEmpty) .SubscribeSafe(x => { var notificationToPlay = (x.Value ? AudioNotification.On : AudioNotification.Off) ?? default(AudioNotificationType).ToString(); Log.Debug($"Playing notification {notificationToPlay} (cfg: {AudioNotification.DumpToTextRaw()})"); audioNotificationsManager.PlayNotification(notificationToPlay, audioNotificationVolume); }, Log.HandleUiException) .AddTo(Anchors); this.WhenAnyValue(x => x.WindowState) .SubscribeSafe(x => ShowInTaskbar = x != WindowState.Minimized, Log.HandleUiException) .AddTo(Anchors); viewController .WhenClosing .SubscribeSafe(x => HandleWindowClosing(viewController, x), Log.HandleUiException) .AddTo(Anchors); ToggleOverlayLockCommand = CommandWrapper.Create(ToggleOverlayCommandExecuted); ExitAppCommand = CommandWrapper.Create(ExitAppCommandExecuted); ShowAppCommand = CommandWrapper.Create(ShowAppCommandExecuted); OpenAppDataDirectoryCommand = CommandWrapper.Create(OpenAppDataDirectory); ResetOverlayPositionCommand = CommandWrapper.Create(ResetOverlayPositionCommandExecuted); RunAtLoginToggleCommand = CommandWrapper.Create <bool>(RunAtLoginCommandExecuted); SelectMicrophoneIconCommand = CommandWrapper.Create(SelectMicrophoneIconCommandExecuted); SelectMutedMicrophoneIconCommand = CommandWrapper.Create(SelectMutedMicrophoneIconCommandExecuted); ResetMicrophoneIconsCommand = CommandWrapper.Create(ResetMicrophoneIconsCommandExecuted); AddSoundCommand = CommandWrapper.Create(AddSoundCommandExecuted); Observable.Merge(configProvider.ListenTo(x => x.Notification).ToUnit(), configProvider.ListenTo(x => x.NotificationVolume).ToUnit()) .Select(_ => new { configProvider.ActualConfig.Notification, configProvider.ActualConfig.NotificationVolume }) .ObserveOn(uiScheduler) .SubscribeSafe(cfg => { Log.Debug($"Applying new notification configuration: {cfg.DumpToTextRaw()} (current: {AudioNotification.DumpToTextRaw()}, volume: {AudioNotificationVolume})"); AudioSelectorWhenMuted.SelectedValue = cfg.Notification.Off; AudioSelectorWhenUnmuted.SelectedValue = cfg.Notification.On; AudioNotificationVolume = cfg.NotificationVolume; }, Log.HandleException) .AddTo(Anchors); configProvider.ListenTo(x => x.MinimizeOnClose) .ObserveOn(uiScheduler) .SubscribeSafe(x => MinimizeOnClose = x, Log.HandleException) .AddTo(Anchors); viewController .WhenLoaded .Take(1) .Select(_ => configProvider.ListenTo(y => y.StartMinimized)) .Switch() .Take(1) .ObserveOn(uiScheduler) .SubscribeSafe( x => { if (x) { Log.Debug($"StartMinimized option is active - minimizing window, current state: {WindowState}"); StartMinimized = true; viewController.Hide(); } else { Log.Debug($"StartMinimized option is not active - showing window as Normal, current state: {WindowState}"); StartMinimized = false; viewController.Show(); } }, Log.HandleUiException) .AddTo(Anchors); configProvider.WhenChanged .Subscribe() .AddTo(Anchors); Observable.Merge( microphoneControllerViewModel.ObservableForProperty(x => x.MuteMode, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.AudioNotification, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.MinimizeOnClose, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.AudioNotificationVolume, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.StartMinimized, skipInitial: true).ToUnit()) .Throttle(ConfigThrottlingTimeout) .ObserveOn(uiScheduler) .SubscribeSafe(() => { var config = configProvider.ActualConfig.CloneJson(); config.Notification = AudioNotification; config.NotificationVolume = AudioNotificationVolume; config.StartMinimized = StartMinimized; config.MinimizeOnClose = MinimizeOnClose; configProvider.Save(config); }, Log.HandleUiException) .AddTo(Anchors); viewController.WhenLoaded .SubscribeSafe(() => { Log.Debug($"Main window loaded - loading overlay, current process({CurrentProcess.ProcessName} 0x{CurrentProcess.Id:x8}) main window: {CurrentProcess.MainWindowHandle} ({CurrentProcess.MainWindowTitle})"); overlayWindowController.RegisterChild(Overlay).AddTo(Anchors); Log.Debug("Overlay loaded successfully"); }, Log.HandleUiException) .AddTo(Anchors); var theme = Theme.Create( Theme.Light, primary: SwatchHelper.Lookup[(MaterialDesignColor)PrimaryColor.BlueGrey], accent: SwatchHelper.Lookup[(MaterialDesignColor)SecondaryColor.LightBlue]); var paletteHelper = new PaletteHelper(); paletteHelper.SetTheme(theme); }
public EyeOverlayViewModel( [NotNull] [Dependency(WellKnownWindows.AllWindows)] IWindowTracker mainWindowTracker, [NotNull] IOverlayWindowController overlayWindowController, [NotNull] IAuraModelController auraModelController, [NotNull] IWindowListProvider windowListProvider, [NotNull] ISelectionAdornerViewModel selectionAdorner, [NotNull] [Dependency(WellKnownSchedulers.UI)] IScheduler uiScheduler) { using var sw = new BenchmarkTimer("Initialization", Log, nameof(EyeOverlayViewModel)); SelectionAdorner = selectionAdorner.AddTo(Anchors); activeConfigEditorAnchors.AddTo(Anchors); this.mainWindowTracker = mainWindowTracker; this.overlayWindowController = overlayWindowController; this.auraModelController = auraModelController; this.windowListProvider = windowListProvider; MinSize = new Size(32, 32); SizeToContent = SizeToContent.Manual; Width = 400; Height = 400; Left = 200; Top = 200; IsUnlockable = true; Title = "EyeAuras"; EnableHeader = false; thumbnailOpacity.SetDefaultValue(DefaultThumbnailOpacity); ResetRegionCommandExecuted(); sw.Step("Basic properties initialized"); WhenLoaded .Take(1) .Subscribe(ApplyConfig) .AddTo(Anchors); sw.Step("WhenLoaded executed"); resetRegionCommand = CommandWrapper.Create(ResetRegionCommandExecuted, ResetRegionCommandCanExecute); selectRegionCommand = CommandWrapper.Create(SelectRegionCommandExecuted, SelectRegionCommandCanExecute); closeConfigEditorCommand = CommandWrapper.Create(CloseConfigEditorCommandExecuted); fitOverlayCommand = CommandWrapper.Create<double?>(FitOverlayCommandExecuted); setAttachedWindowCommand = CommandWrapper.Create<WindowHandle>(SetAttachedWindowCommandExecuted); setClickThroughCommand = CommandWrapper.Create<bool?>(SetClickThroughModeExecuted); DisableAuraCommand = CommandWrapper.Create(() => auraModelController.IsEnabled = false); CloseCommand = CommandWrapper.Create(CloseCommandExecuted, auraModelController.WhenAnyValue(x => x.CloseController).Select(CloseCommandCanExecute)); ToggleLockStateCommand = CommandWrapper.Create( () => { if (IsLocked && UnlockWindowCommand.CanExecute(null)) { UnlockWindowCommand.Execute(null); } else if (!IsLocked && LockWindowCommand.CanExecute(null)) { LockWindowCommand.Execute(null); } else { throw new ApplicationException($"Something went wrong - invalid Overlay Lock state: {new {IsLocked, IsUnlockable, CanUnlock = UnlockWindowCommand.CanExecute(null), CanLock = LockWindowCommand.CanExecute(null) }}"); } }); auraModelController.WhenAnyValue(x => x.Name) .Where(x => !string.IsNullOrEmpty(x)) .Subscribe(x => OverlayName = x) .AddTo(Anchors); this.RaiseWhenSourceValue(x => x.ActiveThumbnailOpacity, thumbnailOpacity, x => x.Value).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.ThumbnailOpacity, this, x => x.ActiveThumbnailOpacity).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.SourceBounds, Region, x => x.Bounds).AddTo(Anchors); isInEditMode = Observable.Merge( this.WhenAnyProperty(x => x.IsInSelectMode, x => x.IsLocked)) .Select(change => IsInSelectMode || !IsLocked) .ToPropertyHelper(this, x => x.IsInEditMode, uiScheduler) .AddTo(Anchors); this.WhenAnyValue(x => x.IsLocked) .Where(x => x && isInSelectMode) .Subscribe(() => IsInSelectMode = false) .AddTo(Anchors); aspectRatio = this.WhenAnyProperty(x => x.Bounds, x => x.ViewModelLocation) .Select(change => Width >= 0 && Height >= 0 ? Width / Height : double.PositiveInfinity) .ToPropertyHelper(this, x => x.AspectRatio, uiScheduler) .AddTo(Anchors); sw.ResetStep(); configEditorSupplier = new Lazy<OverlayConfigEditor>(() => CreateConfigEditor(this)); sw.Step("Initialized Config editor"); }
public MainWindowViewModel( [NotNull] IFactory <IOverlayAuraViewModel, OverlayAuraProperties> auraViewModelFactory, [NotNull] IApplicationUpdaterViewModel appUpdater, [NotNull] IClipboardManager clipboardManager, [NotNull] IConfigSerializer configSerializer, [NotNull] IGenericSettingsViewModel settingsViewModel, [NotNull] IMessageBoxViewModel messageBox, [NotNull] IHotkeyConverter hotkeyConverter, [NotNull] IFactory <HotkeyIsActiveTrigger> hotkeyTriggerFactory, [NotNull] IConfigProvider <EyeAurasConfig> configProvider, [NotNull] IConfigProvider rootConfigProvider, [NotNull] IPrismModuleStatusViewModel moduleStatus, [NotNull] IMainWindowBlocksProvider mainWindowBlocksProvider, [NotNull] IFactory <IRegionSelectorService> regionSelectorServiceFactory, [NotNull] ISharedContext sharedContext, [NotNull] IComparisonService comparisonService, [NotNull][Dependency(WellKnownSchedulers.UI)] IScheduler uiScheduler) { using var unused = new OperationTimer(elapsed => Log.Debug($"{nameof(MainWindowViewModel)} initialization took {elapsed.TotalMilliseconds:F0}ms")); TabsList = new ReadOnlyObservableCollection <IEyeAuraViewModel>(sharedContext.AuraList); ModuleStatus = moduleStatus.AddTo(Anchors); var executingAssemblyName = Assembly.GetExecutingAssembly().GetName(); Title = $"{(AppArguments.Instance.IsDebugMode ? "[D]" : "")} {executingAssemblyName.Name} v{executingAssemblyName.Version}"; Disposable.Create(() => Log.Info("Disposing Main view model")).AddTo(Anchors); ApplicationUpdater = appUpdater.AddTo(Anchors); MessageBox = messageBox.AddTo(Anchors); Settings = settingsViewModel.AddTo(Anchors); StatusBarItems = mainWindowBlocksProvider.StatusBarItems; this.auraViewModelFactory = auraViewModelFactory; this.configProvider = configProvider; this.sharedContext = sharedContext; this.regionSelectorService = regionSelectorServiceFactory.Create(); this.clipboardManager = clipboardManager; this.configSerializer = configSerializer; this.hotkeyConverter = hotkeyConverter; this.hotkeyTriggerFactory = hotkeyTriggerFactory; CreateNewTabCommand = CommandWrapper.Create(() => AddNewCommandExecuted(OverlayAuraProperties.Default)); CloseTabCommand = CommandWrapper .Create <IOverlayAuraViewModel>(CloseTabCommandExecuted, CloseTabCommandCanExecute) .RaiseCanExecuteChangedWhen(this.WhenAnyProperty(x => x.SelectedTab)); DuplicateTabCommand = CommandWrapper .Create(DuplicateTabCommandExecuted, DuplicateTabCommandCanExecute) .RaiseCanExecuteChangedWhen(this.WhenAnyProperty(x => x.SelectedTab)); CopyTabToClipboardCommand = CommandWrapper .Create(CopyTabToClipboardExecuted, CopyTabToClipboardCommandCanExecute) .RaiseCanExecuteChangedWhen(this.WhenAnyProperty(x => x.SelectedTab).Select(x => x)); PasteTabCommand = CommandWrapper.Create(PasteTabCommandExecuted); UndoCloseTabCommand = CommandWrapper.Create(UndoCloseTabCommandExecuted, UndoCloseTabCommandCanExecute); OpenAppDataDirectoryCommand = CommandWrapper.Create(OpenAppDataDirectory); SelectRegionCommand = CommandWrapper.Create(SelectRegionCommandExecuted); Observable .FromEventPattern <OrderChangedEventArgs>(h => positionMonitor.OrderChanged += h, h => positionMonitor.OrderChanged -= h) .Select(x => x.EventArgs) .Subscribe(OnTabOrderChanged, Log.HandleUiException) .AddTo(Anchors); sharedContext .AuraList .ToObservableChangeSet() .ObserveOn(uiScheduler) .OnItemAdded(x => SelectedTab = x) .Subscribe() .AddTo(Anchors); this.WhenAnyValue(x => x.SelectedTab) .Subscribe(x => Log.Debug($"Selected tab: {x}")) .AddTo(Anchors); LoadConfig(); rootConfigProvider.Save(); if (sharedContext.AuraList.Count == 0) { CreateNewTabCommand.Execute(null); } configUpdateSubject .Sample(ConfigSaveSamplingTimeout) .Subscribe(SaveConfig, Log.HandleException) .AddTo(Anchors); Observable.Merge( this.WhenAnyProperty(x => x.Left, x => x.Top, x => x.Width, x => x.Height) .Sample(ConfigSaveSamplingTimeout) .Select(x => $"[{x.Sender}] Main window property change: {x.EventArgs.PropertyName}"), sharedContext.AuraList.ToObservableChangeSet() .Sample(ConfigSaveSamplingTimeout) .Select(x => "Tabs list change"), sharedContext.AuraList.ToObservableChangeSet() .WhenPropertyChanged(x => x.Properties) .Sample(ConfigSaveSamplingTimeout) .WithPrevious((prev, curr) => new { prev, curr }) .Select(x => new { x.curr.Sender, ComparisonResult = comparisonService.Compare(x.prev?.Value, x.curr.Value) }) .Where(x => !x.ComparisonResult.AreEqual) .Select(x => $"[{x.Sender.TabName}] Tab properties change: {x.ComparisonResult.DifferencesString}")) .Buffer(ConfigSaveSamplingTimeout) .Where(x => x.Count > 0) .Subscribe( reasons => { const int maxReasonsToOutput = 50; Log.Debug( $"Config Save reasons{(reasons.Count <= maxReasonsToOutput ? string.Empty : $"first {maxReasonsToOutput} of {reasons.Count} items")}:\r\n\t{reasons.Take(maxReasonsToOutput).DumpToTable()}"); configUpdateSubject.OnNext(Unit.Default); },
public WindowSelectorViewModel( [NotNull] IWindowListProvider windowListProvider, [NotNull][Dependency(WellKnownSchedulers.UI)] IScheduler uiScheduler) { WindowList = windowListProvider.WindowList; this.WhenAnyValue(x => x.WindowTitle) .WithPrevious((prev, curr) => new { prev, curr }) .DistinctUntilChanged() .Where(x => !string.IsNullOrEmpty(x.prev) && x.curr == null) .Subscribe(x => WindowTitle = x.prev) .AddTo(Anchors); windowListProvider.WindowList.ToObservableChangeSet() .ToUnit() .Merge(this.WhenAnyValue(x => x.TargetWindow).ToUnit()) .Throttle(ThrottlingPeriod) .ObserveOn(uiScheduler) .Subscribe(x => MatchingWindowList = BuildMatches(windowListProvider.WindowList)) .AddTo(Anchors); this.WhenAnyValue(x => x.ActiveWindow) .Where(x => x != null) .Where(x => !IsMatch(x, TargetWindow)) .Subscribe( x => { var newTargetWindow = new WindowMatchParams { Title = x.Title, Handle = x.Handle }; Log.Debug($"Selected non-matching Overlay source, changing TargetWindow, {TargetWindow} => {newTargetWindow}"); TargetWindow = newTargetWindow; }) .AddTo(Anchors); this.WhenAnyValue(x => x.MatchingWindowList) .Where(items => !items.Contains(ActiveWindow)) .Select(items => items.FirstOrDefault(x => x.Handle == ActiveWindow?.Handle || x.Handle == WindowHandle) ?? items.FirstOrDefault()) .Where(x => !Equals(ActiveWindow, x)) .Subscribe( x => { Log.Debug( $"Setting new Overlay Window(target: {TargetWindow}): {(ActiveWindow == null ? "null" : ActiveWindow.ToString())} => {(x == null ? "null" : x.ToString())}\n\t{MatchingWindowList.DumpToTable()}"); ActiveWindow = x; }) .AddTo(Anchors); enableOverlaySelector = this.WhenAnyProperty(x => x.MatchingWindowList) .Select(change => MatchingWindowList.Length > 1) .ToPropertyHelper(this, x => x.EnableOverlaySelector) .AddTo(Anchors); this.WhenAnyValue(x => x.TargetWindow) .Subscribe( x => { WindowTitle = x.Title; WindowTitleIsRegex = x.IsRegex; WindowHandle = x.Handle; }) .AddTo(Anchors); this.WhenAnyValue(x => x.WindowTitle, x => x.WindowTitleIsRegex, x => x.WindowHandle) .Select( x => new WindowMatchParams { Title = WindowTitle, IsRegex = WindowTitleIsRegex, Handle = WindowHandle }) .DistinctUntilChanged() .Throttle(ThrottlingPeriod) .ObserveOn(uiScheduler) .Subscribe(x => TargetWindow = x) .AddTo(Anchors); SetWindowTitleCommand = CommandWrapper.Create <WindowHandle>(SetWindowTitleCommandExecuted); }
public MicSwitchOverlayViewModel( [NotNull] IOverlayWindowController overlayWindowController, [NotNull] IMicrophoneControllerEx microphoneController, [NotNull] IConfigProvider<MicSwitchConfig> configProvider, [NotNull] IImageProvider imageProvider, [NotNull] [Dependency(WellKnownSchedulers.UI)] IScheduler uiScheduler) { this.overlayWindowController = overlayWindowController; this.microphoneController = microphoneController; this.configProvider = configProvider; this.imageProvider = imageProvider; OverlayMode = OverlayMode.Transparent; MinSize = new Size(120, 120); MaxSize = new Size(300, 300); SizeToContent = SizeToContent.Manual; TargetAspectRatio = MinSize.Width / MinSize.Height; IsUnlockable = true; Title = "MicSwitch"; WhenLoaded .Take(1) .Select(x => configProvider.WhenChanged) .Switch() .ObserveOn(uiScheduler) .Subscribe(ApplyConfig) .AddTo(Anchors); this.WhenAnyValue(x => x.IsLocked) .Subscribe(isLocked => OverlayMode = isLocked ? OverlayMode.Transparent : OverlayMode.Layered) .AddTo(Anchors); configProvider.ListenTo(x => x.MicrophoneLineId) .ObserveOn(uiScheduler) .Subscribe(lineId => { microphoneController.LineId = lineId; }) .AddTo(Anchors); this.RaiseWhenSourceValue(x => x.IsEnabled, overlayWindowController, x => x.IsEnabled).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.Mute, microphoneController, x => x.Mute, uiScheduler).AddTo(Anchors); this.RaiseWhenSourceValue(x => x.MicrophoneImage, imageProvider, x => x.ActiveMicrophoneImage, uiScheduler).AddTo(Anchors); ToggleLockStateCommand = CommandWrapper.Create( () => { if (IsLocked && UnlockWindowCommand.CanExecute(null)) { UnlockWindowCommand.Execute(null); } else if (!IsLocked && LockWindowCommand.CanExecute(null)) { LockWindowCommand.Execute(null); } else { throw new ApplicationException($"Something went wrong - invalid Overlay Lock state: {new {IsLocked, IsUnlockable, CanUnlock = UnlockWindowCommand.CanExecute(null), CanLock = LockWindowCommand.CanExecute(null) }}"); } }); Observable.Merge( this.ObservableForProperty(x => x.Left, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.Top, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.Width, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.Height, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.IsEnabled, skipInitial: true).ToUnit(), this.ObservableForProperty(x => x.IsLocked, skipInitial: true).ToUnit()) .SkipUntil(WhenLoaded) .Throttle(ConfigThrottlingTimeout) .ObserveOn(uiScheduler) .Subscribe(SaveConfig, Log.HandleUiException) .AddTo(Anchors); this.WhenAnyValue(x => x.IsEnabled) .Where(x => !IsEnabled && !IsLocked) .ObserveOn(uiScheduler) .Subscribe(() => LockWindowCommand.Execute(null), Log.HandleUiException) .AddTo(Anchors); }