Files
duplicati/Duplicati/Library/WindowsModules/PowerManagementModule.cs
T

194 lines
6.9 KiB
C#
Raw Normal View History

2025-10-15 17:58:15 +02:00
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#nullable enable
using System;
2025-10-18 12:36:08 +02:00
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
2025-10-15 17:58:15 +02:00
using Duplicati.Library.Interface;
namespace Duplicati.Library.WindowsModules;
/// <summary>
2025-10-18 14:04:35 +02:00
/// Provides power management functionality for Windows using the powrprof callback API (Windows 8+).
2025-10-15 17:58:15 +02:00
/// </summary>
2025-10-18 12:36:08 +02:00
[SupportedOSPlatform("windows")]
public sealed class PowerManagementModule : IPowerModeProvider, IDisposable
2025-10-15 17:58:15 +02:00
{
/// <summary>
2025-10-18 13:51:16 +02:00
/// Registration handle returned from PowerRegisterSuspendResumeNotification.
2025-10-15 17:58:15 +02:00
/// </summary>
2025-10-18 13:51:16 +02:00
private IntPtr _registrationHandle = IntPtr.Zero;
2025-10-18 12:36:08 +02:00
2025-10-15 17:58:15 +02:00
/// <summary>
2025-10-18 13:51:16 +02:00
/// Keep a reference to the delegate to prevent it from being garbage collected.
2025-10-15 17:58:15 +02:00
/// </summary>
2025-10-18 13:51:16 +02:00
private DEVICE_NOTIFY_CALLBACK_ROUTINE? _callbackRef;
2025-10-18 12:36:08 +02:00
2025-10-18 13:51:16 +02:00
/// <inheritdoc />
2025-10-15 17:58:15 +02:00
public Action? OnResume { get; set; }
2025-10-18 12:36:08 +02:00
2025-10-18 13:51:16 +02:00
/// <inheritdoc />
2025-10-15 17:58:15 +02:00
public Action? OnSuspend { get; set; }
/// <summary>
2025-10-18 13:51:16 +02:00
/// Initializes a new instance. Required for reflection-based loading.
2025-10-15 17:58:15 +02:00
/// </summary>
2025-10-18 12:36:08 +02:00
public PowerManagementModule() : this(null)
2025-10-15 17:58:15 +02:00
{
2025-10-18 12:36:08 +02:00
}
/// <summary>
2025-10-18 13:51:16 +02:00
/// Initializes a new instance. The parameter is ignored in this implementation.
2025-10-18 12:36:08 +02:00
/// </summary>
2025-10-18 13:51:16 +02:00
/// <param name="_">Unused. Present for compatibility with previous constructor.</param>
public PowerManagementModule(Guid? _)
2025-10-18 12:36:08 +02:00
{
2025-10-18 13:51:16 +02:00
RegisterSuspendResumeCallback();
2025-10-15 17:58:15 +02:00
}
/// <summary>
2025-10-18 13:51:16 +02:00
/// Registers the suspend/resume callback using powrprof (Windows 8+).
2025-10-15 17:58:15 +02:00
/// </summary>
2025-10-18 13:51:16 +02:00
private void RegisterSuspendResumeCallback()
2025-10-15 17:58:15 +02:00
{
2025-10-18 13:51:16 +02:00
_callbackRef = new DEVICE_NOTIFY_CALLBACK_ROUTINE(SuspendResumeCallback);
var parameters = new DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
2025-10-18 12:36:08 +02:00
{
2025-10-18 13:51:16 +02:00
Callback = _callbackRef,
Context = IntPtr.Zero
2025-10-18 12:36:08 +02:00
};
2025-10-15 17:58:15 +02:00
2025-10-18 13:51:16 +02:00
// DEVICE_NOTIFY_CALLBACK delivers notifications via the provided delegate.
uint status = PowerRegisterSuspendResumeNotification(DEVICE_NOTIFY_CALLBACK, ref parameters, out _registrationHandle);
2025-10-18 12:36:08 +02:00
2025-10-18 13:51:16 +02:00
// If registration fails, we keep a no-op provider (no window fallback by design).
// STATUS_SUCCESS is 0.
if (status != STATUS_SUCCESS)
2025-10-18 12:36:08 +02:00
{
2025-10-18 13:51:16 +02:00
_registrationHandle = IntPtr.Zero;
2025-10-18 12:36:08 +02:00
}
}
2025-10-18 14:04:35 +02:00
/// <summary>
/// Constant indicating successful operation.
/// </summary>
private const uint STATUS_SUCCESS = 0;
2025-10-18 12:36:08 +02:00
/// <summary>
2025-10-18 13:51:16 +02:00
/// Callback invoked by the system for suspend/resume notifications.
2025-10-18 12:36:08 +02:00
/// </summary>
2025-10-18 13:51:16 +02:00
/// <param name="context">User-provided context (unused).</param>
/// <param name="type">Power event type (e.g., PBT_APMSUSPEND, PBT_APMRESUMEAUTOMATIC).</param>
/// <param name="setting">Additional info (unused).</param>
/// <returns>STATUS_SUCCESS (0) on success.</returns>
private uint SuspendResumeCallback(IntPtr context, uint type, IntPtr setting)
2025-10-18 12:36:08 +02:00
{
2025-10-18 13:51:16 +02:00
switch (type)
2025-10-15 17:58:15 +02:00
{
2025-10-18 13:51:16 +02:00
case PBT_APMSUSPEND:
OnSuspend?.Invoke();
break;
case PBT_APMRESUMEAUTOMATIC:
case PBT_APMRESUMESUSPEND:
OnResume?.Invoke();
break;
2025-10-18 12:36:08 +02:00
}
2025-10-18 13:51:16 +02:00
return STATUS_SUCCESS;
2025-10-15 17:58:15 +02:00
}
2025-10-18 13:51:16 +02:00
/// <inheritdoc />
2025-10-15 17:58:15 +02:00
public void Dispose()
{
2025-10-18 13:51:16 +02:00
if (_registrationHandle != IntPtr.Zero)
2025-10-18 12:36:08 +02:00
{
2025-10-18 13:51:16 +02:00
PowerUnregisterSuspendResumeNotification(_registrationHandle);
_registrationHandle = IntPtr.Zero;
2025-10-18 12:36:08 +02:00
}
2025-10-18 13:51:16 +02:00
_callbackRef = null;
2025-10-15 17:58:15 +02:00
}
2025-10-18 12:36:08 +02:00
2025-10-18 14:04:35 +02:00
/// <summary>
/// Power broadcast event for system suspend.
/// </summary>
2025-10-18 12:36:08 +02:00
private const uint PBT_APMSUSPEND = 0x0004;
2025-10-18 14:04:35 +02:00
/// <summary>
/// Power broadcast event for automatic resume from suspend.
/// </summary>
2025-10-18 12:36:08 +02:00
private const uint PBT_APMRESUMEAUTOMATIC = 0x0012;
2025-10-18 14:04:35 +02:00
/// <summary>
/// Power broadcast event for resume from suspend.
/// </summary>
2025-10-18 12:36:08 +02:00
private const uint PBT_APMRESUMESUSPEND = 0x0007;
2025-10-18 14:04:35 +02:00
/// <summary>
/// Flag indicating that the recipient is a callback routine.
/// </summary>
2025-10-18 13:51:16 +02:00
private const uint DEVICE_NOTIFY_CALLBACK = 2;
2025-10-18 12:36:08 +02:00
/// <summary>
2025-10-18 13:51:16 +02:00
/// Structure used to subscribe to suspend/resume notifications via callback.
2025-10-18 12:36:08 +02:00
/// </summary>
[StructLayout(LayoutKind.Sequential)]
2025-10-18 13:51:16 +02:00
private struct DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
2025-10-18 12:36:08 +02:00
{
2025-10-18 14:04:35 +02:00
/// <summary>
/// The callback routine to receive notifications.
/// </summary>
2025-10-18 13:51:16 +02:00
public DEVICE_NOTIFY_CALLBACK_ROUTINE Callback;
2025-10-18 14:04:35 +02:00
/// <summary>
/// User-defined context passed to the callback.
/// </summary>
2025-10-18 13:51:16 +02:00
public IntPtr Context;
2025-10-18 12:36:08 +02:00
}
/// <summary>
2025-10-18 13:51:16 +02:00
/// Callback routine signature for device/power notifications.
/// Return STATUS_SUCCESS (0) on success.
2025-10-18 12:36:08 +02:00
/// </summary>
2025-10-18 13:51:16 +02:00
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate uint DEVICE_NOTIFY_CALLBACK_ROUTINE(IntPtr Context, uint Type, IntPtr Setting);
2025-10-18 12:36:08 +02:00
/// <summary>
2025-10-18 13:51:16 +02:00
/// Registers to receive power suspend/resume notifications via a callback.
2025-10-18 12:36:08 +02:00
/// </summary>
2025-10-18 13:51:16 +02:00
/// <param name="Flags">Must be DEVICE_NOTIFY_CALLBACK for callback delivery.</param>
/// <param name="Parameters">Callback and context parameters.</param>
/// <param name="Handle">Out registration handle.</param>
/// <returns>STATUS_SUCCESS (0) on success.</returns>
[DllImport("powrprof.dll", SetLastError = true)]
private static extern uint PowerRegisterSuspendResumeNotification(
uint Flags,
ref DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS Parameters,
out IntPtr Handle);
2025-10-18 12:36:08 +02:00
/// <summary>
2025-10-18 13:51:16 +02:00
/// Unregisters a previous suspend/resume notification registration.
2025-10-18 12:36:08 +02:00
/// </summary>
2025-10-18 13:51:16 +02:00
/// <param name="Handle">The registration handle.</param>
/// <returns>STATUS_SUCCESS (0) on success.</returns>
[DllImport("powrprof.dll", SetLastError = true)]
private static extern uint PowerUnregisterSuspendResumeNotification(IntPtr Handle);
2025-10-15 17:58:15 +02:00
}