如何在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()); }
关键说明:
- 窗口创建:必须创建一个隐藏窗口作为消息接收者,Windows的电源通知基于窗口消息机制,没有窗口就无法捕获事件。
- 消息循环:
GetMessage、TranslateMessage、DispatchMessage组成的消息循环是接收窗口消息的核心,必须保持运行才能监听事件。 - 事件解析:在窗口过程中判断
WM_POWERBROADCAST消息,通过wParam的值区分睡眠、唤醒等具体事件。 - 服务权限:如果用NSSM将应用作为服务运行,需在NSSM的
Log On选项卡勾选Allow service to interact with desktop,确保服务有足够权限接收系统电源事件。
内容的提问来源于stack exchange,提问作者Z T
相关产品推荐
相关产品推荐

