You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Node.js服务中检测Windows系统睡眠状态?

问题分析与解决方案

你的代码存在几个核心问题导致回调无法触发:

  • 无效的接收句柄:RegisterPowerSettingNotification在DEVICE_NOTIFY_WINDOW_HANDLE模式下需要传入有效的窗口句柄,你传了null,系统无法将电源事件消息发送到不存在的窗口。
  • 错误的回调方式:这个API不直接接受回调函数,而是通过向指定窗口发送WM_POWERBROADCAST消息传递通知,用ffi.Callback完全不符合它的工作机制。
  • 错误的GUID:你使用的GUID_SYSTEM_AWAYMODE对应系统离开模式,要捕获睡眠事件,应该用GUID_SYSTEM_POWER_STATE_CHANGE。

以下是修正后的完整代码:

const ffi = require('ffi-napi');
const ref = require('ref-napi');
const StructType = require('ref-struct-napi');

// 定义GUID结构体
const GUID = StructType({
  Data1: 'ulong',
  Data2: 'ushort',
  Data3: 'ushort',
  Data4: 'uint8',
  Data5: 'uint8',
  Data6: 'uint8',
  Data7: 'uint8',
  Data8: 'uint8',
  Data9: 'uint8',
  Data10: 'uint8',
  Data11: 'uint8'
});

// 系统电源状态变更的GUID(对应睡眠、唤醒等事件)
const GUID_SYSTEM_POWER_STATE_CHANGE = new GUID({
  Data1: 0x27354044,
  Data2: 0x0E14,
  Data3: 0x4A90,
  Data4: 0xB5,
  Data5: 0x3F,
  Data6: 0x4F,
  Data7: 0x82,
  Data8: 0x22,
  Data9: 0x8C,
  Data10: 0x68,
  Data11: 0x53
});

// 绑定Windows API
const user32 = ffi.Library('user32', {
  'CreateWindowExA': ['pointer', ['uint', 'string', 'string', 'uint', 'int', 'int', 'int', 'int', 'pointer', 'pointer', 'pointer', 'pointer']],
  'RegisterPowerSettingNotification': ['pointer', ['pointer', ref.refType(GUID), 'uint']],
  'GetMessageA': ['int', ['pointer', 'pointer', 'uint', 'uint']],
  'TranslateMessage': ['int', ['pointer']],
  'DispatchMessageA': ['pointer', ['pointer']],
  'DefWindowProcA': ['pointer', ['pointer', 'uint', 'pointer', 'pointer']],
  'RegisterClassExA': ['ushort', ['pointer']]
});

const kernel32 = ffi.Library('kernel32', {
  'GetModuleHandleA': ['pointer', ['string']]
});

// 定义MSG结构体
const MSG = StructType({
  hwnd: 'pointer',
  message: 'uint',
  wParam: 'pointer',
  lParam: 'pointer',
  time: 'uint',
  pt: StructType({ x: 'long', y: 'long' })
});

// 注册窗口类
const WNDCLASSEXA = StructType({
  cbSize: 'uint',
  style: 'uint',
  lpfnWndProc: 'pointer',
  cbClsExtra: 'int',
  cbWndExtra: 'int',
  hInstance: 'pointer',
  hIcon: 'pointer',
  hCursor: 'pointer',
  hbrBackground: 'pointer',
  lpszMenuName: 'string',
  lpszClassName: 'string',
  hIconSm: 'pointer'
});

const hInstance = kernel32.GetModuleHandleA(null);

// 窗口过程函数,处理WM_POWERBROADCAST消息
const windowProc = ffi.Callback('pointer', ['pointer', 'uint', 'pointer', 'pointer'], (hwnd, msg, wParam, lParam) => {
  const WM_POWERBROADCAST = 0x0218;
  const PBT_APMSUSPEND = 0x0004; // 系统即将进入睡眠
  const PBT_APMRESUMESUSPEND = 0x0007; // 系统从睡眠唤醒

  if (msg === WM_POWERBROADCAST) {
    const eventType = wParam.readUInt32LE();
    if (eventType === PBT_APMSUSPEND) {
      console.log('系统即将进入睡眠');
      // 在这里执行你的业务代码
    } else if (eventType === PBT_APMRESUMESUSPEND) {
      console.log('系统已从睡眠唤醒');
    }
    return ref.alloc('uint', 1); // 返回TRUE表示处理了消息
  }

  return user32.DefWindowProcA(hwnd, msg, wParam, lParam);
});

const wc = new WNDCLASSEXA({
  cbSize: WNDCLASSEXA.size,
  style: 0,
  lpfnWndProc: windowProc,
  cbClsExtra: 0,
  cbWndExtra: 0,
  hInstance: hInstance,
  hIcon: null,
  hCursor: null,
  hbrBackground: null,
  lpszMenuName: null,
  lpszClassName: 'PowerMonitorWindowClass',
  hIconSm: null
});

user32.RegisterClassExA(wc.ref());

// 创建隐藏窗口
const hwnd = user32.CreateWindowExA(
  0, // 扩展样式
  'PowerMonitorWindowClass',
  'PowerMonitorWindow',
  0, // 窗口样式
  0, 0, 0, 0, // 位置和大小
  null, null, hInstance, null
);

if (!hwnd) {
  console.error('创建窗口失败');
  process.exit(1);
}

// 注册电源状态变更通知
const DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000;
const notificationHandle = user32.RegisterPowerSettingNotification(hwnd, GUID_SYSTEM_POWER_STATE_CHANGE.ref(), DEVICE_NOTIFY_WINDOW_HANDLE);

if (!notificationHandle) {
  console.error('注册电源通知失败');
  process.exit(1);
}
console.log('成功注册电源通知');

// 启动消息循环,必须保持运行才能接收消息
const msg = new MSG();
while (user32.GetMessageA(msg.ref(), null, 0, 0) !== 0) {
  user32.TranslateMessage(msg.ref());
  user32.DispatchMessageA(msg.ref());
}

关键说明:

  1. 窗口创建:必须创建一个隐藏窗口作为消息接收者,Windows的电源通知基于窗口消息机制,没有窗口就无法捕获事件。
  2. 消息循环:GetMessage、TranslateMessage、DispatchMessage组成的消息循环是接收窗口消息的核心,必须保持运行才能监听事件。
  3. 事件解析:在窗口过程中判断WM_POWERBROADCAST消息,通过wParam的值区分睡眠、唤醒等具体事件。
  4. 服务权限:如果用NSSM将应用作为服务运行,需在NSSM的Log On选项卡勾选Allow service to interact with desktop,确保服务有足够权限接收系统电源事件。

内容的提问来源于stack exchange,提问作者Z T

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 15:07:29