// 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;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Duplicati.Library.Interface;
namespace Duplicati.Library.WindowsModules;
///
/// Provides power management functionality for Windows using the powrprof callback API (Windows 8+).
///
[SupportedOSPlatform("windows")]
public sealed class PowerManagementModule : IPowerModeProvider, IDisposable
{
///
/// Registration handle returned from PowerRegisterSuspendResumeNotification.
///
private IntPtr _registrationHandle = IntPtr.Zero;
///
/// Keep a reference to the delegate to prevent it from being garbage collected.
///
private DEVICE_NOTIFY_CALLBACK_ROUTINE? _callbackRef;
///
public Action? OnResume { get; set; }
///
public Action? OnSuspend { get; set; }
///
/// Initializes a new instance. Required for reflection-based loading.
///
public PowerManagementModule() : this(null)
{
}
///
/// Initializes a new instance. The parameter is ignored in this implementation.
///
/// Unused. Present for compatibility with previous constructor.
public PowerManagementModule(Guid? _)
{
RegisterSuspendResumeCallback();
}
///
/// Registers the suspend/resume callback using powrprof (Windows 8+).
///
private void RegisterSuspendResumeCallback()
{
_callbackRef = new DEVICE_NOTIFY_CALLBACK_ROUTINE(SuspendResumeCallback);
var parameters = new DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
{
Callback = _callbackRef,
Context = IntPtr.Zero
};
// DEVICE_NOTIFY_CALLBACK delivers notifications via the provided delegate.
uint status = PowerRegisterSuspendResumeNotification(DEVICE_NOTIFY_CALLBACK, ref parameters, out _registrationHandle);
// If registration fails, we keep a no-op provider (no window fallback by design).
// STATUS_SUCCESS is 0.
if (status != STATUS_SUCCESS)
{
_registrationHandle = IntPtr.Zero;
}
}
///
/// Constant indicating successful operation.
///
private const uint STATUS_SUCCESS = 0;
///
/// Callback invoked by the system for suspend/resume notifications.
///
/// User-provided context (unused).
/// Power event type (e.g., PBT_APMSUSPEND, PBT_APMRESUMEAUTOMATIC).
/// Additional info (unused).
/// STATUS_SUCCESS (0) on success.
private uint SuspendResumeCallback(IntPtr context, uint type, IntPtr setting)
{
switch (type)
{
case PBT_APMSUSPEND:
OnSuspend?.Invoke();
break;
case PBT_APMRESUMEAUTOMATIC:
case PBT_APMRESUMESUSPEND:
OnResume?.Invoke();
break;
}
return STATUS_SUCCESS;
}
///
public void Dispose()
{
if (_registrationHandle != IntPtr.Zero)
{
PowerUnregisterSuspendResumeNotification(_registrationHandle);
_registrationHandle = IntPtr.Zero;
}
_callbackRef = null;
}
///
/// Power broadcast event for system suspend.
///
private const uint PBT_APMSUSPEND = 0x0004;
///
/// Power broadcast event for automatic resume from suspend.
///
private const uint PBT_APMRESUMEAUTOMATIC = 0x0012;
///
/// Power broadcast event for resume from suspend.
///
private const uint PBT_APMRESUMESUSPEND = 0x0007;
///
/// Flag indicating that the recipient is a callback routine.
///
private const uint DEVICE_NOTIFY_CALLBACK = 2;
///
/// Structure used to subscribe to suspend/resume notifications via callback.
///
[StructLayout(LayoutKind.Sequential)]
private struct DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
{
///
/// The callback routine to receive notifications.
///
public DEVICE_NOTIFY_CALLBACK_ROUTINE Callback;
///
/// User-defined context passed to the callback.
///
public IntPtr Context;
}
///
/// Callback routine signature for device/power notifications.
/// Return STATUS_SUCCESS (0) on success.
///
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate uint DEVICE_NOTIFY_CALLBACK_ROUTINE(IntPtr Context, uint Type, IntPtr Setting);
///
/// Registers to receive power suspend/resume notifications via a callback.
///
/// Must be DEVICE_NOTIFY_CALLBACK for callback delivery.
/// Callback and context parameters.
/// Out registration handle.
/// STATUS_SUCCESS (0) on success.
[DllImport("powrprof.dll", SetLastError = true)]
private static extern uint PowerRegisterSuspendResumeNotification(
uint Flags,
ref DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS Parameters,
out IntPtr Handle);
///
/// Unregisters a previous suspend/resume notification registration.
///
/// The registration handle.
/// STATUS_SUCCESS (0) on success.
[DllImport("powrprof.dll", SetLastError = true)]
private static extern uint PowerUnregisterSuspendResumeNotification(IntPtr Handle);
}