【问题标题】:Hook system power button in Windows在 Windows 中挂钩系统电源按钮
【发布时间】:2013-08-23 00:03:23
【问题描述】:

我有一台运行自定义服务的无头计算机,我想使用电源按钮启用/禁用该服务,而不必每次都远程连接。电脑也会做其他事情,所以不能关闭它。

是否可以在 Windows XP 及以上系统下挂接系统电源按钮,这样我的程序会在 Windows 启动关机/睡眠事件之前获得事件(在 PBT_APMQUERYSUSPEND 事件发出之前)?

【问题讨论】:

  • 你为什么需要这个?
  • 我有一台运行自定义服务的无头计算机,我想使用电源按钮启用/禁用该服务,而不必每次都进行远程连接。电脑也会做其他事情,所以不能关闭它。
  • 您将无法直接挂上电源按钮。您可能可以使用RegisterPowerSettingNotification() 检测关闭电源的尝试然后拒绝它,但我不知道该特定事件的相关GUId(或者它是否存在)。通过SetThreadExecutionState() 可以轻松防止关机,但随后会阻止关机,因此您不会收到通知。您最好将鼠标/键盘连接到机器上,然后使用挂钩来检测机器上的按钮按下。
  • 谢谢 Remy,我想通了,我会发布我自己的答案。

标签: winapi


【解决方案1】:

这确实是可能的,但它有点 hackish 并且需要两个完全不同的实现,具体取决于 Windows 版本。对于这两种方法,您都需要在电源选项中设置电源按钮以使计算机进入睡眠状态。

Windows XP 及以下:

您需要覆盖程序主窗口的WndProc 函数。在本机不支持此功能的 IDE 上,可以使用 user32 API 中的SetWindowLong 来完成。在您的自定义 WndProc 函数中,侦听 WM_POWERBROADCAST (0x218) 消息。如果您收到一条 wParam 为 PBT_APMQUERYSUSPEND (0x0) 的消息,请调用您想要的函数,然后返回 BROADCAST_QUERY_DENY (0x424D5144),而不是调用基本的 WndProc 函数。示例代码:

//At program start
//GWL_WNDPROC = -4
oldWndProc = SetWindowLong(this.hWnd, GWL_WNDPROC, &MyWndProc)

//In MyWndProc(hWnd, wMsg, wParam, lParam)
//WM_POWERBROADCAST = 0x218
//PBT_APMQUERYSUSPEND = 0x0
//BROADCAST_QUERY_DENY = 0x424D5144
if wMsg = WM_POWERBROADCAST && wParam = PBT_APMQUERYSUSPEND (
    //CALL YOUR FUNCTION HERE!
    return BROADCAST_QUERY_DENY
)
return CallWindowProc(oldWndProc, hWnd, wMsg, wParam, lParam)

//Before exiting
SetWindowLong(Me.hWnd, GWL_WNDPROC, oldWndProc)

Windows Vista 及更高版本: (感谢 Remy Lebeau 让我走上正轨)

您需要像 XP 一样覆盖 WndProc,但还需要在 kernel32 API 中调用 SetThreadExecutionState 以禁用睡眠模式,并在 user32 API 中调用 RegisterPowerSettingNotification 以侦听高级电源通知。您将特别听到GUID_SYSTEM_AWAYMODE 通知,该通知在系统被要求进入睡眠状态但无法这样做时发出。要轻松地将字符串转换为格式正确的LPCGUID,您可以在 rpcrt4.dll API 中使用UuidFromStringA。示例代码:

typedef struct UUID{
    int d1, d2, d3, d4
} LPCGUID;

//At program start
//GWL_WNDPROC = -4
//ES_CONTINUOUS = 0x80000000
//ES_SYSTEM_REQUIRED = 0x1
//ES_AWAYMODE_REQUIRED = 0x40
//GUID_SYSTEM_AWAYMODE = "98a7f580-01f7-48aa-9c0f-44352c29e5C0"
LPCGUID uid;
oldWndProc = SetWindowLong(this.hWnd, GWL_WNDPROC, &MyWndProc)
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED)
UuidFromStringA(*(GUID_SYSTEM_AWAYMODE), uid)
ps = RegisterPowerSettingNotification(this.hWnd, uid, 0)

//In MyWndProc(hWnd, wMsg, wParam, lParam)
//WM_POWERBROADCAST = 0x218
//PBT_POWERSETTINGCHANGE = 0x8013
if wMsg = WM_POWERBROADCAST && wParam = PBT_POWERSETTINGCHANGE (
    //CALL YOUR FUNCTION HERE!
    //You can additionally extract data from the lParam to verify
    //this is the notification you're waiting for (see below)
)
return CallWindowProc(oldWndProc, hWnd, wMsg, wParam, lParam)

//Before exiting
SetWindowLong(Me.hWnd, GWL_WNDPROC, oldWndProc)
UnregisterPowerSettingNotification(ps)

此方法的副作用是关闭您的物理屏幕(在无头机器上不是问题),并且还可能锁定您的会话。确保在睡眠后禁用密码提示以避免这种情况。在RegisterPowerSettingNotification 上还有一些其他有用的信息可用here 显示如何从WndProc 函数中的lParam 中提取信息,以防您需要有关通知的更多信息。玩得开心;)

【讨论】:

  • “在原生不支持 [覆盖 WinProc] 的 IDE 上”。 IDE 与此无关。完全没有。完全非骇客的方法是在正常的WndProc 中正常处理WM_POWERBROADCAST
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多