132

据我所知,Windows 没有提供 API 函数来判断哪个应用程序注册了全局热键(通过 RegisterHotkey)。如果RegisterHotkey返回false,我只能发现一个热键被注册,而不是谁“拥有”热键。

在没有直接API的情况下,能有迂回的方式吗?Windows 维护与每个注册的热键关联的句柄——这有点让人抓狂,应该没有办法获得这些信息。

可能不起作用的示例:发送(模拟)注册的热键,然后拦截 Windows 将发送给注册它的进程的热键消息。首先,我认为拦截消息不会泄露目标窗口句柄。其次,即使有可能,这样做也是一件坏事,因为发送热键会触发各种程序的各种潜在有害活动。

这并不重要,但我经常看到对此类功能的请求,并且我自己也是注册热键的应用程序的受害者,甚至没有在 UI 或文档的任何地方公开它。

(在Delphi工作,只是WinAPI的学徒,请善待。)

4

7 回答 7

96

一种可能的方法是使用Visual Studio 工具 Spy++

试试这个:

  1. 运行工具(对我来说,它位于C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\spyxx_amd64.exe
  2. 在菜单栏中,选择Spy -> Log messages...(或点击Ctrl+ M
  3. 在附加窗口框架中检查系统中的所有窗口
  4. 切换到消息选项卡
  5. 单击全部清除按钮
  6. 在列表框中选择WM_HOTKEY,或选中消息组中的键盘(如果您对更多潜在噪音感到满意)
  7. 单击确定按钮
  8. 按有问题的热键(例如Win+ R
  9. 选择Messages (All Windows)WM_HOTKEY窗口中的行,右键单击,然后在上下文菜单中选择Properties...
  10. 在“消息属性”对话框中,单击“窗口句柄”链接(这将是接收消息的窗口的句柄)
  11. 单击窗口属性对话框上的同步按钮。这将在主 Spy++ 窗口树视图中显示该窗口。
  12. 在“窗口属性”对话框中,选择“进程”选项卡
  13. 单击进程 ID链接。这将向您展示该过程(在我的Win+R案例中EXPLORER:)
于 2017-04-26T21:46:24.010 回答
21

你的问题引起了我的兴趣,所以我做了一些挖掘,不幸的是,我没有给你正确的答案,我想我会分享我所拥有的。

我发现这个在 1998 年编写的创建键盘钩子(在 Delphi 中)的示例,但在 Delphi 2007 中可以通过一些调整进行编译。

这是一个SetWindowsHookEx通过回调函数调用的 DLL,然后该回调函数可以拦截击键:在这种情况下,它正在修改它们以获得乐趣,将左光标更改为右光标等。然后一个简单的应用程序调用 DLL 并返回报告它的结果基于 TTimer 事件。如果您有兴趣,我可以发布基于 Delphi 2007 的代码。

它有详细的文档和评论,您可能可以将其用作计算按键位置的基础。如果您可以获取发送击键的应用程序的句柄,您可以通过这种方式进行追踪。有了这个句柄,您就可以很容易地获得所需的信息。

其他应用程序已尝试通过查看其快捷方式来确定热键,因为它们可以包含快捷键,这只是热键的另一个术语。然而,大多数应用程序并不倾向于设置此属性,因此它可能不会返回太多。如果您对该路线感兴趣,Delphi 可以访问IShellLinkCOM 接口,您可以使用该接口加载快捷方式并获取其热键:

uses ShlObj, ComObj, ShellAPI, ActiveX, CommCtrl;

procedure GetShellLinkHotKey;
var
  LinkFile : WideString;
  SL: IShellLink;
  PF: IPersistFile;

  HotKey : Word;
  HotKeyMod: Byte;
  HotKeyText : string;
begin
  LinkFile := 'C:\Temp\Temp.lnk';

  OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER, IShellLink, SL));

  // The IShellLink implementer must also support the IPersistFile
  // interface. Get an interface pointer to it.
  PF := SL as IPersistFile;

  // Load file into IPersistFile object
  OleCheck(PF.Load(PWideChar(LinkFile), STGM_READ));

  // Resolve the link by calling the Resolve interface function.
  OleCheck(SL.Resolve(0, SLR_ANY_MATCH or SLR_NO_UI));

  // Get hotkey info
  OleCheck(SL.GetHotKey(HotKey));

  // Extract the HotKey and Modifier properties.
  HotKeyText := '';
  HotKeyMod := Hi(HotKey);

  if (HotKeyMod and HOTKEYF_ALT) = HOTKEYF_ALT then
    HotKeyText := 'ALT+';
  if (HotKeyMod and HOTKEYF_CONTROL) = HOTKEYF_CONTROL then
    HotKeyText := HotKeyText + 'CTRL+';
  if (HotKeyMod and HOTKEYF_SHIFT) = HOTKEYF_SHIFT then
    HotKeyText := HotKeyText + 'SHIFT+';
  if (HotKeyMod and HOTKEYF_EXT) = HOTKEYF_EXT then
    HotKeyText := HotKeyText + 'Extended+';

  HotKeyText := HotKeyText + Char(Lo(HotKey));

  if (HotKeyText = '') or (HotKeyText = #0) then
    HotKeyText := 'None';

  ShowMessage('Shortcut Key - ' + HotKeyText);
end;

如果您可以访问Safari 联机丛书,那么在 Steve Teixeira 和 Xavier Pacheco 编写的 Borland Delphi 6 开发人员指南中有一个关于使用快捷方式/shell 链接的好部分。我上面的例子是来自那里和这个网站的屠宰版本。

希望有帮助!

于 2009-05-20T09:43:13.683 回答
11

经过一番研究,您似乎需要访问 MS 用来存储热键的内部结构。ReactOS 有一个洁净室实现,它GetHotKey通过迭代内部列表并提取与调用的参数匹配的热键来实现调用。

根据 ReactOS 的实现与 MS 实现的接近程度,您可能能够在内存中四处寻找结构,但这超出了我的想象......

BOOL FASTCALL
GetHotKey (UINT fsModifiers,
           UINT vk,
           struct _ETHREAD **Thread,
           HWND *hWnd,
           int *id)
{
   PHOT_KEY_ITEM HotKeyItem;

   LIST_FOR_EACH(HotKeyItem, &gHotkeyList, HOT_KEY_ITEM, ListEntry)
   {
      if (HotKeyItem->fsModifiers == fsModifiers &&
            HotKeyItem->vk == vk)
      {
         if (Thread != NULL)
            *Thread = HotKeyItem->Thread;

         if (hWnd != NULL)
            *hWnd = HotKeyItem->hWnd;

         if (id != NULL)
            *id = HotKeyItem->id;

         return TRUE;
      }
   }

   return FALSE;
}

我认为sysinternals 上的这个线程是由与这个问题相关的人提出的,但我想我还是会链接到它以将两者保持在一起。该线程看起来非常有趣,但我怀疑在不访问 MS 内部的情况下需要进行一些深入的探索才能弄清楚这一点。

于 2009-05-10T22:09:31.393 回答
3

在我的脑海中,您可能会尝试使用 EnumWindows 枚举所有窗口,然后在回调中,将 WM_GETHOTKEY 发送到每个窗口。

编辑:显然我错了。MSDN有更多信息:

WM_HOTKEY 与 WM_GETHOTKEY 和 WM_SETHOTKEY 热键无关。WM_HOTKEY 消息针对通用热键发送,而 WM_SETHOTKEY 和 WM_GETHOTKEY 消息与窗口激活热键相关。

注意:是一个声称具有您正在寻找的功能的程序。你可以试试反编译。

于 2009-05-19T01:46:53.087 回答
1

另一个线程提到了一个全局 NT 级键盘钩子:

重新分配/覆盖热键 (Win + L) 以锁定窗口

也许您可以通过这种方式获取调用钩子的进程的句柄,然后您可以将其解析为进程名称

(免责声明:我把它放在我的书签里,还没有真正尝试过/测试过)

于 2009-05-07T13:58:07.957 回答
1

我知道您可以在您自己的进程中的任何窗口中截取消息流——我们过去在 VB6 中称为子类化。(虽然我不记得该功能,也许是 SetWindowLong?)我不确定您是否可以为自己进程之外的窗口执行此操作。但是为了这篇文章的缘故,让我们假设你找到了一种方法来做到这一点。然后您可以简单地拦截所有顶级窗口的消息,监视 WM_HOTKEY 消息。你不可能马上知道所有的键,但是当它们被按下时,你可以很容易地找出哪个应用程序正在使用它们。如果您将结果保存到磁盘并在每次运行监视器应用程序时重新加载,您可以随着时间的推移提高应用程序的性能。

于 2009-05-07T19:28:25.150 回答
0

这并不能完全回答关于 Windows API 的部分问题,但它回答了关于全局热键列表和“拥有”它们的应用程序的部分问题。

http://hkcmdr.anymania.com/上的免费热键资源管理器显示了所有全局热键和拥有它们的应用程序的列表。这只是帮助我在几秒钟内弄清楚为什么特定于应用程序的快捷键停止工作以及如何修复它(通过在注册它的应用程序中重新配置注册的全局热键)。

于 2014-12-27T17:56:11.800 回答