From e4fe53d232d805a54b27266650b382c3ad9a2769 Mon Sep 17 00:00:00 2001 From: emoacht Date: Sun, 8 Sep 2024 21:57:47 +0900 Subject: [PATCH 1/2] Improve to capture power status changed --- .../HelloSwitcher.Core.csproj | 1 + .../Models/DeviceSwitcher.cs | 67 +++++--- .../Models/PowerSuspendResumeWatcher.cs | 158 ++++++++++++++++++ .../HelloSwitcherService.cs | 15 +- Source/HelloSwitcher/App.xaml.cs | 22 ++- 5 files changed, 229 insertions(+), 34 deletions(-) create mode 100644 Source/HelloSwitcher.Core/Models/PowerSuspendResumeWatcher.cs diff --git a/Source/HelloSwitcher.Core/HelloSwitcher.Core.csproj b/Source/HelloSwitcher.Core/HelloSwitcher.Core.csproj index 158d7eb..8280b50 100644 --- a/Source/HelloSwitcher.Core/HelloSwitcher.Core.csproj +++ b/Source/HelloSwitcher.Core/HelloSwitcher.Core.csproj @@ -49,6 +49,7 @@ + diff --git a/Source/HelloSwitcher.Core/Models/DeviceSwitcher.cs b/Source/HelloSwitcher.Core/Models/DeviceSwitcher.cs index d639c0b..4606495 100644 --- a/Source/HelloSwitcher.Core/Models/DeviceSwitcher.cs +++ b/Source/HelloSwitcher.Core/Models/DeviceSwitcher.cs @@ -19,47 +19,70 @@ public DeviceSwitcher(Settings settings, Logger logger) public bool RemovableCameraExists => _removableCameraExists.GetValueOrDefault(); private bool? _removableCameraExists = null; - private readonly object _lock = new(); + private int _checkCount = 0; + private readonly TimeSpan _checkInterval = TimeSpan.FromSeconds(1); public Task CheckAsync(string actionName, CancellationToken cancellationToken = default) => CheckAsync(actionName, null, false, cancellationToken); public async Task CheckAsync(string actionName, string deviceName, bool exists, CancellationToken cancellationToken = default) { - var result = new List { actionName }; - void RecordResult() => _logger?.RecordOperation(string.Join(Environment.NewLine, result)); - - result.Add($"deviceName: [{deviceName}], exists: {exists}"); - - lock (_lock) + bool isEntered = false; + try { - if ((deviceName is not null) && (_settings.RemovableCameraVidPid?.IsValid is true)) + isEntered = (Interlocked.Increment(ref _checkCount) == 1); + if (isEntered) { - result.Add($"RemovableCameraVidPid: [{_settings.RemovableCameraVidPid}]"); - - if (!_settings.RemovableCameraVidPid.Equals(new VidPid(deviceName))) - { - RecordResult(); - return; - } + var checkTask = CheckBaseAsync(actionName, deviceName, exists, cancellationToken); + var intervalTask = Task.Delay(_checkInterval, cancellationToken); + await Task.WhenAll(checkTask, intervalTask); } - else + } + catch (TaskCanceledException) + { + } + finally + { + if (isEntered) { - result.Add($"RemovableCameraClassGuid: {_settings.RemovableCameraClassGuid}, RemovableCameraDeviceInstanceId: [{_settings.RemovableCameraDeviceInstanceId}]"); - - exists = DeviceUsbHelper.UsbCameraExists(_settings.RemovableCameraClassGuid, _settings.RemovableCameraDeviceInstanceId); + Interlocked.Exchange(ref _checkCount, 0); } + } + } + + private async Task CheckBaseAsync(string actionName, string deviceName, bool exists, CancellationToken cancellationToken = default) + { + var result = new List { actionName }; + void RecordResult() => _logger?.RecordOperation(string.Join(Environment.NewLine, result)); + + result.Add($"deviceName: [{deviceName}], exists: {exists}"); - result.Add($"removableCameraExists: [{_removableCameraExists}], exists: {exists}"); + if ((deviceName is not null) && (_settings.RemovableCameraVidPid?.IsValid is true)) + { + result.Add($"RemovableCameraVidPid: [{_settings.RemovableCameraVidPid}]"); - if (_removableCameraExists == exists) + if (!_settings.RemovableCameraVidPid.Equals(new VidPid(deviceName))) { RecordResult(); return; } + } + else + { + result.Add($"RemovableCameraClassGuid: {_settings.RemovableCameraClassGuid}, RemovableCameraDeviceInstanceId: [{_settings.RemovableCameraDeviceInstanceId}]"); - _removableCameraExists = exists; + exists = DeviceUsbHelper.UsbCameraExists(_settings.RemovableCameraClassGuid, _settings.RemovableCameraDeviceInstanceId); } + result.Add($"removableCameraExists: [{_removableCameraExists}], exists: {exists}"); + + if (_removableCameraExists == exists) + { + RecordResult(); + return; + } + + _removableCameraExists = exists; + if (cancellationToken.IsCancellationRequested) { RecordResult(); diff --git a/Source/HelloSwitcher.Core/Models/PowerSuspendResumeWatcher.cs b/Source/HelloSwitcher.Core/Models/PowerSuspendResumeWatcher.cs new file mode 100644 index 0000000..c9b0dbe --- /dev/null +++ b/Source/HelloSwitcher.Core/Models/PowerSuspendResumeWatcher.cs @@ -0,0 +1,158 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HelloSwitcher.Models; + +public class PowerSuspendResumeWatcher : IDisposable +{ + #region Win32 + + [DllImport("Powrprof.dll")] + private static extern uint PowerRegisterSuspendResumeNotification( + uint flags, + in DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS recipient, + out IntPtr registrationHandle); + + [DllImport("Powrprof.dll")] + private static extern uint PowerUnregisterSuspendResumeNotification( + IntPtr registrationHandle); + + private const uint DEVICE_NOTIFY_CALLBACK = 2; + + [StructLayout(LayoutKind.Sequential)] + private struct DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS + { + public DeviceNotifyCallbackRoutine callback; + public IntPtr context; + } + + private delegate uint DeviceNotifyCallbackRoutine( + IntPtr context, + int type, + IntPtr setting); + + private const uint ERROR_SUCCESS = 0; + + #endregion + + private DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS _recipient; + private IntPtr _registrationHandle; + + public PowerSuspendResumeWatcher() + { + _recipient = new DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS + { + callback = new DeviceNotifyCallbackRoutine(DeviceNotifyCallback), + context = IntPtr.Zero + }; + uint result = PowerRegisterSuspendResumeNotification( + DEVICE_NOTIFY_CALLBACK, + in _recipient, + out _registrationHandle); + if (result != ERROR_SUCCESS) + { + Debug.WriteLine($"Failed to register suspend resume notification. ({result})"); + } + } + + private uint DeviceNotifyCallback(IntPtr context, int type, IntPtr setting) + { + if (Enum.IsDefined(typeof(PowerStatus), type)) + { + PowerStatusChanged?.Invoke(this, (PowerStatus)type); + } + return 0; + } + + public event EventHandler PowerStatusChanged; + + #region IDisposable + + private bool _isDisposed = false; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_isDisposed) + return; + + if (disposing) + { + // Free any other managed objects here. + PowerStatusChanged = null; + + if (_registrationHandle != IntPtr.Zero) + { + uint result = PowerUnregisterSuspendResumeNotification(_registrationHandle); + if (result != ERROR_SUCCESS) + { + Debug.WriteLine($"Failed to unregister suspend resume notification. ({result})"); + } + } + } + + // Free any unmanaged objects here. + _isDisposed = true; + } + + #endregion +} + +public enum PowerStatus +{ + /// + /// PBT_APMQUERYSUSPEND + /// + QuerySuspend = 0x0000, + + /// + /// PBT_APMQUERYSUSPENDFAILED + /// + QuerySuspendFailed = 0x0002, + + /// + /// PBT_APMSUSPEND + /// + Suspend = 0x0004, + + /// + /// PBT_APMRESUMECRITICAL + /// + ResumeCritical = 0x0006, + + /// + /// PBT_APMRESUMESUSPEND + /// + ResumeSuspend = 0x0007, + + /// + /// PBT_APMBATTERYLOW + /// + BatteryLow = 0x0009, + + /// + /// PBT_APMPOWERSTATUSCHANGE + /// + PowerStatusChange = 0x000A, + + /// + /// PBT_APMOEMEVENT + /// + OemEvent = 0x000B, + + /// + /// PBT_APMRESUMEAUTOMATIC + /// + ResumeAutomatic = 0x0012, + + /// + /// PBT_POWERSETTINGCHANGE + /// + PowerSettingChange = 0x8013 +} \ No newline at end of file diff --git a/Source/HelloSwitcher.Service/HelloSwitcherService.cs b/Source/HelloSwitcher.Service/HelloSwitcherService.cs index 8e7f605..711a33a 100644 --- a/Source/HelloSwitcher.Service/HelloSwitcherService.cs +++ b/Source/HelloSwitcher.Service/HelloSwitcherService.cs @@ -3,7 +3,6 @@ using System.Threading; using System.Threading.Tasks; -using HelloSwitcher.Helper; using HelloSwitcher.Models; namespace HelloSwitcher.Service; @@ -17,7 +16,6 @@ public partial class HelloSwitcherService : ServiceBase internal bool IsPaused { get; private set; } - private readonly Sample _check; private CancellationTokenSource _checkTokenSource; private IntPtr _notificationHandle; @@ -30,10 +28,6 @@ public HelloSwitcherService() Settings = new Settings(); Logger = new Logger("operation.service.log", "error.service.log"); - - _check = new Sample( - TimeSpan.FromSeconds(1), - (actionName, cancellationToken) => _switcher?.CheckAsync(actionName, cancellationToken)); } protected override async void OnStart(string[] args) @@ -91,7 +85,7 @@ protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus) { case PowerBroadcastStatus.ResumeAutomatic: case PowerBroadcastStatus.ResumeSuspend: - _check.Push($"Power Changed Check", _checkTokenSource?.Token ?? default); + OnChanged($"Power Changed Check", _checkTokenSource?.Token ?? default); break; } } @@ -118,9 +112,14 @@ protected override void OnCustomCommand(int command) if (!IsPaused) { - _check.Push($"Device Changed Check", _checkTokenSource?.Token ?? default); + OnChanged($"Device Changed Check", _checkTokenSource?.Token ?? default); } break; } } + + private async void OnChanged(string actionName, CancellationToken cancellationToken) + { + await _switcher?.CheckAsync(actionName, cancellationToken); + } } \ No newline at end of file diff --git a/Source/HelloSwitcher/App.xaml.cs b/Source/HelloSwitcher/App.xaml.cs index bce1e7a..8175020 100644 --- a/Source/HelloSwitcher/App.xaml.cs +++ b/Source/HelloSwitcher/App.xaml.cs @@ -16,7 +16,8 @@ public partial class App : Application internal Logger Logger { get; } private DeviceSwitcher _switcher; - private DeviceUsbWindowWatcher _watcher; + private DeviceUsbWindowWatcher _deviceWatcher; + private PowerSuspendResumeWatcher _powerWatcher; private NotifyIconHolder _holder; internal static bool IsInteractive { get; } = Environment.UserInteractive; @@ -50,8 +51,8 @@ protected override async void OnStartup(StartupEventArgs e) _switcher = new DeviceSwitcher(Settings, Logger); await _switcher.CheckAsync("Initial Check"); - _watcher = new DeviceUsbWindowWatcher(); - _watcher.UsbDeviceChanged += async (_, e) => + _deviceWatcher = new DeviceUsbWindowWatcher(); + _deviceWatcher.UsbDeviceChanged += async (_, e) => { await _switcher.CheckAsync("Device Changed Check", e.deviceName, e.exists); @@ -62,6 +63,18 @@ protected override async void OnStartup(StartupEventArgs e) } }; + _powerWatcher = new PowerSuspendResumeWatcher(); + _powerWatcher.PowerStatusChanged += async (_, status) => + { + switch (status) + { + case PowerStatus.ResumeAutomatic: + case PowerStatus.ResumeSuspend: + await _switcher.CheckAsync($"Resumed Check ({status})"); + break; + } + }; + if (IsInteractive) { _holder = new NotifyIconHolder( @@ -81,7 +94,8 @@ protected override async void OnStartup(StartupEventArgs e) protected override void OnExit(ExitEventArgs e) { - _watcher?.Dispose(); + _deviceWatcher?.Dispose(); + _powerWatcher?.Dispose(); _holder?.Dispose(); End(); From c857388c6caf875aed42083a8f4a17ff34f0dc91 Mon Sep 17 00:00:00 2001 From: emoacht Date: Sun, 8 Sep 2024 22:09:32 +0900 Subject: [PATCH 2/2] Increment version to 1.6.1 --- .../Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- .../HelloSwitcher/Properties/AssemblyInfo.cs | 4 +- Source/Installer/Product.wxs | 172 +++++++++--------- 4 files changed, 92 insertions(+), 92 deletions(-) diff --git a/Source/HelloSwitcher.Core/Properties/AssemblyInfo.cs b/Source/HelloSwitcher.Core/Properties/AssemblyInfo.cs index 8a0976f..bcc4667 100644 --- a/Source/HelloSwitcher.Core/Properties/AssemblyInfo.cs +++ b/Source/HelloSwitcher.Core/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.6.0.0")] -[assembly: AssemblyFileVersion("1.6.0.0")] +[assembly: AssemblyVersion("1.6.1.0")] +[assembly: AssemblyFileVersion("1.6.1.0")] diff --git a/Source/HelloSwitcher.Service/Properties/AssemblyInfo.cs b/Source/HelloSwitcher.Service/Properties/AssemblyInfo.cs index e9045b4..30a39c8 100644 --- a/Source/HelloSwitcher.Service/Properties/AssemblyInfo.cs +++ b/Source/HelloSwitcher.Service/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.6.0.0")] -[assembly: AssemblyFileVersion("1.6.0.0")] +[assembly: AssemblyVersion("1.6.1.0")] +[assembly: AssemblyFileVersion("1.6.1.0")] diff --git a/Source/HelloSwitcher/Properties/AssemblyInfo.cs b/Source/HelloSwitcher/Properties/AssemblyInfo.cs index 5d6e42c..266711e 100644 --- a/Source/HelloSwitcher/Properties/AssemblyInfo.cs +++ b/Source/HelloSwitcher/Properties/AssemblyInfo.cs @@ -51,6 +51,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.6.0.0")] -[assembly: AssemblyFileVersion("1.6.0.0")] +[assembly: AssemblyVersion("1.6.1.0")] +[assembly: AssemblyFileVersion("1.6.1.0")] [assembly: Guid("24aba232-090c-4c0e-8568-27e13b281fab")] diff --git a/Source/Installer/Product.wxs b/Source/Installer/Product.wxs index fe98a8c..4802e68 100644 --- a/Source/Installer/Product.wxs +++ b/Source/Installer/Product.wxs @@ -1,104 +1,104 @@ - - + + - - + + - - - - - + + + + + - - 1 - 1 - - - + + 1 + 1 + + + - - - - - + + + + + - - + + - - - - - + + + + + - - - - + + + + - - - + + + - - - + + + - - - - + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + - - + + - - - - - - - - + + + + + + + + - - - - (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") - - + + + + (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") + + \ No newline at end of file