diff --git a/HeliosPlus.Shared/ProfileRepository.cs b/HeliosPlus.Shared/ProfileRepository.cs index 53fb1c3..9bdd9de 100644 --- a/HeliosPlus.Shared/ProfileRepository.cs +++ b/HeliosPlus.Shared/ProfileRepository.cs @@ -39,16 +39,15 @@ namespace HeliosPlus.Shared private static List _allProfiles = new List(); private static bool _profilesLoaded = false; public static Version Version = new Version(1, 0, 0); + private static ProfileItem _currentProfile; + // Other constants that are useful public static string AppDataPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "HeliosPlus"); public static string AppIconPath = System.IO.Path.Combine(AppDataPath, $"Icons"); public static string AppHeliosPlusIconFilename = System.IO.Path.Combine(AppIconPath, @"HeliosPlus.ico"); private static string AppProfileStoragePath = System.IO.Path.Combine(AppDataPath, $"Profiles"); private static string _profileStorageJsonFileName = System.IO.Path.Combine(AppProfileStoragePath, $"DisplayProfiles_{Version.ToString(2)}.json"); - private static uint _lastProfileId; - private static ProfileItem _currentProfile; - //private static List _availableDisplays; - //private static List _unavailableDisplays; + #endregion @@ -94,7 +93,8 @@ namespace HeliosPlus.Shared { get { - UpdateActiveProfile(); + if (_currentProfile == null) + UpdateActiveProfile(); return _currentProfile; } set @@ -298,6 +298,20 @@ namespace HeliosPlus.Shared } + public static bool ContainsCurrentProfile() + { + if (!(_currentProfile is ProfileItem)) + return false; + + foreach (ProfileItem testProfile in _allProfiles) + { + if (testProfile.Paths.SequenceEqual(_currentProfile.Paths)) + return true; + } + + return false; + } + public static ProfileItem GetProfile(string ProfileNameOrId) { if (String.IsNullOrWhiteSpace(ProfileNameOrId)) @@ -369,7 +383,7 @@ namespace HeliosPlus.Shared { foreach (ProfileItem loadedProfile in ProfileRepository.AllProfiles) { - if (activeProfile.Equals(loadedProfile)) + if (activeProfile.Paths.SequenceEqual(loadedProfile.Paths)) { _currentProfile = loadedProfile; return; @@ -383,7 +397,7 @@ namespace HeliosPlus.Shared public static ProfileItem GetActiveProfile() { - UpdateActiveProfile(); + //UpdateActiveProfile(); if (!(_currentProfile is ProfileItem)) return null; @@ -392,7 +406,7 @@ namespace HeliosPlus.Shared public static bool IsActiveProfile(ProfileItem profile) { - UpdateActiveProfile(); + //UpdateActiveProfile(); if (!(_currentProfile is ProfileItem)) return false; @@ -400,7 +414,7 @@ namespace HeliosPlus.Shared if (!(profile is ProfileItem)) return false; - if (profile.Equals(_currentProfile)) + if (profile.Paths.SequenceEqual(_currentProfile.Paths)) return true; return false; diff --git a/HeliosPlus.Shared/Topology/Path.cs b/HeliosPlus.Shared/Topology/Path.cs index 252e718..b4124ca 100644 --- a/HeliosPlus.Shared/Topology/Path.cs +++ b/HeliosPlus.Shared/Topology/Path.cs @@ -119,7 +119,7 @@ namespace HeliosPlus.Shared.Topology } // Custom comparer for the ProfileViewport class - class ProfileViewportComparer : IEqualityComparer + class PathComparer : IEqualityComparer { // Products are equal if their names and product numbers are equal. public bool Equals(Path x, Path y) diff --git a/HeliosPlus/DeviceNotification.cs b/HeliosPlus/DeviceNotification.cs new file mode 100644 index 0000000..3b949fe --- /dev/null +++ b/HeliosPlus/DeviceNotification.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace HeliosPlus +{ + internal static class DeviceNotification + { + #region Constants & types + + public const int DbtDeviceArrival = 0x8000; // System detected a new device + public const int DbtDeviceRemoveComplete = 0x8004; // Device is gone + public const int WmDeviceChange = 0x0219; // Device change event + + #endregion + + #region Methods + + public static bool IsMonitor(IntPtr lParam) + { + return IsDeviceOfClass(lParam, GuidDeviceInterfaceMonitorDevice); + } + + public static bool IsUsbDevice(IntPtr lParam) + { + return IsDeviceOfClass(lParam, GuidDeviceInterfaceUSBDevice); + } + + /// Registers a window to receive notifications when Monitor devices are plugged or unplugged. + public static void RegisterMonitorDeviceNotification(IntPtr windowHandle) + { + var dbi = CreateBroadcastDeviceInterface(GuidDeviceInterfaceMonitorDevice); + monitorNotificationHandle = RegisterDeviceNotification(dbi, windowHandle); + } + + /// Registers a window to receive notifications when USB devices are plugged or unplugged. + public static void RegisterUsbDeviceNotification(IntPtr windowHandle) + { + var dbi = CreateBroadcastDeviceInterface(GuidDeviceInterfaceUSBDevice); + usbNotificationHandle = RegisterDeviceNotification(dbi, windowHandle); + } + + /// UnRegisters the window for Monitor device notifications + public static void UnRegisterMonitorDeviceNotification() + { + UnregisterDeviceNotification(monitorNotificationHandle); + } + + /// UnRegisters the window for USB device notifications + public static void UnRegisterUsbDeviceNotification() + { + UnregisterDeviceNotification(usbNotificationHandle); + } + + #endregion + + #region Private or protected constants & types + + private const int DbtDeviceTypeDeviceInterface = 5; + + // https://docs.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-usb-device + private static readonly Guid GuidDeviceInterfaceUSBDevice = new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED"); // USB devices + + // https://docs.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-monitor + private static readonly Guid GuidDeviceInterfaceMonitorDevice = new Guid("E6F07B5F-EE97-4a90-B076-33F57BF4EAA7"); // Monitor devices + private static IntPtr usbNotificationHandle; + private static IntPtr monitorNotificationHandle; + + [StructLayout(LayoutKind.Sequential)] + private struct DevBroadcastDeviceInterface + { + internal int Size; + internal int DeviceType; + internal int Reserved; + internal Guid ClassGuid; + internal short Name; + } + + [StructLayout(LayoutKind.Sequential)] + private struct DevBroadcastHdr + { + internal UInt32 Size; + internal UInt32 DeviceType; + internal UInt32 Reserved; + } + + #endregion + + #region Private & protected methods + + private static bool IsDeviceOfClass(IntPtr lParam, Guid classGuid) + { + var hdr = Marshal.PtrToStructure(lParam); + if (hdr.DeviceType != DbtDeviceTypeDeviceInterface) + return false; + + var devIF = Marshal.PtrToStructure(lParam); + + return devIF.ClassGuid == classGuid; + + } + + private static DevBroadcastDeviceInterface CreateBroadcastDeviceInterface(Guid classGuid) + { + var dbi = new DevBroadcastDeviceInterface + { + DeviceType = DbtDeviceTypeDeviceInterface, + Reserved = 0, + ClassGuid = classGuid, + Name = 0 + }; + + dbi.Size = Marshal.SizeOf(dbi); + + return dbi; + } + + private static IntPtr RegisterDeviceNotification(DevBroadcastDeviceInterface dbi, IntPtr windowHandle) + { + var buffer = Marshal.AllocHGlobal(dbi.Size); + IntPtr handle; + + try + { + Marshal.StructureToPtr(dbi, buffer, true); + + handle = RegisterDeviceNotification(windowHandle, buffer, 0); + } + finally + { + // Free buffer + Marshal.FreeHGlobal(buffer); + } + + return handle; + } + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr RegisterDeviceNotification(IntPtr recipient, IntPtr notificationFilter, int flags); + + [DllImport("user32.dll")] + private static extern bool UnregisterDeviceNotification(IntPtr handle); + + #endregion + } +} diff --git a/HeliosPlus/HeliosPlus.csproj b/HeliosPlus/HeliosPlus.csproj index 8d73d02..1ef927a 100644 --- a/HeliosPlus/HeliosPlus.csproj +++ b/HeliosPlus/HeliosPlus.csproj @@ -73,6 +73,7 @@ + diff --git a/HeliosPlus/UIForms/DisplayProfileForm.cs b/HeliosPlus/UIForms/DisplayProfileForm.cs index 2b44f60..4dae4dd 100644 --- a/HeliosPlus/UIForms/DisplayProfileForm.cs +++ b/HeliosPlus/UIForms/DisplayProfileForm.cs @@ -8,6 +8,7 @@ using HeliosPlus.Resources; using HeliosPlus.Shared; using Manina.Windows.Forms; using System.Text.RegularExpressions; +using System.Diagnostics; namespace HeliosPlus.UIForms { @@ -146,21 +147,19 @@ namespace HeliosPlus.UIForms // alter their Windows Display settings then come back to our app // and the app will automatically recognise that things have changed. - /*// Reload the profiles in case we swapped to another program to change it - ProfileRepository.UpdateCurrentProfile(); + // Reload the profiles in case we swapped to another program to change it + //ChangeSelectedProfile(ProfileRepository.CurrentProfile); // Refresh the Profile UI - RefreshDisplayProfileUI();*/ + //RefreshDisplayProfileUI(); } private void DisplayProfileForm_Load(object sender, EventArgs e) { - // Load all the profiles to prepare things - //_savedProfiles = ProfileRepository.AllProfiles; + // Update the Current Profile - //ProfileRepository.UpdateCurrentProfile(); - // ProfileRepository.GetActiveProfile(); - // Change to the current selected Profile - ChangeSelectedProfile(ProfileRepository.GetActiveProfile()); + ProfileRepository.UpdateActiveProfile(); + + ChangeSelectedProfile(ProfileRepository.CurrentProfile); // Refresh the Profile UI RefreshDisplayProfileUI(); @@ -178,7 +177,7 @@ namespace HeliosPlus.UIForms // And update the save/rename textbox txt_profile_save_name.Text = _selectedProfile.Name; - if (ProfileRepository.ContainsProfile(_selectedProfile)) + if (ProfileRepository.ContainsProfile(profile)) { // we already have the profile stored _saveOrRenameMode = "rename"; @@ -341,5 +340,81 @@ namespace HeliosPlus.UIForms } } + + protected override void WndProc(ref Message m) + { + const int WM_DISPLAYCHANGE = 0x007E; + + const int x_bitshift = 0; + const int y_bitshift = 16; + const int xy_mask = 0xFFFF; + + bool displayChange = false; + + switch (m.Msg) + { + case WM_DISPLAYCHANGE: + ProfileRepository.UpdateActiveProfile(); + break; + } + + base.WndProc(ref m); + } + + /*private static void MainWindow_Closed(object sender, EventArgs e) + { + DeviceNotification.UnRegisterUsbDeviceNotification(); + DeviceNotification.UnRegisterMonitorDeviceNotification(); + } + + protected override void OnSourceInitialized(EventArgs e) + { + base.OnSourceInitialized(e); + + if (!(PresentationSource.FromVisual(this) is HwndSource source)) + return; + + source.AddHook(this.WndProc); + + DeviceNotification.RegisterUsbDeviceNotification(source.Handle); + DeviceNotification.RegisterMonitorDeviceNotification(source.Handle); + }*/ + + // Notification registeration for display detection from here http://codetips.nl/detectmonitor.html + + /*protected override void WndProc(ref Message m) + { + const int WM_SETTINGCHANGE = 0x001A; + const int SPI_SETWORKAREA = 0x02F; + const int WM_DISPLAYCHANGE = 0x007E; + const int WM_DEVICECHANGE = 0x0219; // WM Device change message ID + const int DBT_DEVICEARRIVAL = 0x8000; // WM Device Change Event: System detected a new device + const int DBT_DEVICEREMOVECOMPLETE = 0x8004; // WM Device Change Event: Device is gone + + + const int x_bitshift = 0; + const int y_bitshift = 16; + const int xy_mask = 0xFFFF; + + bool displayChange = false; + + switch (m.Msg) + { + case WM_DISPLAYCHANGE: + case DeviceNotification.DbtDeviceRemoveComplete: + case DeviceNotification.DbtDeviceArrival: + { + if (DeviceNotification.IsMonitor(lParam)) + Debug.WriteLine($"Monitor {((int)wParam == DeviceNotification.DbtDeviceArrival ? "arrived" : "removed")}"); + + if (DeviceNotification.IsUsbDevice(lParam)) + Debug.WriteLine($"Usb device {((int)wParam == DeviceNotification.DbtDeviceArrival ? "arrived" : "removed")}"); + } + break; + } + + base.WndProc(ref m); + }*/ + } } \ No newline at end of file