Example #1
0
        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);
        }
Example #2
0
        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);
        }