TB2Hook.pas 9.5 KB

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