From 8f82f9a7850035a7c032dc2ec62a325bc5d2c442 Mon Sep 17 00:00:00 2001 From: hawkeye Date: Sat, 27 Jul 2024 20:12:07 -0400 Subject: [PATCH] Version 4.1.1 Release --- DomainModel/Setting/AppAutoStart.cs | 5 +- DomainModel/Setting/DynamicLodSetting.cs | 8 +- DomainModel/Setting/GeneralSetting.cs | 21 ++++- MainApp/App.xaml.cs | 4 +- MainApp/AppUserControl/HelpDrawer.xaml | 18 ---- MainApp/AppUserControl/PreferenceDrawer.xaml | 82 ++++++++++++------ .../AppUserControl/PreferenceDrawer.xaml.cs | 9 ++ MainApp/AppWindow/NumPad.xaml | 21 ++++- MainApp/AppWindow/NumPad.xaml.cs | 17 +++- MainApp/ViewModel/BaseViewModel.cs | 13 --- MainApp/ViewModel/HelpViewModel.cs | 18 ---- MainApp/ViewModel/OrchestratorUIHelper.cs | 2 +- MainApp/ViewModel/ProfileCardViewModel.cs | 4 +- Orchestration/AppOrchestrator.cs | 21 ++++- Orchestration/AppSettingData.cs | 2 +- Orchestration/AppSettingDataManager.cs | 74 ++++++++++++---- Orchestration/DynamicLodOrchestrator.cs | 2 - Orchestration/HelpOrchestrator.cs | 35 -------- Orchestration/KeyboardOrchestrator.cs | 10 ++- .../PanelConfigurationOrchestrator.cs | 2 +- Orchestration/ProfileData.cs | 14 +-- Orchestration/ProfileDataManager.cs | 50 ++++++++--- README.md | 4 +- RELEASENOTES.md | 20 ++--- Shared/FileIO.cs | 47 ++++------ Shared/FileLogger.cs | 31 ++++--- .../Microsoft.FlightSimulator.SimConnect.dll | Bin 152576 -> 152576 bytes .../Resources/SimConnect/SimConnect.dll | Bin 67072 -> 67072 bytes VERSION.md | 16 ++++ 29 files changed, 321 insertions(+), 229 deletions(-) diff --git a/DomainModel/Setting/AppAutoStart.cs b/DomainModel/Setting/AppAutoStart.cs index 5bd0328..98cfa06 100644 --- a/DomainModel/Setting/AppAutoStart.cs +++ b/DomainModel/Setting/AppAutoStart.cs @@ -144,7 +144,7 @@ namespace MSFSPopoutPanelManager.DomainModel.Setting private static string GetFilePath() { var filePathMsStore = Environment.ExpandEnvironmentVariables("%LocalAppData%") + @"\Packages\Microsoft.FlightSimulator_8wekyb3d8bbwe\LocalCache\"; - var filePathSteam = Environment.ExpandEnvironmentVariables("%AppData%") + @"\Microsoft Flight Simulator\LocalCache\"; + var filePathSteam = Environment.ExpandEnvironmentVariables("%AppData%") + @"\Microsoft Flight Simulator\"; if (Directory.Exists(filePathMsStore)) return filePathMsStore + "exe.xml"; @@ -187,5 +187,8 @@ namespace MSFSPopoutPanelManager.DomainModel.Setting [XmlElement(ElementName = "Path")] public string Path { get; set; } + + [XmlElement(ElementName = "CommandLine")] + public string CommandLine { get; set; } } } diff --git a/DomainModel/Setting/DynamicLodSetting.cs b/DomainModel/Setting/DynamicLodSetting.cs index 95de666..f5545d1 100644 --- a/DomainModel/Setting/DynamicLodSetting.cs +++ b/DomainModel/Setting/DynamicLodSetting.cs @@ -23,7 +23,7 @@ namespace MSFSPopoutPanelManager.DomainModel.Setting public int TargetedFps { get; set; } = 60; - public int FpsTolerance { get; set; } = 5; + public int FpsTolerance { get; set; } = 4; public bool TlodMinOnGround { get; set; } = true; @@ -31,13 +31,13 @@ namespace MSFSPopoutPanelManager.DomainModel.Setting public int TlodMin { get; set; } = 50; - public int TlodMax { get; set; } = 400; + public int TlodMax { get; set; } = 200; public int CloudRecoveryTlod { get; set; } = 100; - public bool DecreaseCloudQuality { get; set; } = true; + public bool DecreaseCloudQuality { get; set; } = false; - public int OlodTop { get; set; } = 20; + public int OlodTop { get; set; } = 50; public int OlodBase { get; set; } = 200; diff --git a/DomainModel/Setting/GeneralSetting.cs b/DomainModel/Setting/GeneralSetting.cs index 7f7487d..8f036ef 100644 --- a/DomainModel/Setting/GeneralSetting.cs +++ b/DomainModel/Setting/GeneralSetting.cs @@ -1,4 +1,5 @@ -using MSFSPopoutPanelManager.Shared; +using System; +using MSFSPopoutPanelManager.Shared; using Newtonsoft.Json; namespace MSFSPopoutPanelManager.DomainModel.Setting @@ -8,6 +9,17 @@ namespace MSFSPopoutPanelManager.DomainModel.Setting public GeneralSetting() { InitializeChildPropertyChangeBinding(); + + PropertyChanged += (_, e) => + { + if (e.PropertyName == "UseApplicationDataPath") + { + OnApplicationDataPathUpdated?.Invoke(this, UseApplicationDataPath); + ApplicationDataPath = FileIo.GetUserDataFilePath(UseApplicationDataPath); + } + }; + + ApplicationDataPath = FileIo.GetUserDataFilePath(UseApplicationDataPath); } public bool AlwaysOnTop { get; set; } = true; @@ -22,6 +34,8 @@ namespace MSFSPopoutPanelManager.DomainModel.Setting public bool TurboMode { get; set; } = false; + public bool UseApplicationDataPath { get; set; } = false; + [JsonIgnore, IgnorePropertyChanged] public bool AutoStart { @@ -34,5 +48,10 @@ namespace MSFSPopoutPanelManager.DomainModel.Setting AppAutoStart.Deactivate(); } } + + [JsonIgnore] + public string ApplicationDataPath { get; set; } + + public event EventHandler OnApplicationDataPathUpdated; } } diff --git a/MainApp/App.xaml.cs b/MainApp/App.xaml.cs index 15c9509..6604a77 100644 --- a/MainApp/App.xaml.cs +++ b/MainApp/App.xaml.cs @@ -43,8 +43,10 @@ namespace MSFSPopoutPanelManager.MainApp // Setup all data storage objects SharedStorage = new SharedStorage(); SharedStorage.AppSettingData.ReadSettings(); + SharedStorage.ProfileData.AppSettingDataRef = SharedStorage.AppSettingData; SharedStorage.ProfileData.ReadProfiles(); - + FileLogger.UseApplicationDataPath = SharedStorage.AppSettingData.ApplicationSetting.GeneralSetting.UseApplicationDataPath; + // Setup dependency injections AppHost = Host.CreateDefaultBuilder() .ConfigureServices((_, services) => diff --git a/MainApp/AppUserControl/HelpDrawer.xaml b/MainApp/AppUserControl/HelpDrawer.xaml index 0c81733..9582219 100644 --- a/MainApp/AppUserControl/HelpDrawer.xaml +++ b/MainApp/AppUserControl/HelpDrawer.xaml @@ -247,14 +247,6 @@ Stroke="Gray" X2="1" /> - - - - - - diff --git a/MainApp/AppUserControl/PreferenceDrawer.xaml b/MainApp/AppUserControl/PreferenceDrawer.xaml index 56ab59e..2e18583 100644 --- a/MainApp/AppUserControl/PreferenceDrawer.xaml +++ b/MainApp/AppUserControl/PreferenceDrawer.xaml @@ -180,6 +180,40 @@ Automatically close the application when exiting MSFS. + + Folder Path to Store POPM Configuration and Profiles + + + + + + + + + + + + + Turbo Mode - - - - - - (Unsupported Feature) - - Dynamically adjust Terrain and Object Level of Details - - - - - - Enable automatic adjustments of TLOD and OLOD to get the desired targeted frame rate (FPS). - - - - - + + + + + (Unsupported Feature) + + Dynamically adjust Terrain and Object Level of Details + + + + + + Enable automatic adjustments of TLOD and OLOD to get the desired targeted frame rate (FPS). + + + + diff --git a/MainApp/AppUserControl/PreferenceDrawer.xaml.cs b/MainApp/AppUserControl/PreferenceDrawer.xaml.cs index f84f2fa..f79be60 100644 --- a/MainApp/AppUserControl/PreferenceDrawer.xaml.cs +++ b/MainApp/AppUserControl/PreferenceDrawer.xaml.cs @@ -1,7 +1,10 @@ using Microsoft.Extensions.DependencyInjection; using MSFSPopoutPanelManager.MainApp.ViewModel; +using System; using System.ComponentModel; +using System.Diagnostics; using System.Windows; +using System.Windows.Navigation; namespace MSFSPopoutPanelManager.MainApp.AppUserControl { @@ -22,5 +25,11 @@ namespace MSFSPopoutPanelManager.MainApp.AppUserControl InitializeComponent(); }; } + + private void Hyperlink_OpenDataFolder(object sender, RequestNavigateEventArgs e) + { + // ToDo: check for folder existence + Process.Start("explorer.exe",e.Uri.ToString()); + } } } \ No newline at end of file diff --git a/MainApp/AppWindow/NumPad.xaml b/MainApp/AppWindow/NumPad.xaml index 7cc9de0..05fd0ad 100644 --- a/MainApp/AppWindow/NumPad.xaml +++ b/MainApp/AppWindow/NumPad.xaml @@ -14,14 +14,27 @@ mc:Ignorable="d"> diff --git a/MainApp/AppWindow/NumPad.xaml.cs b/MainApp/AppWindow/NumPad.xaml.cs index 2665337..e618274 100644 --- a/MainApp/AppWindow/NumPad.xaml.cs +++ b/MainApp/AppWindow/NumPad.xaml.cs @@ -13,7 +13,7 @@ namespace MSFSPopoutPanelManager.MainApp.AppWindow { private readonly NumPadViewModel _viewModel; - public NumPad(Guid panelId) + public NumPad(Guid panelId, int initialWidth, int initialHeight) { InitializeComponent(); if (DesignerProperties.GetIsInDesignMode(new DependencyObject())) @@ -30,8 +30,19 @@ namespace MSFSPopoutPanelManager.MainApp.AppWindow throw new ApplicationException("Unable to instantiate NumPad window"); _viewModel.PanelConfig.PanelHandle = new WindowInteropHelper(window).Handle; - _viewModel.PanelConfig.Width = Convert.ToInt32(Width); - _viewModel.PanelConfig.Height = Convert.ToInt32(Height); + + if (initialWidth == 0 && initialHeight == 0) + { + this.Width = 300; + this.Height = 400; + _viewModel.PanelConfig.Width = Convert.ToInt16(this.Width); + _viewModel.PanelConfig.Height = Convert.ToInt32(this.Height); + } + else + { + this.Width = initialWidth; + this.Height = initialHeight; + } }; this.MouseLeftButtonDown += NumPad_MouseLeftButtonDown; diff --git a/MainApp/ViewModel/BaseViewModel.cs b/MainApp/ViewModel/BaseViewModel.cs index 8182f63..9b86958 100644 --- a/MainApp/ViewModel/BaseViewModel.cs +++ b/MainApp/ViewModel/BaseViewModel.cs @@ -41,18 +41,5 @@ namespace MSFSPopoutPanelManager.MainApp.ViewModel get => SharedStorage.ApplicationWindow; set => SharedStorage.ApplicationWindow = value; } - - public bool LocalCompileOnly - { - get - { - #if LOCAL - return true; - #endif - - return false; - - } - } } } diff --git a/MainApp/ViewModel/HelpViewModel.cs b/MainApp/ViewModel/HelpViewModel.cs index 2e8f62f..3e4ce6d 100644 --- a/MainApp/ViewModel/HelpViewModel.cs +++ b/MainApp/ViewModel/HelpViewModel.cs @@ -16,8 +16,6 @@ namespace MSFSPopoutPanelManager.MainApp.ViewModel public ICommand DeleteAppCacheCommand { get; private set; } - public ICommand RollBackCommand { get; private set; } - public string ApplicationVersion { get; private set; } public bool IsRollBackCommandVisible { get; private set; } @@ -30,7 +28,6 @@ namespace MSFSPopoutPanelManager.MainApp.ViewModel HyperLinkCommand = new DelegateCommand(OnHyperLinkActivated); DeleteAppCacheCommand = new DelegateCommand(OnDeleteAppCache); - RollBackCommand = new DelegateCommand(OnRollBack); #if DEBUG var buildConfig = " (Debug)"; @@ -41,8 +38,6 @@ namespace MSFSPopoutPanelManager.MainApp.ViewModel #endif ApplicationVersion = $"{WindowProcessManager.GetApplicationVersion()}{buildConfig}"; - - IsRollBackCommandVisible = _helpOrchestrator.IsRollBackUpdateEnabled(); HasOrphanAppCache = _helpOrchestrator.HasOrphanAppCache(); } @@ -68,9 +63,6 @@ namespace MSFSPopoutPanelManager.MainApp.ViewModel case "Version Info": _helpOrchestrator.OpenVersionInfo(); break; - case "Open Data Folder": - _helpOrchestrator.OpenDataFolder(); - break; case "Download VCC Library": _helpOrchestrator.DownloadVccLibrary(); break; @@ -82,15 +74,5 @@ namespace MSFSPopoutPanelManager.MainApp.ViewModel _helpOrchestrator.DeleteAppCache(); HasOrphanAppCache = _helpOrchestrator.HasOrphanAppCache(); } - - private async void OnRollBack() - { - var result = await DialogHost.Show(new ConfirmationDialog($"WARNING!{Environment.NewLine}Are you sure you want to rollback to previous version of Pop Out Panel Manager (v3.4.6.0321)? All your changes since updated to v4.0.0 will be lost. Backups of user profile and application settings file from previous version of the application will be restored.", "Rollback"), "RootDialog"); - - if (result != null && result.Equals("CONFIRM")) - { - _helpOrchestrator.RollBackUpdate(); - } - } } } diff --git a/MainApp/ViewModel/OrchestratorUIHelper.cs b/MainApp/ViewModel/OrchestratorUIHelper.cs index 33e129c..bd0bc66 100644 --- a/MainApp/ViewModel/OrchestratorUIHelper.cs +++ b/MainApp/ViewModel/OrchestratorUIHelper.cs @@ -87,7 +87,7 @@ namespace MSFSPopoutPanelManager.MainApp.ViewModel { Application.Current.Dispatcher.Invoke(async () => { - var numPad = new NumPad(panelConfig.Id); + var numPad = new NumPad(panelConfig.Id, panelConfig.Width, panelConfig.Height); numPad.Show(); await Task.Run(() => diff --git a/MainApp/ViewModel/ProfileCardViewModel.cs b/MainApp/ViewModel/ProfileCardViewModel.cs index 3910d53..c93c74f 100644 --- a/MainApp/ViewModel/ProfileCardViewModel.cs +++ b/MainApp/ViewModel/ProfileCardViewModel.cs @@ -228,8 +228,8 @@ namespace MSFSPopoutPanelManager.MainApp.ViewModel PanelType = PanelType.NumPadWindow, Left = 0, Top = 0, - Width = 1920, - Height = 40, + Width = 0, + Height = 0, AutoGameRefocus = false }); } diff --git a/Orchestration/AppOrchestrator.cs b/Orchestration/AppOrchestrator.cs index 81a1b4c..67ccd1d 100644 --- a/Orchestration/AppOrchestrator.cs +++ b/Orchestration/AppOrchestrator.cs @@ -41,6 +41,25 @@ namespace MSFSPopoutPanelManager.Orchestration Task.Run(() => _flightSimOrchestrator.StartSimConnectServer()); // Start the SimConnect server _keyboardOrchestrator.Initialize(); + + AppSettingData.ApplicationSetting.GeneralSetting.OnApplicationDataPathUpdated += (_, e) => + { + AppSettingDataManager.MoveAppSettings(AppSettingData.ApplicationSetting); + ProfileDataManager.MoveProfiles(ProfileData.Profiles, e); + + FileLogger.UseApplicationDataPath = e; + + try + { + FileLogger.CloseFileLogger(); + if (Directory.Exists(FileIo.GetUserDataFilePath(!e))) + Directory.Delete(FileIo.GetUserDataFilePath(!e), true); + } + catch + { + FileLogger.WriteLog($"Unable to remove old POPM data folder. {FileIo.GetUserDataFilePath(!e)}", StatusMessageType.Error); + } + }; } public void ApplicationClose() @@ -56,7 +75,7 @@ namespace MSFSPopoutPanelManager.Orchestration private void CheckForAutoUpdate() { - var jsonPath = Path.Combine(Path.Combine(FileIo.GetUserDataFilePath(), "autoupdate.json")); + var jsonPath = Path.Combine(Path.Combine(FileIo.GetUserDataFilePath(AppSettingData.ApplicationSetting.GeneralSetting.UseApplicationDataPath), "autoupdate.json")); AutoUpdater.PersistenceProvider = new JsonFilePersistenceProvider(jsonPath); AutoUpdater.Synchronous = true; AutoUpdater.AppTitle = "MSFS Pop Out Panel Manager"; diff --git a/Orchestration/AppSettingData.cs b/Orchestration/AppSettingData.cs index 0d219a0..9b2123f 100644 --- a/Orchestration/AppSettingData.cs +++ b/Orchestration/AppSettingData.cs @@ -21,7 +21,7 @@ namespace MSFSPopoutPanelManager.Orchestration ApplicationSetting = new ApplicationSetting(); AppSettingDataManager.WriteAppSetting(ApplicationSetting); } - + // Auto Save data ApplicationSetting.PropertyChanged += (_, e) => { diff --git a/Orchestration/AppSettingDataManager.cs b/Orchestration/AppSettingDataManager.cs index 5dd52cb..7a1d4f9 100644 --- a/Orchestration/AppSettingDataManager.cs +++ b/Orchestration/AppSettingDataManager.cs @@ -1,8 +1,8 @@ -using System; -using MSFSPopoutPanelManager.DomainModel.DataFile; +using MSFSPopoutPanelManager.DomainModel.DataFile; using MSFSPopoutPanelManager.DomainModel.Setting; using MSFSPopoutPanelManager.Shared; using Newtonsoft.Json; +using System; using System.Diagnostics; using System.IO; @@ -18,10 +18,30 @@ namespace MSFSPopoutPanelManager.Orchestration { Debug.WriteLine("Reading application settings data file..."); - using var reader = new StreamReader(Path.Combine(FileIo.GetUserDataFilePath(), APP_SETTING_DATA_FILENAME)); + // Try to read folder path from both locations + var documentsFolderPath = Path.Combine(FileIo.GetUserDataFilePath(false), APP_SETTING_DATA_FILENAME); + var applicationDataFolderPath = Path.Combine(FileIo.GetUserDataFilePath(true), APP_SETTING_DATA_FILENAME); + string folderPath; + + if (File.Exists(applicationDataFolderPath)) + { + folderPath = applicationDataFolderPath; + FileLogger.UseApplicationDataPath = true; + } + else + { + folderPath = documentsFolderPath; + FileLogger.UseApplicationDataPath = false; + } + + using var reader = new StreamReader(folderPath); var fileContent = reader.ReadToEnd(); - return JsonConvert.DeserializeObject(fileContent).ApplicationSetting; + var appSetting = JsonConvert.DeserializeObject(fileContent).ApplicationSetting; + + appSetting.GeneralSetting.UseApplicationDataPath = folderPath.IndexOf("Roaming", StringComparison.Ordinal) > -1; + + return appSetting; } catch { @@ -33,24 +53,44 @@ namespace MSFSPopoutPanelManager.Orchestration { try { - Debug.WriteLine("Saving application settings data file..."); - - var dataFilePath = FileIo.GetUserDataFilePath(); - - if (string.IsNullOrEmpty(dataFilePath)) - throw new Exception("Unable to get app setting data file path."); - - if (!Directory.Exists(dataFilePath)) - Directory.CreateDirectory(dataFilePath); - - using var file = File.CreateText(Path.Combine(dataFilePath, APP_SETTING_DATA_FILENAME)); - var serializer = new JsonSerializer(); - serializer.Serialize(file, new AppSettingFile { ApplicationSetting = appSetting }); + CreateAppSettings(appSetting); } catch { FileLogger.WriteLog($"Unable to write app setting data file: {APP_SETTING_DATA_FILENAME}", StatusMessageType.Error); } } + + public static void MoveAppSettings(ApplicationSetting appSetting) + { + try + { + CreateAppSettings(appSetting); + + // Remove file in old path + var oldPath = FileIo.GetUserDataFilePath(!appSetting.GeneralSetting.UseApplicationDataPath); + if (File.Exists(oldPath)) + File.Delete(oldPath); + } + catch + { + FileLogger.WriteLog($"Unable to remove old app setting data folder:", StatusMessageType.Error); + } + } + + private static void CreateAppSettings(ApplicationSetting appSetting) + { + var dataFilePath = FileIo.GetUserDataFilePath(appSetting.GeneralSetting.UseApplicationDataPath); + + if (string.IsNullOrEmpty(dataFilePath)) + throw new Exception("Unable to get app setting data file path."); + + if (!Directory.Exists(dataFilePath)) + Directory.CreateDirectory(dataFilePath); + + using var file = File.CreateText(Path.Combine(dataFilePath, APP_SETTING_DATA_FILENAME)); + var serializer = new JsonSerializer(); + serializer.Serialize(file, new AppSettingFile { ApplicationSetting = appSetting }); + } } } diff --git a/Orchestration/DynamicLodOrchestrator.cs b/Orchestration/DynamicLodOrchestrator.cs index 75b2d92..ff0c937 100644 --- a/Orchestration/DynamicLodOrchestrator.cs +++ b/Orchestration/DynamicLodOrchestrator.cs @@ -3,10 +3,8 @@ using MSFSPopoutPanelManager.DomainModel.Setting; using MSFSPopoutPanelManager.Shared; using MSFSPopoutPanelManager.SimConnectAgent; using MSFSPopoutPanelManager.WindowsAgent; -using Newtonsoft.Json.Linq; using System; using System.Diagnostics; -using System.Dynamic; using System.Runtime.InteropServices; namespace MSFSPopoutPanelManager.Orchestration diff --git a/Orchestration/HelpOrchestrator.cs b/Orchestration/HelpOrchestrator.cs index c11701f..8a480ea 100644 --- a/Orchestration/HelpOrchestrator.cs +++ b/Orchestration/HelpOrchestrator.cs @@ -39,11 +39,6 @@ namespace MSFSPopoutPanelManager.Orchestration Process.Start("notepad.exe", "VERSION.md"); } - public void OpenDataFolder() - { - Process.Start("explorer.exe", Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "MSFS Pop Out Panel Manager")); - } - public bool HasOrphanAppCache() { var appLocal = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); @@ -104,35 +99,5 @@ namespace MSFSPopoutPanelManager.Orchestration FileLogger.WriteLog("Delete app cache exception: " + ex.Message, StatusMessageType.Error); } } - - public void RollBackUpdate() - { - var userProfileBackupPath = Path.Combine(FileIo.GetUserDataFilePath(), "Backup-previous-version", "userprofiledata.json"); - var appSettingBackupPath = Path.Combine(FileIo.GetUserDataFilePath(), "Backup-previous-version", "appsettingdata.json"); - - var userProfilePath = Path.Combine(FileIo.GetUserDataFilePath(), "userprofiledata.json"); - var appSettingPath = Path.Combine(FileIo.GetUserDataFilePath(), "appsettingdata.json"); - - if (File.Exists(userProfileBackupPath)) - File.Copy(userProfileBackupPath, userProfilePath, true); - - if (File.Exists(appSettingBackupPath)) - File.Copy(appSettingBackupPath, appSettingPath, true); - - AutoUpdater.InstalledVersion = new Version("1.0.0.0"); - AutoUpdater.Synchronous = true; - AutoUpdater.AppTitle = "MSFS Pop Out Panel Manager"; - AutoUpdater.RunUpdateAsAdmin = false; - AutoUpdater.UpdateFormSize = new System.Drawing.Size(1024, 660); - AutoUpdater.UpdateMode = Mode.ForcedDownload; - AutoUpdater.Start("https://raw.githubusercontent.com/hawkeye-stan/msfs-popout-panel-manager/master/rollback.xml"); - } - - public bool IsRollBackUpdateEnabled() - { - var appSettingBackupPath = Path.Combine(FileIo.GetUserDataFilePath(), "Backup-previous-version", "appsettingdata.json"); - - return File.Exists(appSettingBackupPath); - } } } diff --git a/Orchestration/KeyboardOrchestrator.cs b/Orchestration/KeyboardOrchestrator.cs index 5994f67..1a72272 100644 --- a/Orchestration/KeyboardOrchestrator.cs +++ b/Orchestration/KeyboardOrchestrator.cs @@ -69,9 +69,13 @@ namespace MSFSPopoutPanelManager.Orchestration { Debug.WriteLine("Ends Global Keyboard Hook (Forced)"); _keyPressCaptureList = new List(); - _globalKeyboardHook.OnKeyboardPressed -= HandleGlobalKeyboardHookOnKeyboardPressed; - _globalKeyboardHook?.Dispose(); - _globalKeyboardHook = null; + + if (_globalKeyboardHook != null) + { + _globalKeyboardHook.OnKeyboardPressed -= HandleGlobalKeyboardHookOnKeyboardPressed; + _globalKeyboardHook?.Dispose(); + _globalKeyboardHook = null; + } } private void HandleGlobalKeyboardHookOnKeyboardPressed(object sender, GlobalKeyboardHookEventArgs e) diff --git a/Orchestration/PanelConfigurationOrchestrator.cs b/Orchestration/PanelConfigurationOrchestrator.cs index 86c0073..7589b03 100644 --- a/Orchestration/PanelConfigurationOrchestrator.cs +++ b/Orchestration/PanelConfigurationOrchestrator.cs @@ -209,7 +209,7 @@ namespace MSFSPopoutPanelManager.Orchestration foreach (var panel in panels) { - if (!panel.FloatingPanel.IsEnabled || panel.FullScreen) + if (!panel.FloatingPanel.IsEnabled) return; if (panel.PanelType is not (PanelType.CustomPopout or PanelType.BuiltInPopout)) diff --git a/Orchestration/ProfileData.cs b/Orchestration/ProfileData.cs index bdd911e..bd26408 100644 --- a/Orchestration/ProfileData.cs +++ b/Orchestration/ProfileData.cs @@ -19,7 +19,7 @@ namespace MSFSPopoutPanelManager.Orchestration internal FlightSimData FlightSimDataRef { private get; set; } [IgnorePropertyChanged] - internal AppSettingData AppSettingDataRef { private get; set; } + public AppSettingData AppSettingDataRef { private get; set; } public void AddProfile(string profileName) { @@ -29,7 +29,7 @@ namespace MSFSPopoutPanelManager.Orchestration Profiles.Add(newProfile); SetActiveProfile(newProfile.Id); - ProfileDataManager.WriteProfiles(Profiles); + ProfileDataManager.WriteProfiles(Profiles, AppSettingDataRef.ApplicationSetting.GeneralSetting.UseApplicationDataPath); AppSettingDataRef.ApplicationSetting.SystemSetting.LastUsedProfileId = newProfile.Id; } @@ -64,7 +64,7 @@ namespace MSFSPopoutPanelManager.Orchestration Profiles.Add(newProfile); SetActiveProfile(newProfile.Id); - ProfileDataManager.WriteProfiles(Profiles); + ProfileDataManager.WriteProfiles(Profiles, AppSettingDataRef.ApplicationSetting.GeneralSetting.UseApplicationDataPath); AppSettingDataRef.ApplicationSetting.SystemSetting.LastUsedProfileId = newProfile.Id; } @@ -99,7 +99,7 @@ namespace MSFSPopoutPanelManager.Orchestration ActiveProfile.AircraftBindings.Add(aircraft); - ProfileDataManager.WriteProfiles(Profiles); + ProfileDataManager.WriteProfiles(Profiles, AppSettingDataRef.ApplicationSetting.GeneralSetting.UseApplicationDataPath); RefreshProfile(); } @@ -110,13 +110,13 @@ namespace MSFSPopoutPanelManager.Orchestration ActiveProfile.AircraftBindings.Remove(aircraft); - ProfileDataManager.WriteProfiles(Profiles); + ProfileDataManager.WriteProfiles(Profiles, AppSettingDataRef.ApplicationSetting.GeneralSetting.UseApplicationDataPath); RefreshProfile(); } public void ReadProfiles() { - Profiles = new SortedObservableCollection(ProfileDataManager.ReadProfiles()); + Profiles = new SortedObservableCollection(ProfileDataManager.ReadProfiles(AppSettingDataRef.ApplicationSetting.GeneralSetting.UseApplicationDataPath)); Profiles.ToList().ForEach(p => p.OnProfileChanged += (_, _) => WriteProfiles()); // Detect profiles collection changes @@ -135,7 +135,7 @@ namespace MSFSPopoutPanelManager.Orchestration public void WriteProfiles() { Debug.WriteLine("Saving Data ... "); - ProfileDataManager.WriteProfiles(Profiles); + ProfileDataManager.WriteProfiles(Profiles, AppSettingDataRef.ApplicationSetting.GeneralSetting.UseApplicationDataPath); } public void SetActiveProfile(Guid id) diff --git a/Orchestration/ProfileDataManager.cs b/Orchestration/ProfileDataManager.cs index b5aed52..8f7257c 100644 --- a/Orchestration/ProfileDataManager.cs +++ b/Orchestration/ProfileDataManager.cs @@ -13,13 +13,13 @@ namespace MSFSPopoutPanelManager.Orchestration { private const string USER_PROFILE_DATA_FILENAME = "userprofiledata.json"; - public static IList ReadProfiles() + public static IList ReadProfiles(bool isRoamingPath) { try { Debug.WriteLine("Reading user profile data file..."); - using var reader = new StreamReader(Path.Combine(FileIo.GetUserDataFilePath(), USER_PROFILE_DATA_FILENAME)); + using var reader = new StreamReader(Path.Combine(FileIo.GetUserDataFilePath(isRoamingPath), USER_PROFILE_DATA_FILENAME)); var fileContent = reader.ReadToEnd(); return JsonConvert.DeserializeObject(fileContent).Profiles; @@ -30,7 +30,7 @@ namespace MSFSPopoutPanelManager.Orchestration } } - public static void WriteProfiles(IList profiles) + public static void WriteProfiles(IList profiles, bool isRoamingPath) { if (profiles == null) { @@ -40,22 +40,44 @@ namespace MSFSPopoutPanelManager.Orchestration try { - var dataFilePath = FileIo.GetUserDataFilePath(); - - if (string.IsNullOrEmpty(dataFilePath)) - throw new Exception("Unable to get user profile dat file path."); - - if (!Directory.Exists(dataFilePath)) - Directory.CreateDirectory(dataFilePath); - - using var file = File.CreateText(Path.Combine(dataFilePath, USER_PROFILE_DATA_FILENAME)); - var serializer = new JsonSerializer(); - serializer.Serialize(file, new UserProfileFile { Profiles = profiles }); + CreateProfiles(profiles, isRoamingPath); } catch { FileLogger.WriteLog($"Unable to write user data file: {USER_PROFILE_DATA_FILENAME}", StatusMessageType.Error); } } + + public static void MoveProfiles(IList profiles, bool isRoamingPath) + { + try + { + CreateProfiles(profiles, isRoamingPath); + + // Remove file in old path + var oldPath = FileIo.GetUserDataFilePath(!isRoamingPath); + if (File.Exists(oldPath)) + File.Delete(oldPath); + } + catch + { + FileLogger.WriteLog($"Unable to move user data file: {USER_PROFILE_DATA_FILENAME}", StatusMessageType.Error); + } + } + + private static void CreateProfiles(IList profiles, bool isRoamingPath) + { + var dataFilePath = FileIo.GetUserDataFilePath(isRoamingPath); + + if (string.IsNullOrEmpty(dataFilePath)) + throw new Exception("Unable to get user profile data file path."); + + if (!Directory.Exists(dataFilePath)) + Directory.CreateDirectory(dataFilePath); + + using var file = File.CreateText(Path.Combine(dataFilePath, USER_PROFILE_DATA_FILENAME)); + var serializer = new JsonSerializer(); + serializer.Serialize(file, new UserProfileFile { Profiles = profiles }); + } } } diff --git a/README.md b/README.md index 4a66f3b..7f1ca8c 100644 --- a/README.md +++ b/README.md @@ -170,4 +170,6 @@ Thank you for your super kind support of this app! [WPF CalcBinding](https://github.com/Alex141/CalcBinding) by Alexander Zinchenko -[AutoUpdater.NET](https://github.com/ravibpatel/AutoUpdater.NET) by Ravi Patel \ No newline at end of file +[AutoUpdater.NET](https://github.com/ravibpatel/AutoUpdater.NET) by Ravi Patel + +[DynamicLOD](https://github.com/Fragtality/DynamicLOD_ by Fragtality (idea and MSFS memory access code) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 101aecc..5ad2bc7 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,19 +1,15 @@ -## Version 4.1.0 +## Version 4.1.1 -* Added new method to select panel source for an aircraft profile using fixed camera view instead of relying saved custom camera view. Previous method of using saved custom camera view is still available to use if desire. +* Added option to store POPM profiles and configuration files in your user's AppData Roaming folder instead of Documents folder. Hopefully, this solved the issue where OneDrive users are having issue with POPM files. -Video showing how to create a new aircraft profile using the new panel selection method: https://vimeo.com/917361559 +* Fixed POPM inability to close correctly when Keyboard Shortcuts preference is disabled. -Video showing how to update existing aircraft profile to use the new panel selection method: https://vimeo.com/917364912 +* Fixed issue where auto start option failed to retain CommandLine arguments in exe.xml file. -* Added new virtual number pad to be used for touch enabled screen. This number pad will first focus the game window before sending num pad keystroke to the game. +* Fixed issue where auto start does not work correctly for Steam version of MSFS since exe.xml file location has been moved by Steam installation. -* Added new feature to allow pop up panel as floating window. You can assign hotkeys (Ctrl-0 to Ctrl-9) to have the pop out to toggle either showing on screen or minimize. +* Added ability for full screen panel to work as floating panel. An example use case is to show and hide EFB as full screen using keyboard shortcut. -Video showing how to manage floating panel: https://vimeo.com/918153200 +* Added dynamic LOD (my own implementation of AutoFPS) - this is totally experimental and unsupported. If you decide to use this version of dynamic LOD, you don't have to run multiple apps. -* Added a new button to easily close all Pop Out Panel Manager's managed pop outs. - -* Updated keyboard shortcut feature in preference setting to allow usage of custom keyboard shortcut instead of predefined set of keyboard shortcuts. - -* Fixed few reported bugs in the application. \ No newline at end of file +* Fixed various smaller bugs. diff --git a/Shared/FileIO.cs b/Shared/FileIO.cs index d4bd46b..d4a1ee4 100644 --- a/Shared/FileIO.cs +++ b/Shared/FileIO.cs @@ -5,47 +5,38 @@ namespace MSFSPopoutPanelManager.Shared { public class FileIo { - public static string GetUserDataFilePath() + public static string GetUserDataFilePath(bool isRoamingPath) { -#if DEBUG - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "MSFS Pop Out Panel Manager Debug"); -#elif LOCAL - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "MSFS Pop Out Panel Manager Local"); -#else - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "MSFS Pop Out Panel Manager"); -#endif + var specialFolder = isRoamingPath ? Environment.SpecialFolder.ApplicationData : Environment.SpecialFolder.MyDocuments; + return Path.Combine(Environment.GetFolderPath(specialFolder), GetBuildConfigPath()); } - public static string GetErrorLogFilePath() + public static string GetErrorLogFilePath(bool isRoamingPath) { -#if DEBUG - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @"MSFS Pop Out Panel Manager Debug\LogFiles\error.log"); -#elif LOCAL - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @"MSFS Pop Out Panel Manager Local\LogFiles\error.log"); -#else - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @"MSFS Pop Out Panel Manager\LogFiles\error.log"); -#endif + var specialFolder = isRoamingPath ? Environment.SpecialFolder.ApplicationData : Environment.SpecialFolder.MyDocuments; + return Path.Combine(Environment.GetFolderPath(specialFolder), GetBuildConfigPath(), @"LogFiles\error.log"); } - public static string GetDebugLogFilePath() + public static string GetDebugLogFilePath(bool isRoamingPath) { -#if DEBUG - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @"MSFS Pop Out Panel Manager Debug\LogFiles\debug.log"); -#elif LOCAL - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @"MSFS Pop Out Panel Manager Local\LogFiles\debug.log"); -#else - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @"MSFS Pop Out Panel Manager\LogFiles\debug.log"); -#endif + var specialFolder = isRoamingPath ? Environment.SpecialFolder.ApplicationData : Environment.SpecialFolder.MyDocuments; + return Path.Combine(Environment.GetFolderPath(specialFolder), GetBuildConfigPath(), @"LogFiles\debug.log"); } - public static string GetInfoLogFilePath() + public static string GetInfoLogFilePath(bool isRoamingPath) + { + var specialFolder = isRoamingPath ? Environment.SpecialFolder.ApplicationData : Environment.SpecialFolder.MyDocuments; + return Path.Combine(Environment.GetFolderPath(specialFolder), GetBuildConfigPath(), @"LogFiles\info.log"); + } + + private static string GetBuildConfigPath() { #if DEBUG - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @"MSFS Pop Out Panel Manager Debug\LogFiles\info.log"); + return "MSFS Pop Out Panel Manager Debug"; #elif LOCAL - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @"MSFS Pop Out Panel Manager Local\LogFiles\info.log"); + return "MSFS Pop Out Panel Manager Local"; #else - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @"MSFS Pop Out Panel Manager\LogFiles\info.log"); + return "MSFS Pop Out Panel Manager"; #endif } } diff --git a/Shared/FileLogger.cs b/Shared/FileLogger.cs index 9fb058c..cf99a76 100644 --- a/Shared/FileLogger.cs +++ b/Shared/FileLogger.cs @@ -11,7 +11,7 @@ namespace MSFSPopoutPanelManager.Shared public class FileLogger { private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod()?.DeclaringType); - + static FileLogger() { // Setup log4Net @@ -24,18 +24,12 @@ namespace MSFSPopoutPanelManager.Shared if (LogManager.GetRepository(Assembly.GetEntryAssembly()).GetAppenders().First() is not RollingFileAppender errorLogAppender) return; - errorLogAppender.File = FileIo.GetErrorLogFilePath(); + errorLogAppender.File = FileIo.GetErrorLogFilePath(UseApplicationDataPath); errorLogAppender.ActivateOptions(); - - //var infoLogAppender = LogManager.GetRepository(Assembly.GetEntryAssembly()).GetAppenders().Skip(1).First() as RollingFileAppender; - //infoLogAppender.File = FileIo.GetInfoLogFilePath(); - //infoLogAppender.ActivateOptions(); - - //var debugLogAppender = LogManager.GetRepository(Assembly.GetEntryAssembly()).GetAppenders().Skip(2).First() as RollingFileAppender; - //debugLogAppender.File = FileIo.GetDebugLogFilePath(); - //debugLogAppender.ActivateOptions(); } + public static bool UseApplicationDataPath { get; set; } = false; + public static void WriteLog(string message, StatusMessageType messageType) { switch (messageType) @@ -43,12 +37,12 @@ namespace MSFSPopoutPanelManager.Shared case StatusMessageType.Error: Log.Error(message); break; - //case StatusMessageType.Info: - // Log.Info(message); - // break; - //case StatusMessageType.Debug: - // Log.Debug(message); - // break; + //case StatusMessageType.Info: + // Log.Info(message); + // break; + //case StatusMessageType.Debug: + // Log.Debug(message); + // break; } } @@ -56,5 +50,10 @@ namespace MSFSPopoutPanelManager.Shared { Log.Error(message, exception); } + + public static void CloseFileLogger() + { + LogManager.ShutdownRepository(); + } } } diff --git a/SimconnectAgent/Resources/SimConnect/Managed/Microsoft.FlightSimulator.SimConnect.dll b/SimconnectAgent/Resources/SimConnect/Managed/Microsoft.FlightSimulator.SimConnect.dll index b9264d5381555def66fd761c8b9dd3561db97da0..8ffed1c176fc7dd00aaabd42d2afc6fa1959df5e 100644 GIT binary patch delta 30021 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 delta 107 zcmZqZVQJ`L*&x8kqVFt{x>VtB6ooJ6p#S~FgYF|TLg%w0Oh72 i$!$JxB!X2y@1x?ihj}NRTOUonKBrNpakIv$8`c2IEF50| diff --git a/VERSION.md b/VERSION.md index 154c1e7..1eabd21 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1,6 +1,22 @@ # Version History
+## Version 4.1.1 + +* Added option to store POPM profiles and configuration files in your user's AppData Roaming folder instead of Documents folder. Hopefully, this solved the issue where OneDrive users are having issue with POPM files. + +* Fixed POPM inability to close correctly when Keyboard Shortcuts preference is disabled. + +* Fixed issue where auto start option failed to retain CommandLine arguments in exe.xml file. + +* Fixed issue where auto start does not work correctly for Steam version of MSFS since exe.xml file location has been moved by Steam installation. + +* Added ability for full screen panel to work as floating panel. An example use case is to show and hide EFB as full screen using keyboard shortcut. + +* Added dynamic LOD (my own implementation of AutoFPS) - this is totally experimental and unsupported. If you decide to use this version of dynamic LOD, you don't have to run multiple apps. + +* Fixed various smaller bugs. + ## Version 4.1.0 * Added new method to select panel source for an aircraft profile using fixed camera view instead of relying saved custom camera view. Previous method of using saved custom camera view is still available to use if desire.