using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Drawing.IconLib; using System.IO; using System.Linq; using System.Text; using System.Diagnostics; using System.Text.RegularExpressions; using DisplayMagicianShared.AMD; using DisplayMagicianShared.NVIDIA; using DisplayMagicianShared.Windows; using System.Runtime.Serialization; using Newtonsoft.Json.Linq; using System.Windows.Forms; using System.Threading; using System.Threading.Tasks; namespace DisplayMagicianShared { // This enum sets the video card mode used within DisplayMagician // It effectively controls what video card library is used to store profiles on the computer // We look up the PCI vendor ID for the video cards, and then we look for them in the order from most commonly // sold video card to the least, followed by the generic 'catch-all' windows mode. public enum VIDEO_MODE : Int32 { WINDOWS = 0, NVIDIA = 1, AMD = 2, } public enum FORCED_VIDEO_MODE : Int32 { WINDOWS = 0, NVIDIA = 1, AMD = 2, DETECT = 99, } public enum ApplyProfileResult { Successful, Cancelled, Error } public static class ProfileRepository { #region Class Variables // Common items to the class private static List _allProfiles = new List(); public static Dictionary _profileWarningLookup = new Dictionary(); private static bool _profilesLoaded = false; private static ProfileItem _currentProfile; private static List _connectedDisplayIdentifiers = new List(); private static bool notifiedEDIDErrorToUser = false; private static AMDLibrary amdLibrary; private static NVIDIALibrary nvidiaLibrary; private static WinLibrary winLibrary; // Make the default video mode Windows private static VIDEO_MODE _currentVideoMode = VIDEO_MODE.WINDOWS; private static FORCED_VIDEO_MODE _forcedVideoMode = FORCED_VIDEO_MODE.DETECT; private static bool _pauseReadsUntilChangeCompleted = false; // Other constants that are useful public static string AppDataPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DisplayMagician"); public static string AppIconPath = System.IO.Path.Combine(AppDataPath, $"Icons"); public static string AppDisplayMagicianIconFilename = System.IO.Path.Combine(AppIconPath, @"DisplayMagician.ico"); private static readonly string AppProfileStoragePath = System.IO.Path.Combine(AppDataPath, $"Profiles"); private static readonly string _profileStorageJsonFileName = System.IO.Path.Combine(AppProfileStoragePath, $"DisplayProfiles_2.2.json"); #endregion #region Class Constructors static ProfileRepository() { try { // Create the Profile Storage Path if it doesn't exist so that it's avilable for all the program if (!Directory.Exists(AppProfileStoragePath)) { SharedLogger.logger.Debug($"ProfileRepository/ProfileRepository: Creating the Profiles storage folder {AppProfileStoragePath}."); Directory.CreateDirectory(AppProfileStoragePath); } } catch (UnauthorizedAccessException ex) { SharedLogger.logger.Fatal(ex, $"ProfileRepository/ProfileRepository: DisplayMagician doesn't have permissions to create the Profiles storage folder {AppProfileStoragePath}."); } catch (ArgumentException ex) { SharedLogger.logger.Fatal(ex, $"ProfileRepository/ProfileRepository: DisplayMagician can't create the Profiles storage folder {AppProfileStoragePath} due to an invalid argument."); } catch (PathTooLongException ex) { SharedLogger.logger.Fatal(ex, $"ProfileRepository/ProfileRepository: DisplayMagician can't create the Profiles storage folder {AppProfileStoragePath} as the path is too long."); } catch (DirectoryNotFoundException ex) { SharedLogger.logger.Fatal(ex, $"ProfileRepository/ProfileRepository: DisplayMagician can't create the Profiles storage folder {AppProfileStoragePath} as the parent folder isn't there."); } catch (Exception ex) { SharedLogger.logger.Warn(ex, $"ProfileRepository/ProfileRepository: Exception creating the Profiles storage folder."); } } #endregion #region Class Properties public static List AllProfiles { get { if (!_profilesLoaded) // Load the Profiles from storage if they need to be LoadProfiles(); return _allProfiles; } } public static Dictionary ProfileWarningLookup { get { if (!_profilesLoaded) // Load the Profiles from storage if they need to be LoadProfiles(); return _profileWarningLookup; } } public static ProfileItem CurrentProfile { get { if (_currentProfile == null) UpdateActiveProfile(); return _currentProfile; } set { if (value is ProfileItem) { _currentProfile = value; // And if we have the _originalBitmap we can also save the Bitmap overlay, but only if the ProfileToUse is set //if (_originalBitmap is Bitmap) // _shortcutBitmap = ToBitmapOverlay(_originalBitmap, ProfileToUse.ProfileTightestBitmap, 256, 256); } } } public static int ProfileCount { get { if (!_profilesLoaded) // Load the Profiles from storage if they need to be LoadProfiles(); return _allProfiles.Count; } } public static string ProfileStorageFileName { get => _profileStorageJsonFileName; } public static VIDEO_MODE CurrentVideoMode { get { return _currentVideoMode; } set { _currentVideoMode = value; } } public static FORCED_VIDEO_MODE ForcedVideoMode { get { return _forcedVideoMode; } set { _forcedVideoMode = value; SetVideoCardMode(value); } } public static List ConnectedDisplayIdentifiers { get { if (_connectedDisplayIdentifiers.Count == 0) // Load the Profiles from storage if they need to be _connectedDisplayIdentifiers = GetAllConnectedDisplayIdentifiers(); return _connectedDisplayIdentifiers; } set { _connectedDisplayIdentifiers = value; } } public static bool ProfilesLoaded { get { return _profilesLoaded; } set { _profilesLoaded = value; } } #endregion #region Class Methods public static bool InitialiseRepository(FORCED_VIDEO_MODE forcedVideoMode = FORCED_VIDEO_MODE.DETECT) { if (!SetVideoCardMode(forcedVideoMode)) { return false; } if (!_profilesLoaded) { if (!LoadProfiles()) { return false; } } return true; } public static bool AddProfile(ProfileItem profile) { if (!(profile is ProfileItem)) return false; SharedLogger.logger.Debug($"ProfileRepository/AddProfile: Adding profile {profile.Name} to our profile repository"); // Doublecheck if it already exists // Because then we just update the one that already exists if (!ContainsProfile(profile)) { // Add the Profile to the list of Profiles _allProfiles.Add(profile); // Generate the Profile Icon ready to be used SaveProfileIconToCache(profile); profile.PreSave(); // Save the Profiles JSON as it's different SaveProfiles(); } // Refresh the profiles to see whats valid IsPossibleRefresh(); //Doublecheck it's been added if (ContainsProfile(profile)) { return true; } else return false; } public static bool RemoveProfile(ProfileItem profile) { if (!(profile is ProfileItem)) return false; SharedLogger.logger.Debug($"ProfileRepository/RemoveProfile: Removing profile {profile.Name} if it exists in our profile repository"); // Remove the Profile Icons from the Cache List ProfilesToRemove = _allProfiles.FindAll(item => item.UUID.Equals(profile.UUID)); foreach (ProfileItem ProfileToRemove in ProfilesToRemove) { try { File.Delete(ProfileToRemove.SavedProfileIconCacheFilename); File.Delete(ProfileToRemove.WallpaperBitmapFilename); } catch (UnauthorizedAccessException ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/RemoveProfile: DisplayMagician doesn't have permissions to delete the cached Profile Icon {ProfileToRemove.SavedProfileIconCacheFilename}."); } catch (ArgumentException ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/RemoveProfile: DisplayMagician can't delete the cached Profile Icon {ProfileToRemove.SavedProfileIconCacheFilename} due to an invalid argument."); } catch (PathTooLongException ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/RemoveProfile: DisplayMagician can't delete the cached Profile Icon {ProfileToRemove.SavedProfileIconCacheFilename} as the path is too long."); } catch (DirectoryNotFoundException ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/RemoveProfile: DisplayMagician can't delete the cached Profile Icon {ProfileToRemove.SavedProfileIconCacheFilename} as the parent folder isn't there."); } } // Remove the Profile from the list. int numRemoved = _allProfiles.RemoveAll(item => item.UUID.Equals(profile.UUID)); if (numRemoved == 1) { SaveProfiles(); IsPossibleRefresh(); return true; } else if (numRemoved == 0) return false; else throw new ProfileRepositoryException(); } public static bool RemoveProfile(string profileName) { if (String.IsNullOrWhiteSpace(profileName)) return false; SharedLogger.logger.Debug($"ProfileRepository/RemoveProfile2: Removing profile {profileName} if it exists in our profile repository"); // Remove the Profile Icons from the Cache List ProfilesToRemove = _allProfiles.FindAll(item => item.Name.Equals(profileName)); foreach (ProfileItem ProfileToRemove in ProfilesToRemove) { try { File.Delete(ProfileToRemove.SavedProfileIconCacheFilename); File.Delete(ProfileToRemove.WallpaperBitmapFilename); } catch (UnauthorizedAccessException ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/RemoveProfile2: DisplayMagician doesn't have permissions to delete the cached Profile Icon {ProfileToRemove.SavedProfileIconCacheFilename}."); } catch (ArgumentException ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/RemoveProfile2: DisplayMagician can't delete the cached Profile Icon {ProfileToRemove.SavedProfileIconCacheFilename} due to an invalid argument."); } catch (PathTooLongException ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/RemoveProfile2: DisplayMagician can't delete the cached Profile Icon {ProfileToRemove.SavedProfileIconCacheFilename} as the path is too long."); } catch (DirectoryNotFoundException ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/RemoveProfile2: DisplayMagician can't delete the cached Profile Icon {ProfileToRemove.SavedProfileIconCacheFilename} as the parent folder isn't there."); } } // Remove the Profile from the list. int numRemoved = _allProfiles.RemoveAll(item => item.Name.Equals(profileName)); if (numRemoved == 1) { SaveProfiles(); IsPossibleRefresh(); return true; } else if (numRemoved == 0) return false; else throw new ProfileRepositoryException(); } public static bool RemoveProfile(uint profileId) { if (profileId == 0) return false; SharedLogger.logger.Debug($"ProfileRepository/RemoveProfile3: Removing profile wih profileId {profileId} if it exists in our profile repository"); // Remove the Profile Icons from the Cache List ProfilesToRemove = _allProfiles.FindAll(item => item.UUID.Equals(profileId)); foreach (ProfileItem ProfileToRemove in ProfilesToRemove) { try { File.Delete(ProfileToRemove.SavedProfileIconCacheFilename); File.Delete(ProfileToRemove.WallpaperBitmapFilename); } catch (UnauthorizedAccessException ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/RemoveProfile3: DisplayMagician doesn't have permissions to delete the cached Profile Icon {ProfileToRemove.SavedProfileIconCacheFilename}."); } catch (ArgumentException ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/RemoveProfile3: DisplayMagician can't delete the cached Profile Icon {ProfileToRemove.SavedProfileIconCacheFilename} due to an invalid argument."); } catch (PathTooLongException ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/RemoveProfile3: DisplayMagician can't delete the cached Profile Icon {ProfileToRemove.SavedProfileIconCacheFilename} as the path is too long."); } catch (DirectoryNotFoundException ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/RemoveProfile3: DisplayMagician can't delete the cached Profile Icon {ProfileToRemove.SavedProfileIconCacheFilename} as the parent folder isn't there."); } } // Remove the Profile from the list. int numRemoved = _allProfiles.RemoveAll(item => item.UUID.Equals(profileId)); if (numRemoved == 1) { SaveProfiles(); IsPossibleRefresh(); return true; } else if (numRemoved == 0) return false; else throw new ProfileRepositoryException(); } public static bool ContainsProfile(ProfileItem profile) { if (!(profile is ProfileItem)) return false; SharedLogger.logger.Debug($"ProfileRepository/ContainsProfile: Checking if our profile repository contains a profile called {profile.Name}"); foreach (ProfileItem testProfile in _allProfiles) { if (testProfile.Equals(profile)) { SharedLogger.logger.Debug($"ProfileRepository/ContainsProfile: Our profile repository does contain a profile called {profile.Name}"); return true; } } SharedLogger.logger.Debug($"ProfileRepository/ContainsProfile: Our profile repository doesn't contain a profile called {profile.Name}"); return false; } public static bool ContainsProfile(string ProfileNameOrId) { if (String.IsNullOrWhiteSpace(ProfileNameOrId)) return false; SharedLogger.logger.Debug($"ProfileRepository/ContainsProfile2: Checking if our profile repository contains a profile with UUID or Name {ProfileNameOrId}"); if (ProfileItem.IsValidUUID(ProfileNameOrId)) foreach (ProfileItem testProfile in _allProfiles) { if (testProfile.UUID.Equals(ProfileNameOrId)) { SharedLogger.logger.Debug($"ProfileRepository/ContainsProfile2: Our profile repository does contain a profile with UUID {ProfileNameOrId}"); return true; } } else foreach (ProfileItem testProfile in _allProfiles) { if (testProfile.Name.Equals(ProfileNameOrId)) { SharedLogger.logger.Debug($"ProfileRepository/ContainsProfile2: Our profile repository does contain a profile with Name {ProfileNameOrId}"); return true; } } SharedLogger.logger.Debug($"ProfileRepository/ContainsProfile2: Our profile repository doesn't contain a profile with a UUID or Name {ProfileNameOrId}"); return false; } public static bool ContainsCurrentProfile(out string savedProfileName) { savedProfileName = ""; if (!(_currentProfile is ProfileItem)) { return false; } SharedLogger.logger.Debug($"ProfileRepository/ContainsCurrentProfile: Checking if our profile repository contains the display profile currently in use"); foreach (ProfileItem testProfile in _allProfiles) { if (testProfile.Equals(_currentProfile)) { SharedLogger.logger.Debug($"ProfileRepository/ContainsProfile: Our profile repository does contain a profile called {testProfile.Name}"); savedProfileName = testProfile.Name; return true; } } SharedLogger.logger.Debug($"ProfileRepository/ContainsCurrentProfile: Our profile repository doesn't contain the display profile currently in use"); return false; } public static ProfileItem GetProfile(string ProfileNameOrId) { SharedLogger.logger.Debug($"ProfileRepository/GetProfile: Finding and returning {ProfileNameOrId} if it exists in our profile repository"); if (String.IsNullOrWhiteSpace(ProfileNameOrId)) { SharedLogger.logger.Error($"ProfileRepository/GetProfile: Profile to get was empty or only whitespace"); return null; } if (ProfileItem.IsValidUUID(ProfileNameOrId)) foreach (ProfileItem testProfile in _allProfiles) { if (testProfile.UUID.Equals(ProfileNameOrId)) { SharedLogger.logger.Debug($"ProfileRepository/GetProfile: Returning profile with UUID {ProfileNameOrId}"); return testProfile; } } else foreach (ProfileItem testProfile in _allProfiles) { if (testProfile.Name.Equals(ProfileNameOrId)) { SharedLogger.logger.Debug($"ProfileRepository/GetProfile: Returning profile with Name {ProfileNameOrId}"); return testProfile; } } SharedLogger.logger.Debug($"ProfileRepository/GetProfile: Didn't match any profiles with UUD or Name {ProfileNameOrId}"); return null; } public static bool RenameProfile(ProfileItem profile, string renamedName) { if (!(profile is ProfileItem)) { SharedLogger.logger.Error($"ProfileRepository/RenameProfile: Profile to rename was empty or only whitespace"); return false; } SharedLogger.logger.Debug($"ProfileRepository/RenameProfile: Attempting to rename profile {profile.Name} to {renamedName}"); if (!IsValidFilename(renamedName)) { SharedLogger.logger.Error($"ProfileRepository/RenameProfile: The name the user wanted to renamed to profile to is not a valid filename"); return false; } profile.Name = GetValidFilename(renamedName); IsPossibleRefresh(); // If it's been added to the list of AllProfiles // then we also need to reproduce the Icons if (ContainsProfile(profile)) { // Save the Profiles JSON as it's different now SaveProfiles(); SharedLogger.logger.Debug($"ProfileRepository/RenameProfile: The profile was successfully renamed from {profile.Name} to {renamedName}"); return true; } else { SharedLogger.logger.Debug($"ProfileRepository/RenameProfile: The profile was not renamed from {profile.Name} to {renamedName}"); return false; } } public static void UpdateActiveProfile() { SharedLogger.logger.Debug($"ProfileRepository/UpdateActiveProfile: Updating the profile currently active (in use now)."); ProfileItem profile; SharedLogger.logger.Debug($"ProfileRepository/UpdateActiveProfile: Attempting to access configuration through NVIDIA, then AMD, then Windows CCD interfaces, in that order."); if (_currentVideoMode == VIDEO_MODE.NVIDIA) { SharedLogger.logger.Debug($"ProfileRepository/UpdateActiveProfile: NVIDIA NVAPI Driver is installed, so using that for this display profile."); profile = new ProfileItem { Name = "Current NVIDIA Display Profile", VideoMode = VIDEO_MODE.NVIDIA }; } else if (_currentVideoMode == VIDEO_MODE.AMD) { SharedLogger.logger.Debug($"ProfileRepository/UpdateActiveProfile: NVIDIA is not installed but the AMD ADL Driver IS installed, so using that for this display profile."); profile = new ProfileItem { Name = "Current AMD Display Profile", VideoMode = VIDEO_MODE.AMD }; } else { profile = new ProfileItem { Name = "Current Windows Display Profile", VideoMode = VIDEO_MODE.WINDOWS }; } int totalDelay = 0; if (_pauseReadsUntilChangeCompleted) { SharedLogger.logger.Warn($"ProfileRepository/UpdateActiveProfile: Pausing updating display settings as a display change is currently taking place."); while (!_pauseReadsUntilChangeCompleted) { Task.Delay(200); totalDelay += 200; if (totalDelay > 10000) { SharedLogger.logger.Warn($"ProfileRepository/UpdateActiveProfile: Timeout while pausing updating display settingss as it took longer than 10 seconds."); break; } } SharedLogger.logger.Trace($"ProfileRepository/UpdateActiveProfile: Paused updating display settings for {totalDelay} milliseconds."); } // Force explorer to update the TaskBar settings just in case they were moved //ShellHelper.TellShellToWriteSettings(); //WinLibrary.RefreshTaskBars(); profile.CreateProfileFromCurrentDisplaySettings(); if (_profilesLoaded && _allProfiles.Count > 0) { foreach (ProfileItem loadedProfile in ProfileRepository.AllProfiles) { if (loadedProfile.Equals(profile)) { _currentProfile = loadedProfile; SharedLogger.logger.Debug($"ProfileRepository/UpdateActiveProfile: The {loadedProfile.VideoMode.ToString("G")} profile '{loadedProfile.Name}' is currently active (in use now)."); return; } } } SharedLogger.logger.Debug($"ProfileRepository/UpdateActiveProfile: The current profile is a new profile that doesn't already exist in the Profile Repository."); _currentProfile = profile; } public static ProfileItem GetActiveProfile() { if (!(_currentProfile is ProfileItem)) return null; SharedLogger.logger.Debug($"ProfileRepository/GetActiveProfile: Retrieving the currently active profile."); return _currentProfile; } public static bool IsActiveProfile(ProfileItem profile) { SharedLogger.logger.Trace($"ProfileRepository/IsActiveProfile: Checking whether the profile {profile.Name} is the currently active profile."); if (_currentProfile == null) { SharedLogger.logger.Error($"ProfileRepository/IsActiveProfile: The current profile {profile.Name} is null, so can't test it against anything."); return false; } if (profile == null) { SharedLogger.logger.Error($"ProfileRepository/IsActiveProfile: The requested profile {profile.Name} is null. Not changing anything, and reporting an error"); return false; } if (profile.Equals(_currentProfile)) { SharedLogger.logger.Debug($"ProfileRepository/IsActiveProfile: The profile {profile.Name} is the currently active profile."); return true; } else { SharedLogger.logger.Debug($"ProfileRepository/IsActiveProfile: The profile {profile.Name} is the not currently active profile."); return false; } } private static bool LoadProfiles() { SharedLogger.logger.Debug($"ProfileRepository/LoadProfiles: Loading profiles from {_profileStorageJsonFileName} into the Profile Repository"); if (File.Exists(_profileStorageJsonFileName)) { string json = ""; try { json = File.ReadAllText(_profileStorageJsonFileName, Encoding.Unicode); } catch (Exception ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/LoadProfiles: Tried to read the JSON file {_profileStorageJsonFileName} to memory but File.ReadAllTextthrew an exception."); } // Migrate any previous entries to the latest version of the file format to the latest one json = MigrateJsonToLatestVersion(json); if (!string.IsNullOrWhiteSpace(json)) { List jsonErrors = new List(); try { JsonSerializerSettings mySerializerSettings = new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Ignore, NullValueHandling = NullValueHandling.Include, DefaultValueHandling = DefaultValueHandling.Include, TypeNameHandling = TypeNameHandling.Auto, ObjectCreationHandling = ObjectCreationHandling.Replace, Error = delegate (object sender, Newtonsoft.Json.Serialization.ErrorEventArgs args) { jsonErrors.Add($"JSON.net Error: {args.ErrorContext.Error.Source}:{args.ErrorContext.Error.StackTrace} - {args.ErrorContext.Error.Message} | InnerException:{args.ErrorContext.Error.InnerException.Source}:{args.ErrorContext.Error.InnerException.StackTrace} - {args.ErrorContext.Error.InnerException.Message}"); args.ErrorContext.Handled = true; }, }; _allProfiles = JsonConvert.DeserializeObject>(json, mySerializerSettings); } catch (JsonReaderException ex) { // If there is a error in the JSON format if (ex.HResult == -2146233088) { MessageBox.Show($"The Display Profiles file {_profileStorageJsonFileName} contains a syntax error. Please check the file for correctness with a JSON validator.", "Error loading the Display Profiles", MessageBoxButtons.OK, MessageBoxIcon.Error); SharedLogger.logger.Error(ex, $"ProfileRepository/LoadProfiles: JSONReaderException - The Display Profiles file {_profileStorageJsonFileName} contains a syntax error. Please check the file for correctness with a JSON validator."); } else { SharedLogger.logger.Error(ex, $"ProfileRepository/LoadProfiles: JSONReaderException while trying to process the Profiles json data file {_profileStorageJsonFileName} but JsonConvert threw an exception."); } } catch (Exception ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/LoadProfiles: Tried to parse the JSON in the {_profileStorageJsonFileName} but the JsonConvert threw an exception."); } // If we have any JSON.net errors, then we need to records them in the logs if (jsonErrors.Count > 0) { foreach (string jsonError in jsonErrors) { SharedLogger.logger.Error($"ProfileRepository/LoadProfiles: {jsonErrors}"); } } // We need to try and SharedLogger.logger.Debug($"ProfileRepository/LoadProfiles: Finding the current profile in the Profile Repository"); // Go through all the profiles and set up the needed structures (such as the Screens list) // and check if the current profile is used /*foreach (ProfileItem loadedProfile in _allProfiles) { SharedLogger.logger.Debug($"ProfileRepository/LoadProfiles: Profile {loadedProfile.Name} is a NVIDIA Profile"); loadedProfile.PerformPostLoadingTasks(); *//*if (ProfileRepository.IsActiveProfile(loadedProfile)) _currentProfile = loadedProfile;*//* }*/ // Sort the profiles alphabetically _allProfiles.Sort(); } else { SharedLogger.logger.Debug($"ProfileRepository/LoadProfiles: The {_profileStorageJsonFileName} profile JSON file exists but is empty! So we're going to treat it as if it didn't exist."); //UpdateActiveProfile(); } } else { // If we get here, then we don't have any profiles saved! // So we gotta start from scratch SharedLogger.logger.Debug($"ProfileRepository/LoadProfiles: Couldn't find the {_profileStorageJsonFileName} profile JSON file that contains the Profiles"); //UpdateActiveProfile(); } _profilesLoaded = true; // Update the current active profile //UpdateActiveProfile(); IsPossibleRefresh(); return true; } public static string MigrateJsonToLatestVersion(string json) { bool changedJson = false; JArray root = new JArray(); try { SharedLogger.logger.Trace($"ProfileRepository/MigrateJsonToLatestVersion: Processing the Profiles json data to migrate any older feature to the latest version."); root = JArray.Parse(json); } catch(JsonReaderException ex) { // If there is a error in the JSON format if (ex.HResult == -2146233088) { MessageBox.Show("The Display Profiles file contains a syntax error. Please check the file for correctness with a JSON validator.", "Error loading the Display Profiles", MessageBoxButtons.OK, MessageBoxIcon.Error); SharedLogger.logger.Error(ex, $"ProfileRepository/MigrateJsonToLatestVersion: JSONReaderException - The Display Profiles file contains a syntax error. Please check the file for correctness with a JSON validator."); } else { SharedLogger.logger.Error(ex, $"ProfileRepository/MigrateJsonToLatestVersion: JSONReaderException while trying to process the Profiles json data to migrate any older feature to the latest version."); } } catch(Exception ex) { SharedLogger.logger.Error(ex,$"ProfileRepository/MigrateJsonToLatestVersion: Exception while trying to process the Profiles json data to migrate any older feature to the latest version."); } // We do the actual change we were trying to do try { // Nothing to patch at the moment! } catch (JsonReaderException ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/MigrateJsonToLatestVersion: JSONReaderException while trying to process the Profiles json data to migrate any older feature to the latest version."); } catch (Exception ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/MigrateJsonToLatestVersion: Exception while trying to process the Profiles json data to migrate any older feature to the latest version."); } // Now write the changed json to the json string but only if we've changed something if (changedJson) { json = root.ToString(Formatting.Indented); if (!string.IsNullOrWhiteSpace(json)) { SharedLogger.logger.Debug($"ProfileRepository/SaveProfiles: Saving the profile repository to the {_profileStorageJsonFileName}."); File.WriteAllText(_profileStorageJsonFileName, json, Encoding.Unicode); } } return json; } public static bool SaveProfiles() { SharedLogger.logger.Debug($"ProfileRepository/SaveProfiles: Attempting to save the profiles repository to the {AppProfileStoragePath}."); if (!Directory.Exists(AppProfileStoragePath)) { try { Directory.CreateDirectory(AppProfileStoragePath); } catch (UnauthorizedAccessException ex) { SharedLogger.logger.Fatal(ex, $"ProfileRepository/SaveProfiles: DisplayMagician doesn't have permissions to create the Profiles storage folder {AppProfileStoragePath}."); } catch (ArgumentException ex) { SharedLogger.logger.Fatal(ex, $"ProfileRepository/SaveProfiles: DisplayMagician can't create the Profiles storage folder {AppProfileStoragePath} due to an invalid argument."); } catch (PathTooLongException ex) { SharedLogger.logger.Fatal(ex, $"ProfileRepository/SaveProfiles: DisplayMagician can't create the Profiles storage folder {AppProfileStoragePath} as the path is too long."); } catch (DirectoryNotFoundException ex) { SharedLogger.logger.Fatal(ex, $"ProfileRepository/SaveProfiles: DisplayMagician can't create the Profiles storage folder {AppProfileStoragePath} as the parent folder isn't there."); } } else { SharedLogger.logger.Debug($"ProfileRepository/SaveProfiles: Profiles folder {AppProfileStoragePath} exists."); } List jsonErrors = new List(); try { SharedLogger.logger.Debug($"ProfileRepository/SaveProfiles: Converting the objects to JSON format."); JsonSerializerSettings mySerializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Include, //NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Include, //DefaultValueHandling = DefaultValueHandling.Ignore, TypeNameHandling = TypeNameHandling.Auto, MissingMemberHandling = MissingMemberHandling.Error, ObjectCreationHandling = ObjectCreationHandling.Replace, Error = delegate (object sender, Newtonsoft.Json.Serialization.ErrorEventArgs args) { jsonErrors.Add($"JSON.net Error: {args.ErrorContext.Error.Source}:{args.ErrorContext.Error.StackTrace} - {args.ErrorContext.Error.Message} | InnerException:{args.ErrorContext.Error.InnerException.Source}:{args.ErrorContext.Error.InnerException.StackTrace} - {args.ErrorContext.Error.InnerException.Message}"); args.ErrorContext.Handled = true; }, }; var json = JsonConvert.SerializeObject(_allProfiles, Formatting.Indented, mySerializerSettings); if (!string.IsNullOrWhiteSpace(json)) { SharedLogger.logger.Debug($"ProfileRepository/SaveProfiles: Saving the profile repository to the {_profileStorageJsonFileName}."); File.WriteAllText(_profileStorageJsonFileName, json, Encoding.Unicode); return true; } } catch (Exception ex) { SharedLogger.logger.Error(ex, $"ProfileRepository/SaveProfiles: Unable to save the profile repository to the {_profileStorageJsonFileName}."); } // If we have any JSON.net errors, then we need to records them in the logs if (jsonErrors.Count > 0) { foreach (string jsonError in jsonErrors) { SharedLogger.logger.Error($"ProfileRepository/SaveProfiles: {jsonErrors}"); } } return false; } private static void SaveProfileIconToCache(ProfileItem profile) { // Work out the name of the Profile we'll save. profile.SavedProfileIconCacheFilename = System.IO.Path.Combine(AppProfileStoragePath, string.Concat(@"profile-", profile.UUID, @".ico")); SharedLogger.logger.Debug($"ProfileRepository/SaveProfileIconToCache: Attempting to save the profile icon {profile.SavedProfileIconCacheFilename} to the {AppProfileStoragePath} folder"); MultiIcon ProfileIcon; try { ProfileIcon = profile.ProfileIcon.ToIcon(); ProfileIcon.Save(profile.SavedProfileIconCacheFilename, MultiIconFormat.ICO); } catch (Exception ex) { SharedLogger.logger.Warn(ex,$"ProfileRepository/SaveProfileIconToCache: Exception saving the profile icon {profile.SavedProfileIconCacheFilename} to the {AppProfileStoragePath} folder. Using the default DisplayMagician icon instead"); // If we fail to create an icon based on the Profile, then we use the standard DisplayMagician profile one. // Which is created on program startup. File.Copy(AppDisplayMagicianIconFilename, profile.SavedProfileIconCacheFilename); } } public static void IsPossibleRefresh() { // We need to refresh the cached answer // Get the list of connected devices //ConnectedDisplayIdentifiers = GetAllConnectedDisplayIdentifiers(); if (_profilesLoaded && _allProfiles.Count > 0) { int totalDelay = 0; if (_pauseReadsUntilChangeCompleted) { SharedLogger.logger.Warn($"ProfileRepository/IsPossibleRefresh: Pausing refreshing display profile possibility as a display change is currently taking place."); while (!_pauseReadsUntilChangeCompleted) { Task.Delay(200); totalDelay += 200; if (totalDelay > 10000) { SharedLogger.logger.Warn($"ProfileRepository/IsPossibleRefresh: Timeout while refreshing display profile possibility as it took longer than 10 seconds."); break; } } SharedLogger.logger.Trace($"ProfileRepository/IsPossibleRefresh: Paused refreshing display profile possibility for {totalDelay} milliseconds."); } foreach (ProfileItem loadedProfile in AllProfiles) loadedProfile.RefreshPossbility(); } } public static List GetAllConnectedDisplayIdentifiers() { int totalDelay = 0; if (_pauseReadsUntilChangeCompleted) { SharedLogger.logger.Warn($"ProfileRepository/GetAllConnectedDisplayIdentifiers: Pausing checking for all connected display identifiers as a display change is currently taking place."); while (!_pauseReadsUntilChangeCompleted) { Task.Delay(200); totalDelay += 200; if (totalDelay > 10000) { SharedLogger.logger.Warn($"ProfileRepository/GetAllConnectedDisplayIdentifiers: Timeout while pausing checking for all connected display identifiers as it took longer than 10 seconds."); break; } } SharedLogger.logger.Trace($"ProfileRepository/GetAllConnectedDisplayIdentifiers: Paused checking for all connected display identifiers for {totalDelay} milliseconds."); } if (_currentVideoMode == VIDEO_MODE.NVIDIA && NVIDIALibrary.GetLibrary().IsInstalled) { return NVIDIALibrary.GetLibrary().GetAllConnectedDisplayIdentifiers(); } else if (_currentVideoMode == VIDEO_MODE.AMD && AMDLibrary.GetLibrary().IsInstalled) { return AMDLibrary.GetLibrary().GetAllConnectedDisplayIdentifiers(); } else { return WinLibrary.GetLibrary().GetAllConnectedDisplayIdentifiers(); } } public static List GetCurrentDisplayIdentifiers() { int totalDelay = 0; if (_pauseReadsUntilChangeCompleted) { SharedLogger.logger.Warn($"ProfileRepository/GetCurrentDisplayIdentifiers: Pausing checking for currently connected display identifiers as a display change is currently taking place."); while (!_pauseReadsUntilChangeCompleted) { Task.Delay(200); totalDelay += 200; if (totalDelay > 10000) { SharedLogger.logger.Warn($"ProfileRepository/GetCurrentDisplayIdentifiers: Timeout while pausing checking for currently connected display identifiers as it took longer than 10 seconds."); break; } } SharedLogger.logger.Trace($"ProfileRepository/GetCurrentDisplayIdentifiers: Paused checking for currently connected display identifiers for {totalDelay} milliseconds."); } if (_currentVideoMode == VIDEO_MODE.NVIDIA && NVIDIALibrary.GetLibrary().IsInstalled) { return NVIDIALibrary.GetLibrary().CurrentDisplayIdentifiers; } else if (_currentVideoMode == VIDEO_MODE.AMD && AMDLibrary.GetLibrary().IsInstalled) { return AMDLibrary.GetLibrary().CurrentDisplayIdentifiers; } else { return WinLibrary.GetLibrary().CurrentDisplayIdentifiers; } } public static bool IsValidFilename(string testName) { SharedLogger.logger.Trace($"ProfileRepository/IsValidFilename: Checking whether {testName} is a valid filename"); string strTheseAreInvalidFileNameChars = new string(System.IO.Path.GetInvalidFileNameChars()); Regex regInvalidFileName = new Regex("[" + Regex.Escape(strTheseAreInvalidFileNameChars) + "]"); if (regInvalidFileName.IsMatch(testName)) { SharedLogger.logger.Trace($"ProfileRepository/IsValidFilename: {testName} is a valid filename"); return false; } else { SharedLogger.logger.Debug($"ProfileRepository/IsValidFilename: {testName} isn't a valid filename as it contains one of these characters [" + Regex.Escape(strTheseAreInvalidFileNameChars) + "]"); return true; } } public static string GetValidFilename(string uncheckedFilename) { SharedLogger.logger.Trace($"ProfileRepository/GetValidFilename: Modifying filename {uncheckedFilename} to be a valid filename for this filesystem"); string invalid = new string(System.IO.Path.GetInvalidFileNameChars()) + new string(System.IO.Path.GetInvalidPathChars()); foreach (char c in invalid) { uncheckedFilename = uncheckedFilename.Replace(c.ToString(), ""); } SharedLogger.logger.Trace($"ProfileRepository/GetValidFilename: Modified filename {uncheckedFilename} so it is a valid filename for this filesystem"); return uncheckedFilename; } // ApplyProfile lives here so that the UI works. public static ApplyProfileResult ApplyProfile(ProfileItem profile) { SharedLogger.logger.Trace($"Program/ApplyProfile: Starting"); // We try to time the profile display swap Stopwatch stopWatch = new Stopwatch(); bool wasDisplayChangeSuccessful = true; if (profile == null) { SharedLogger.logger.Debug($"ProfileRepository/ApplyProfile: The supplied profile is null! Can't be used."); return ApplyProfileResult.Error; } try { // We start the timer just before we attempt the display change stopWatch.Start(); // We also set the lock to pause reads until the profile change has happened _pauseReadsUntilChangeCompleted = true; // We try to swap profiles. The profiles have checking logic in them if (!(profile.SetActive())) { SharedLogger.logger.Error($"ProfileRepository/ApplyProfile: Error applying the {profile.VideoMode.ToString("G")} Profile!"); return ApplyProfileResult.Error; } else { SharedLogger.logger.Trace($"ProfileRepository/ApplyProfile: Successfully applied the {profile.VideoMode.ToString("G")} Profile!"); return ApplyProfileResult.Successful; } } catch (Exception ex) { SharedLogger.logger.Debug($"ProfileRepository/ApplyProfile: Failed to complete changing the Windows Display layout"); wasDisplayChangeSuccessful = false; return ApplyProfileResult.Error; } finally { // If the applying path info worked, then we attempt to set the desktop background if needed if (profile.WallpaperMode.Equals(Wallpaper.Mode.Apply) && !String.IsNullOrWhiteSpace(profile.WallpaperBitmapFilename)) { if (Wallpaper.Set(profile.WallpaperBitmapFilename, profile.WallpaperStyle)) { SharedLogger.logger.Trace($"Program/ApplyProfile: We attempted to set the desktop wallpaper to {profile.SavedProfileIconCacheFilename} using {profile.WallpaperStyle} style for profile {profile.Name}, and it worked!"); } else { SharedLogger.logger.Warn($"Program/ApplyProfile: We attempted to set the desktop wallpaper to {profile.SavedProfileIconCacheFilename} using {profile.WallpaperStyle} style for profile {profile.Name}, and it failed :("); } } else if (profile.WallpaperMode.Equals(Wallpaper.Mode.Clear)) { if (Wallpaper.Clear()) { SharedLogger.logger.Trace($"Program/ApplyProfile: We attempted to clear the desktop wallpaper and it worked!"); } else { SharedLogger.logger.Warn($"Program/ApplyProfile: We attempted to clear the desktop wallpaper and it failed :("); } } // We stop the stop watch stopWatch.Stop(); _pauseReadsUntilChangeCompleted = false; // Get the elapsed time as a TimeSpan value. TimeSpan ts = stopWatch.Elapsed; string result = "failed"; if (wasDisplayChangeSuccessful) { result = "was successful"; ProfileRepository.UpdateActiveProfile(); } // Display the TimeSpan time and result. SharedLogger.logger.Debug($"ProfileRepository/ApplyProfile: Display change attempt took {ts.Minutes}:{ts.Seconds}.{ts.Milliseconds} and {result}."); } return ApplyProfileResult.Successful; } public static bool SetVideoCardMode(FORCED_VIDEO_MODE forcedVideoMode = FORCED_VIDEO_MODE.DETECT) { _forcedVideoMode = forcedVideoMode; // This sets the order in which the different modes have been chosen. // NVIDIA Video cards are the most common, so go first if (_forcedVideoMode == FORCED_VIDEO_MODE.NVIDIA) { // We force the video mode to be NVIDIA _currentVideoMode = VIDEO_MODE.NVIDIA; } else if (forcedVideoMode == FORCED_VIDEO_MODE.AMD) { // We force the video mode to be AMD _currentVideoMode = VIDEO_MODE.AMD; } else if (forcedVideoMode == FORCED_VIDEO_MODE.WINDOWS) { // We force the video mode to be WINDOWS _currentVideoMode = VIDEO_MODE.WINDOWS; } else { // We do normal video library detection based on the video card! // First we check if we need to pause int totalDelay = 0; if (_pauseReadsUntilChangeCompleted) { SharedLogger.logger.Warn($"ProfileRepository/SetVideoCardMode: Pausing detecting video card PCI vendors as a display change is currently taking place."); while (!_pauseReadsUntilChangeCompleted) { Task.Delay(200); totalDelay += 200; if (totalDelay > 10000) { SharedLogger.logger.Warn($"ProfileRepository/SetVideoCardMode: Timeout while detecting video card PCI vendors as it took longer than 10 seconds."); break; } } SharedLogger.logger.Trace($"ProfileRepository/SetVideoCardMode: Paused detecting video card PCI vendors for {totalDelay} milliseconds."); } // Figure out the Video Cards and see what mode we want // Get a list of all the PCI Vendor IDs List videoCardVendors = WinLibrary.GetLibrary().GetCurrentPCIVideoCardVendors(); if (NVIDIALibrary.GetLibrary().IsInstalled && NVIDIALibrary.GetLibrary().PCIVendorIDs.All(value => videoCardVendors.Contains(value))) { // We detected a NVIDIA video card in the computer _currentVideoMode = VIDEO_MODE.NVIDIA; } else if (AMDLibrary.GetLibrary().IsInstalled && AMDLibrary.GetLibrary().PCIVendorIDs.All(value => videoCardVendors.Contains(value))) { // We detected an AMD video card in the computer _currentVideoMode = VIDEO_MODE.AMD; } else { // We fallback to the built-in Windows CCD drivers _currentVideoMode = VIDEO_MODE.WINDOWS; } } if (_currentVideoMode == VIDEO_MODE.NVIDIA) { // Initialise the the NVIDIA NvAPI Library try { SharedLogger.logger.Debug($"ProfileRepository/ProfileRepository: Initialising the NVIDIA NVAPI library."); nvidiaLibrary = new NVIDIALibrary(); } catch (Exception ex) { SharedLogger.logger.Warn(ex, $"ProfileRepository/ProfileRepository: Initialising NVIDIA NVAPI caused an exception."); return false; } } else if (_currentVideoMode == VIDEO_MODE.AMD) { // Initialise the the AMD ADL Library try { SharedLogger.logger.Debug($"ProfileRepository/ProfileRepository: Initialising the AMD ADL library."); amdLibrary = new AMDLibrary(); } catch (Exception ex) { SharedLogger.logger.Warn(ex, $"ProfileRepository/ProfileRepository: Initialising AMD ADL caused an exception."); return false; } } return true; } #endregion } [global::System.Serializable] public class ProfileRepositoryException : Exception { public ProfileRepositoryException() { } public ProfileRepositoryException(string message) : base(message) { } public ProfileRepositoryException(string message, Exception inner) : base(message, inner) { } protected ProfileRepositoryException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } public class ApplyTopologyException : Exception { public ApplyTopologyException() { } public ApplyTopologyException(string message) : base(message) { } public ApplyTopologyException(string message, Exception innerException) : base(message, innerException) { } public ApplyTopologyException(SerializationInfo info, StreamingContext context) : base(info, context) { } } }