Skip to content

Commit b4ba5c2

Browse files
authored
Merge pull request #16 from emoacht/develop
Develop
2 parents b505ef4 + c857388 commit b4ba5c2

File tree

9 files changed

+321
-126
lines changed

9 files changed

+321
-126
lines changed

Source/HelloSwitcher.Core/HelloSwitcher.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
<Compile Include="Models\DeviceUsbWindowWatcher.cs" />
5050
<Compile Include="Models\Logger.cs" />
5151
<Compile Include="Models\PnpUtility.cs" />
52+
<Compile Include="Models\PowerSuspendResumeWatcher.cs" />
5253
<Compile Include="Models\Settings.cs" />
5354
<Compile Include="Models\VidPid.cs" />
5455
<Compile Include="Models\WmiUsbHelper.cs" />

Source/HelloSwitcher.Core/Models/DeviceSwitcher.cs

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,47 +19,70 @@ public DeviceSwitcher(Settings settings, Logger logger)
1919
public bool RemovableCameraExists => _removableCameraExists.GetValueOrDefault();
2020
private bool? _removableCameraExists = null;
2121

22-
private readonly object _lock = new();
22+
private int _checkCount = 0;
23+
private readonly TimeSpan _checkInterval = TimeSpan.FromSeconds(1);
2324

2425
public Task CheckAsync(string actionName, CancellationToken cancellationToken = default) => CheckAsync(actionName, null, false, cancellationToken);
2526

2627
public async Task CheckAsync(string actionName, string deviceName, bool exists, CancellationToken cancellationToken = default)
2728
{
28-
var result = new List<string> { actionName };
29-
void RecordResult() => _logger?.RecordOperation(string.Join(Environment.NewLine, result));
30-
31-
result.Add($"deviceName: [{deviceName}], exists: {exists}");
32-
33-
lock (_lock)
29+
bool isEntered = false;
30+
try
3431
{
35-
if ((deviceName is not null) && (_settings.RemovableCameraVidPid?.IsValid is true))
32+
isEntered = (Interlocked.Increment(ref _checkCount) == 1);
33+
if (isEntered)
3634
{
37-
result.Add($"RemovableCameraVidPid: [{_settings.RemovableCameraVidPid}]");
38-
39-
if (!_settings.RemovableCameraVidPid.Equals(new VidPid(deviceName)))
40-
{
41-
RecordResult();
42-
return;
43-
}
35+
var checkTask = CheckBaseAsync(actionName, deviceName, exists, cancellationToken);
36+
var intervalTask = Task.Delay(_checkInterval, cancellationToken);
37+
await Task.WhenAll(checkTask, intervalTask);
4438
}
45-
else
39+
}
40+
catch (TaskCanceledException)
41+
{
42+
}
43+
finally
44+
{
45+
if (isEntered)
4646
{
47-
result.Add($"RemovableCameraClassGuid: {_settings.RemovableCameraClassGuid}, RemovableCameraDeviceInstanceId: [{_settings.RemovableCameraDeviceInstanceId}]");
48-
49-
exists = DeviceUsbHelper.UsbCameraExists(_settings.RemovableCameraClassGuid, _settings.RemovableCameraDeviceInstanceId);
47+
Interlocked.Exchange(ref _checkCount, 0);
5048
}
49+
}
50+
}
51+
52+
private async Task CheckBaseAsync(string actionName, string deviceName, bool exists, CancellationToken cancellationToken = default)
53+
{
54+
var result = new List<string> { actionName };
55+
void RecordResult() => _logger?.RecordOperation(string.Join(Environment.NewLine, result));
56+
57+
result.Add($"deviceName: [{deviceName}], exists: {exists}");
5158

52-
result.Add($"removableCameraExists: [{_removableCameraExists}], exists: {exists}");
59+
if ((deviceName is not null) && (_settings.RemovableCameraVidPid?.IsValid is true))
60+
{
61+
result.Add($"RemovableCameraVidPid: [{_settings.RemovableCameraVidPid}]");
5362

54-
if (_removableCameraExists == exists)
63+
if (!_settings.RemovableCameraVidPid.Equals(new VidPid(deviceName)))
5564
{
5665
RecordResult();
5766
return;
5867
}
68+
}
69+
else
70+
{
71+
result.Add($"RemovableCameraClassGuid: {_settings.RemovableCameraClassGuid}, RemovableCameraDeviceInstanceId: [{_settings.RemovableCameraDeviceInstanceId}]");
5972

60-
_removableCameraExists = exists;
73+
exists = DeviceUsbHelper.UsbCameraExists(_settings.RemovableCameraClassGuid, _settings.RemovableCameraDeviceInstanceId);
6174
}
6275

76+
result.Add($"removableCameraExists: [{_removableCameraExists}], exists: {exists}");
77+
78+
if (_removableCameraExists == exists)
79+
{
80+
RecordResult();
81+
return;
82+
}
83+
84+
_removableCameraExists = exists;
85+
6386
if (cancellationToken.IsCancellationRequested)
6487
{
6588
RecordResult();
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Runtime.InteropServices;
4+
5+
namespace HelloSwitcher.Models;
6+
7+
public class PowerSuspendResumeWatcher : IDisposable
8+
{
9+
#region Win32
10+
11+
[DllImport("Powrprof.dll")]
12+
private static extern uint PowerRegisterSuspendResumeNotification(
13+
uint flags,
14+
in DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS recipient,
15+
out IntPtr registrationHandle);
16+
17+
[DllImport("Powrprof.dll")]
18+
private static extern uint PowerUnregisterSuspendResumeNotification(
19+
IntPtr registrationHandle);
20+
21+
private const uint DEVICE_NOTIFY_CALLBACK = 2;
22+
23+
[StructLayout(LayoutKind.Sequential)]
24+
private struct DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
25+
{
26+
public DeviceNotifyCallbackRoutine callback;
27+
public IntPtr context;
28+
}
29+
30+
private delegate uint DeviceNotifyCallbackRoutine(
31+
IntPtr context,
32+
int type,
33+
IntPtr setting);
34+
35+
private const uint ERROR_SUCCESS = 0;
36+
37+
#endregion
38+
39+
private DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS _recipient;
40+
private IntPtr _registrationHandle;
41+
42+
public PowerSuspendResumeWatcher()
43+
{
44+
_recipient = new DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
45+
{
46+
callback = new DeviceNotifyCallbackRoutine(DeviceNotifyCallback),
47+
context = IntPtr.Zero
48+
};
49+
uint result = PowerRegisterSuspendResumeNotification(
50+
DEVICE_NOTIFY_CALLBACK,
51+
in _recipient,
52+
out _registrationHandle);
53+
if (result != ERROR_SUCCESS)
54+
{
55+
Debug.WriteLine($"Failed to register suspend resume notification. ({result})");
56+
}
57+
}
58+
59+
private uint DeviceNotifyCallback(IntPtr context, int type, IntPtr setting)
60+
{
61+
if (Enum.IsDefined(typeof(PowerStatus), type))
62+
{
63+
PowerStatusChanged?.Invoke(this, (PowerStatus)type);
64+
}
65+
return 0;
66+
}
67+
68+
public event EventHandler<PowerStatus> PowerStatusChanged;
69+
70+
#region IDisposable
71+
72+
private bool _isDisposed = false;
73+
74+
public void Dispose()
75+
{
76+
Dispose(true);
77+
GC.SuppressFinalize(this);
78+
}
79+
80+
protected virtual void Dispose(bool disposing)
81+
{
82+
if (_isDisposed)
83+
return;
84+
85+
if (disposing)
86+
{
87+
// Free any other managed objects here.
88+
PowerStatusChanged = null;
89+
90+
if (_registrationHandle != IntPtr.Zero)
91+
{
92+
uint result = PowerUnregisterSuspendResumeNotification(_registrationHandle);
93+
if (result != ERROR_SUCCESS)
94+
{
95+
Debug.WriteLine($"Failed to unregister suspend resume notification. ({result})");
96+
}
97+
}
98+
}
99+
100+
// Free any unmanaged objects here.
101+
_isDisposed = true;
102+
}
103+
104+
#endregion
105+
}
106+
107+
public enum PowerStatus
108+
{
109+
/// <summary>
110+
/// PBT_APMQUERYSUSPEND
111+
/// </summary>
112+
QuerySuspend = 0x0000,
113+
114+
/// <summary>
115+
/// PBT_APMQUERYSUSPENDFAILED
116+
/// </summary>
117+
QuerySuspendFailed = 0x0002,
118+
119+
/// <summary>
120+
/// PBT_APMSUSPEND
121+
/// </summary>
122+
Suspend = 0x0004,
123+
124+
/// <summary>
125+
/// PBT_APMRESUMECRITICAL
126+
/// </summary>
127+
ResumeCritical = 0x0006,
128+
129+
/// <summary>
130+
/// PBT_APMRESUMESUSPEND
131+
/// </summary>
132+
ResumeSuspend = 0x0007,
133+
134+
/// <summary>
135+
/// PBT_APMBATTERYLOW
136+
/// </summary>
137+
BatteryLow = 0x0009,
138+
139+
/// <summary>
140+
/// PBT_APMPOWERSTATUSCHANGE
141+
/// </summary>
142+
PowerStatusChange = 0x000A,
143+
144+
/// <summary>
145+
/// PBT_APMOEMEVENT
146+
/// </summary>
147+
OemEvent = 0x000B,
148+
149+
/// <summary>
150+
/// PBT_APMRESUMEAUTOMATIC
151+
/// </summary>
152+
ResumeAutomatic = 0x0012,
153+
154+
/// <summary>
155+
/// PBT_POWERSETTINGCHANGE
156+
/// </summary>
157+
PowerSettingChange = 0x8013
158+
}

Source/HelloSwitcher.Core/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@
3232
// You can specify all the values or you can default the Build and Revision Numbers
3333
// by using the '*' as shown below:
3434
// [assembly: AssemblyVersion("1.0.*")]
35-
[assembly: AssemblyVersion("1.6.0.0")]
36-
[assembly: AssemblyFileVersion("1.6.0.0")]
35+
[assembly: AssemblyVersion("1.6.1.0")]
36+
[assembly: AssemblyFileVersion("1.6.1.0")]

Source/HelloSwitcher.Service/HelloSwitcherService.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Threading;
44
using System.Threading.Tasks;
55

6-
using HelloSwitcher.Helper;
76
using HelloSwitcher.Models;
87

98
namespace HelloSwitcher.Service;
@@ -17,7 +16,6 @@ public partial class HelloSwitcherService : ServiceBase
1716

1817
internal bool IsPaused { get; private set; }
1918

20-
private readonly Sample _check;
2119
private CancellationTokenSource _checkTokenSource;
2220
private IntPtr _notificationHandle;
2321

@@ -30,10 +28,6 @@ public HelloSwitcherService()
3028

3129
Settings = new Settings();
3230
Logger = new Logger("operation.service.log", "error.service.log");
33-
34-
_check = new Sample(
35-
TimeSpan.FromSeconds(1),
36-
(actionName, cancellationToken) => _switcher?.CheckAsync(actionName, cancellationToken));
3731
}
3832

3933
protected override async void OnStart(string[] args)
@@ -91,7 +85,7 @@ protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
9185
{
9286
case PowerBroadcastStatus.ResumeAutomatic:
9387
case PowerBroadcastStatus.ResumeSuspend:
94-
_check.Push($"Power Changed Check", _checkTokenSource?.Token ?? default);
88+
OnChanged($"Power Changed Check", _checkTokenSource?.Token ?? default);
9589
break;
9690
}
9791
}
@@ -118,9 +112,14 @@ protected override void OnCustomCommand(int command)
118112

119113
if (!IsPaused)
120114
{
121-
_check.Push($"Device Changed Check", _checkTokenSource?.Token ?? default);
115+
OnChanged($"Device Changed Check", _checkTokenSource?.Token ?? default);
122116
}
123117
break;
124118
}
125119
}
120+
121+
private async void OnChanged(string actionName, CancellationToken cancellationToken)
122+
{
123+
await _switcher?.CheckAsync(actionName, cancellationToken);
124+
}
126125
}

Source/HelloSwitcher.Service/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@
3232
// You can specify all the values or you can default the Build and Revision Numbers
3333
// by using the '*' as shown below:
3434
// [assembly: AssemblyVersion("1.0.*")]
35-
[assembly: AssemblyVersion("1.6.0.0")]
36-
[assembly: AssemblyFileVersion("1.6.0.0")]
35+
[assembly: AssemblyVersion("1.6.1.0")]
36+
[assembly: AssemblyFileVersion("1.6.1.0")]

Source/HelloSwitcher/App.xaml.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ public partial class App : Application
1616
internal Logger Logger { get; }
1717

1818
private DeviceSwitcher _switcher;
19-
private DeviceUsbWindowWatcher _watcher;
19+
private DeviceUsbWindowWatcher _deviceWatcher;
20+
private PowerSuspendResumeWatcher _powerWatcher;
2021
private NotifyIconHolder _holder;
2122

2223
internal static bool IsInteractive { get; } = Environment.UserInteractive;
@@ -50,8 +51,8 @@ protected override async void OnStartup(StartupEventArgs e)
5051
_switcher = new DeviceSwitcher(Settings, Logger);
5152
await _switcher.CheckAsync("Initial Check");
5253

53-
_watcher = new DeviceUsbWindowWatcher();
54-
_watcher.UsbDeviceChanged += async (_, e) =>
54+
_deviceWatcher = new DeviceUsbWindowWatcher();
55+
_deviceWatcher.UsbDeviceChanged += async (_, e) =>
5556
{
5657
await _switcher.CheckAsync("Device Changed Check", e.deviceName, e.exists);
5758

@@ -62,6 +63,18 @@ protected override async void OnStartup(StartupEventArgs e)
6263
}
6364
};
6465

66+
_powerWatcher = new PowerSuspendResumeWatcher();
67+
_powerWatcher.PowerStatusChanged += async (_, status) =>
68+
{
69+
switch (status)
70+
{
71+
case PowerStatus.ResumeAutomatic:
72+
case PowerStatus.ResumeSuspend:
73+
await _switcher.CheckAsync($"Resumed Check ({status})");
74+
break;
75+
}
76+
};
77+
6578
if (IsInteractive)
6679
{
6780
_holder = new NotifyIconHolder(
@@ -81,7 +94,8 @@ protected override async void OnStartup(StartupEventArgs e)
8194

8295
protected override void OnExit(ExitEventArgs e)
8396
{
84-
_watcher?.Dispose();
97+
_deviceWatcher?.Dispose();
98+
_powerWatcher?.Dispose();
8599
_holder?.Dispose();
86100
End();
87101

Source/HelloSwitcher/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,6 @@
5151
// You can specify all the values or you can default the Build and Revision Numbers
5252
// by using the '*' as shown below:
5353
// [assembly: AssemblyVersion("1.0.*")]
54-
[assembly: AssemblyVersion("1.6.0.0")]
55-
[assembly: AssemblyFileVersion("1.6.0.0")]
54+
[assembly: AssemblyVersion("1.6.1.0")]
55+
[assembly: AssemblyFileVersion("1.6.1.0")]
5656
[assembly: Guid("24aba232-090c-4c0e-8568-27e13b281fab")]

0 commit comments

Comments
 (0)