Repository navigation
Expand file tree
/
Copy pathWindowsPowerRequest.cs
More file actions
242 lines (212 loc) · 8.4 KB
/
Copy pathWindowsPowerRequest.cs
File metadata and controls
242 lines (212 loc) · 8.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace WindowsDeviceControl;
/// <summary>The Windows idle transition held by a power request.</summary>
public enum WindowsPowerRequestKind
{
/// <summary>Keeps the display on.</summary>
Display = 0,
/// <summary>Blocks automatic sleep while allowing the display to time out.</summary>
System = 1
}
/// <summary>Owns a Windows power-request handle and its diagnostic string. Dispose releases both.</summary>
/// <remarks>Windows may limit battery-powered requests; explicit user sleep still wins. Methods are thread-safe.</remarks>
public sealed class WindowsPowerRequest : IDisposable
{
private readonly IPowerRequestApi _api;
private readonly object _gate = new();
private readonly int _kind;
private readonly string _reason;
private RequestHandle? _handle;
private bool _held, _disposed;
/// <summary>Creates an inert owner; no Windows request exists until Acquire.</summary>
/// <param name="reason">Diagnostic text shown by Windows power-request tools.</param>
/// <param name="kind">The idle transition to hold.</param>
/// <exception cref="ArgumentNullException"><paramref name="reason" /> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="kind" /> is not Display or System.</exception>
public WindowsPowerRequest(string reason, WindowsPowerRequestKind kind = WindowsPowerRequestKind.System)
: this(reason, kind, new NativePowerRequestApi())
{
}
internal WindowsPowerRequest(string reason, WindowsPowerRequestKind kind, IPowerRequestApi api)
{
ArgumentNullException.ThrowIfNull(reason);
if (kind is not WindowsPowerRequestKind.Display and not WindowsPowerRequestKind.System)
{
throw new ArgumentOutOfRangeException(nameof(kind));
}
_reason = reason;
_kind = (int)kind;
_api = api;
}
/// <summary>Whether Windows accepted an acquisition that this owner has not released or disposed.</summary>
/// <remarks>This tracks the handle's request, not proof that Windows will prevent the transition.</remarks>
public bool IsHeld
{
get
{
lock (_gate)
{
return _held;
}
}
}
/// <summary>Closes the kernel request and releases the reason buffer. Idempotent.</summary>
public void Dispose()
{
lock (_gate)
{
if (_disposed)
{
return;
}
_disposed = true;
_handle?.Dispose();
_handle = null;
_held = false;
}
}
/// <summary>Sets the request once. Repeated successful acquisition is inert; native failures throw Win32Exception.</summary>
/// <exception cref="ObjectDisposedException">This owner has already been disposed.</exception>
/// <exception cref="Win32Exception">Creating or setting the request failed; IsHeld remains false.</exception>
public void Acquire()
{
lock (_gate)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (_held)
{
return;
}
_handle ??= new RequestHandle(_api, _reason);
if (!_api.Set(_handle.DangerousGetHandle(), _kind))
{
throw new Win32Exception(_api.LastError);
}
_held = true;
}
}
/// <summary>Clears the request once. A native failure throws and keeps IsHeld true.</summary>
/// <remarks>Does nothing when not held, including after disposal. A failure is not retried automatically.</remarks>
/// <exception cref="Win32Exception">Windows refused to clear the outstanding request.</exception>
public void Release()
{
lock (_gate)
{
if (!_held)
{
return;
}
if (!_api.Clear(_handle!.DangerousGetHandle(), _kind))
{
throw new Win32Exception(_api.LastError);
}
_held = false;
}
}
private sealed class RequestHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private readonly IPowerRequestApi _api;
private nint _reason;
internal RequestHandle(IPowerRequestApi api, string reason) : base(true)
{
_api = api;
_reason = Marshal.StringToHGlobalUni(reason);
try
{
SetHandle(api.Create(_reason));
if (IsInvalid)
{
throw new Win32Exception(api.LastError);
}
}
catch
{
Marshal.FreeHGlobal(_reason);
_reason = 0;
throw;
}
}
protected override bool ReleaseHandle()
{
_api.Close(handle);
Marshal.FreeHGlobal(_reason);
_reason = 0;
return true;
}
}
}
/// <summary>Native power-request boundary; the owner retains the handle and reason buffer until Close.</summary>
internal interface IPowerRequestApi
{
/// <summary>Win32 error from the immediately preceding failed native operation.</summary>
int LastError { get; }
/// <summary>Creates a kernel power-request handle borrowing the reason string.</summary>
/// <param name="reason">NUL-terminated UTF-16 diagnostic string kept alive until the handle is closed.</param>
/// <returns>An owned request handle, or a zero/invalid handle with LastError set.</returns>
nint Create(nint reason);
/// <summary>Adds one native request count of the selected kind.</summary>
/// <param name="request">Live request handle owned by the caller.</param>
/// <param name="kind">Native POWER_REQUEST_TYPE value.</param>
/// <returns>True when accepted; false with LastError on refusal.</returns>
bool Set(nint request, int kind);
/// <summary>Clears one matching request count.</summary>
/// <param name="request">Live request handle with a previously accepted request.</param>
/// <param name="kind">Same native request type used for Set.</param>
/// <returns>True when accepted; false with LastError while ownership remains unchanged.</returns>
bool Clear(nint request, int kind);
/// <summary>Closes the handle, releasing all outstanding request counts.</summary>
/// <param name="request">Handle to release once; its reason buffer can be freed after this call.</param>
void Close(nint request);
}
internal sealed partial class NativePowerRequestApi : IPowerRequestApi
{
/// <inheritdoc />
public nint Create(nint reason)
{
ReasonContext context = new() { Version = 0, Flags = 1, LocalizedReasonModuleOrSimpleString = reason };
return PowerCreateRequest(in context);
}
/// <inheritdoc />
public bool Set(nint request, int kind)
{
return PowerSetRequest(request, kind);
}
/// <inheritdoc />
public bool Clear(nint request, int kind)
{
return PowerClearRequest(request, kind);
}
/// <inheritdoc />
public void Close(nint request)
{
CloseHandle(request);
}
/// <inheritdoc />
public int LastError => Marshal.GetLastPInvokeError();
[LibraryImport("kernel32.dll", SetLastError = true)]
private static partial nint PowerCreateRequest(in ReasonContext context);
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool PowerSetRequest(nint request, int kind);
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool PowerClearRequest(nint request, int kind);
[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool CloseHandle(nint handle);
/// <summary>
/// REASON_CONTEXT declared through its detailed arm, the larger union member, so the managed size
/// matches the native one on both bitnesses. The simple string shares the first field's offset.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct ReasonContext
{
internal uint Version, Flags;
internal nint LocalizedReasonModuleOrSimpleString;
internal uint LocalizedReasonId, ReasonStringCount;
internal nint ReasonStrings;
}
}