TB2Hook.pas 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. unit TB2Hook;
  2. {
  3. Toolbar2000
  4. Copyright (C) 1998-2005 by Jordan Russell
  5. All rights reserved.
  6. The contents of this file are subject to the "Toolbar2000 License"; you may
  7. not use or distribute this file except in compliance with the
  8. "Toolbar2000 License". A copy of the "Toolbar2000 License" may be found in
  9. TB2k-LICENSE.txt or at:
  10. http://www.jrsoftware.org/files/tb2k/TB2k-LICENSE.txt
  11. Alternatively, the contents of this file may be used under the terms of the
  12. GNU General Public License (the "GPL"), in which case the provisions of the
  13. GPL are applicable instead of those in the "Toolbar2000 License". A copy of
  14. the GPL may be found in GPL-LICENSE.txt or at:
  15. http://www.jrsoftware.org/files/tb2k/GPL-LICENSE.txt
  16. If you wish to allow use of your version of this file only under the terms of
  17. the GPL and not to allow others to use your version of this file under the
  18. "Toolbar2000 License", indicate your decision by deleting the provisions
  19. above and replace them with the notice and other provisions required by the
  20. GPL. If you do not delete the provisions above, a recipient may use your
  21. version of this file under either the "Toolbar2000 License" or the GPL.
  22. $jrsoftware: tb2k/Source/TB2Hook.pas,v 1.15 2005/06/26 18:21:33 jr Exp $
  23. }
  24. interface
  25. uses
  26. Windows;
  27. type
  28. THookProcCode = (hpSendActivate, hpSendActivateApp, hpSendWindowPosChanged,
  29. hpPreDestroy, hpGetMessage);
  30. THookProcCodes = set of THookProcCode;
  31. THookProc = procedure(Code: THookProcCode; Wnd: HWND; WParam: WPARAM; LParam: LPARAM);
  32. procedure InstallHookProc(AUser: TObject; AProc: THookProc; ACodes: THookProcCodes);
  33. procedure UninstallHookProc(AUser: TObject; AProc: THookProc);
  34. implementation
  35. uses
  36. SysUtils, Classes, Messages;
  37. type
  38. THookType = (htCallWndProc, htCBT, htGetMessage);
  39. THookTypes = set of THookType;
  40. PHookUserData = ^THookUserData;
  41. THookUserData = record
  42. Prev: PHookUserData;
  43. User: TObject;
  44. InstalledHookTypes: THookTypes;
  45. end;
  46. PHookProcData = ^THookProcData;
  47. THookProcData = record
  48. Proc: THookProc;
  49. Codes: THookProcCodes;
  50. LastUserData: PHookUserData;
  51. end;
  52. threadvar
  53. HookHandles: array[THookType] of HHOOK;
  54. HookProcList: TList;
  55. HookCounts: array[THookType] of Longint;
  56. function CallWndProcHook(Code: Integer; WParam: WPARAM; LParam: LPARAM): LRESULT;
  57. stdcall;
  58. type
  59. THookProcCodeMsgs = hpSendActivate..hpSendWindowPosChanged;
  60. const
  61. MsgMap: array[THookProcCodeMsgs] of UINT =
  62. (WM_ACTIVATE, WM_ACTIVATEAPP, WM_WINDOWPOSCHANGED);
  63. var
  64. J: THookProcCodeMsgs;
  65. I: Integer;
  66. begin
  67. if Assigned(HookProcList) and (Code = HC_ACTION) then
  68. with PCWPStruct(LParam)^ do begin
  69. for J := Low(J) to High(J) do
  70. if Message = MsgMap[J] then begin
  71. for I := 0 to HookProcList.Count-1 do
  72. try
  73. with PHookProcData(HookProcList.List[I])^ do
  74. if J in Codes then
  75. Proc(J, hwnd, WParam, LParam);
  76. except
  77. end;
  78. Break;
  79. end;
  80. end;
  81. Result := CallNextHookEx(HookHandles[htCallWndProc], Code, WParam, LParam);
  82. end;
  83. function CBTHook(Code: Integer; WParam: WPARAM; LParam: LPARAM): LRESULT;
  84. stdcall;
  85. var
  86. I: Integer;
  87. begin
  88. if Assigned(HookProcList) and (Code = HCBT_DESTROYWND) then
  89. for I := 0 to HookProcList.Count-1 do
  90. try
  91. with PHookProcData(HookProcList.List[I])^ do
  92. if hpPreDestroy in Codes then
  93. Proc(hpPreDestroy, HWND(WParam), 0, 0);
  94. except
  95. end;
  96. Result := CallNextHookEx(HookHandles[htCBT], Code, WParam, LParam);
  97. end;
  98. function GetMessageHook(Code: Integer; WParam: WPARAM; LParam: LPARAM): LRESULT;
  99. stdcall;
  100. var
  101. I: Integer;
  102. begin
  103. if Assigned(HookProcList) and (Code = HC_ACTION) then
  104. for I := 0 to HookProcList.Count-1 do
  105. try
  106. with PHookProcData(HookProcList.List[I])^ do
  107. if hpGetMessage in Codes then
  108. Proc(hpGetMessage, 0, WParam, LParam);
  109. except
  110. end;
  111. Result := CallNextHookEx(HookHandles[htGetMessage], Code, WParam, LParam);
  112. end;
  113. function HookCodesToTypes(Codes: THookProcCodes): THookTypes;
  114. const
  115. HookCodeToType: array[THookProcCode] of THookType =
  116. (htCallWndProc, htCallWndProc, htCallWndProc, htCBT, htGetMessage);
  117. var
  118. J: THookProcCode;
  119. begin
  120. Result := [];
  121. for J := Low(J) to High(J) do
  122. if J in Codes then
  123. Include(Result, HookCodeToType[J]);
  124. end;
  125. const
  126. HookProcs: array[THookType] of TFNHookProc =
  127. (CallWndProcHook, CBTHook, GetMessageHook);
  128. HookIDs: array[THookType] of Integer =
  129. (WH_CALLWNDPROC, WH_CBT, WH_GETMESSAGE);
  130. procedure InstallHooks(ATypes: THookTypes; var InstalledTypes: THookTypes);
  131. var
  132. T: THookType;
  133. begin
  134. { Don't increment reference counts for hook types that were already
  135. installed previously }
  136. ATypes := ATypes - InstalledTypes;
  137. { Increment reference counts first. This should never raise an exception. }
  138. for T := Low(T) to High(T) do
  139. if T in ATypes then begin
  140. Inc(HookCounts[T]);
  141. Include(InstalledTypes, T);
  142. end;
  143. { Then install the hooks }
  144. for T := Low(T) to High(T) do
  145. if T in InstalledTypes then begin
  146. if HookHandles[T] = 0 then begin
  147. { On Windows NT platforms, SetWindowsHookExW is used to work around an
  148. apparent bug in Windows NT/2000/XP: if an 'ANSI' WH_GETMESSAGE hook
  149. is called *before* a 'wide' WH_GETMESSAGE hook, then WM_*CHAR
  150. messages passed to the 'wide' hook use ANSI character codes.
  151. This is needed for compatibility with the combination of Tnt Unicode
  152. Controls and Keyman. See "Widechar's and tb2k" thread on the
  153. newsgroup from 2003-09-23 for more information. }
  154. if Win32Platform = VER_PLATFORM_WIN32_NT then
  155. HookHandles[T] := SetWindowsHookExW(HookIDs[T], HookProcs[T],
  156. 0, GetCurrentThreadId)
  157. else
  158. HookHandles[T] := SetWindowsHookEx(HookIDs[T], HookProcs[T],
  159. 0, GetCurrentThreadId);
  160. end;
  161. end;
  162. end;
  163. procedure UninstallHooks(const ATypes: THookTypes; const Force: Boolean);
  164. var
  165. T: THookType;
  166. begin
  167. { Decrement reference counts first. This should never raise an exception. }
  168. if not Force then
  169. for T := Low(T) to High(T) do
  170. if T in ATypes then
  171. Dec(HookCounts[T]);
  172. { Then uninstall the hooks }
  173. for T := Low(T) to High(T) do
  174. if T in ATypes then begin
  175. if (Force or (HookCounts[T] = 0)) and (HookHandles[T] <> 0) then begin
  176. UnhookWindowsHookEx(HookHandles[T]);
  177. HookHandles[T] := 0;
  178. end;
  179. end;
  180. end;
  181. procedure InstallHookProc(AUser: TObject; AProc: THookProc; ACodes: THookProcCodes);
  182. var
  183. Found: Boolean;
  184. I: Integer;
  185. UserData: PHookUserData;
  186. ProcData: PHookProcData;
  187. label 1;
  188. begin
  189. if HookProcList = nil then
  190. HookProcList := TList.Create;
  191. Found := False;
  192. UserData := nil; { avoid warning }
  193. for I := 0 to HookProcList.Count-1 do begin
  194. ProcData := PHookProcData(HookProcList[I]);
  195. if @ProcData.Proc = @AProc then begin
  196. UserData := ProcData.LastUserData;
  197. while Assigned(UserData) do begin
  198. if UserData.User = AUser then begin
  199. { InstallHookProc was already called for AUser/AProc. Go ahead and
  200. call InstallHooks again just in case the hooks weren't successfully
  201. installed last time. }
  202. goto 1;
  203. end;
  204. UserData := UserData.Prev;
  205. end;
  206. New(UserData);
  207. UserData.Prev := ProcData.LastUserData;
  208. UserData.User := AUser;
  209. UserData.InstalledHookTypes := [];
  210. ProcData.LastUserData := UserData;
  211. Found := True;
  212. Break;
  213. end;
  214. end;
  215. if not Found then begin
  216. New(UserData);
  217. try
  218. UserData.Prev := nil;
  219. UserData.User := AUser;
  220. UserData.InstalledHookTypes := [];
  221. HookProcList.Expand;
  222. New(ProcData);
  223. except
  224. Dispose(UserData);
  225. raise;
  226. end;
  227. ProcData.Proc := AProc;
  228. ProcData.Codes := ACodes;
  229. ProcData.LastUserData := UserData;
  230. HookProcList.Add(ProcData);
  231. end;
  232. 1:InstallHooks(HookCodesToTypes(ACodes), UserData.InstalledHookTypes);
  233. end;
  234. procedure UninstallHookProc(AUser: TObject; AProc: THookProc);
  235. var
  236. I: Integer;
  237. ProcData: PHookProcData;
  238. NextUserData, UserData: PHookUserData;
  239. T: THookTypes;
  240. begin
  241. if HookProcList = nil then Exit;
  242. for I := 0 to HookProcList.Count-1 do begin
  243. ProcData := PHookProcData(HookProcList[I]);
  244. if @ProcData.Proc = @AProc then begin
  245. { Locate the UserData record }
  246. NextUserData := nil;
  247. UserData := ProcData.LastUserData;
  248. while Assigned(UserData) and (UserData.User <> AUser) do begin
  249. NextUserData := UserData;
  250. UserData := UserData.Prev;
  251. end;
  252. if UserData = nil then
  253. Exit;
  254. { Remove record from linked list }
  255. if NextUserData = nil then begin
  256. { It's the last item in the list }
  257. if UserData.Prev = nil then begin
  258. { It's the only item in the list, so destroy the ProcData record }
  259. HookProcList.Delete(I);
  260. Dispose(ProcData);
  261. end
  262. else
  263. ProcData.LastUserData := UserData.Prev;
  264. end
  265. else
  266. NextUserData.Prev := UserData.Prev;
  267. T := UserData.InstalledHookTypes;
  268. Dispose(UserData);
  269. UninstallHooks(T, False);
  270. Break;
  271. end;
  272. end;
  273. if HookProcList.Count = 0 then
  274. FreeAndNil(HookProcList);
  275. end;
  276. initialization
  277. finalization
  278. UninstallHooks([Low(THookType)..High(THookType)], True);
  279. end.