hook.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. /*
  2. * Server-side window hooks support
  3. *
  4. * Copyright (C) 2002 Alexandre Julliard
  5. * Copyright (C) 2005 Dmitry Timoshkov
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation; either
  10. * version 2.1 of the License, or (at your option) any later version.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this library; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  20. */
  21. #include "config.h"
  22. #include "wine/port.h"
  23. #include <assert.h>
  24. #include <stdarg.h>
  25. #include <stdio.h>
  26. #include "ntstatus.h"
  27. #define WIN32_NO_STATUS
  28. #include "windef.h"
  29. #include "winbase.h"
  30. #include "winuser.h"
  31. #include "winternl.h"
  32. #include "object.h"
  33. #include "process.h"
  34. #include "request.h"
  35. #include "user.h"
  36. struct hook_table;
  37. struct hook
  38. {
  39. struct list chain; /* hook chain entry */
  40. user_handle_t handle; /* user handle for this hook */
  41. struct process *process; /* process the hook is set to */
  42. struct thread *thread; /* thread the hook is set to */
  43. struct thread *owner; /* owner of the out of context hook */
  44. struct hook_table *table; /* hook table that contains this hook */
  45. int index; /* hook table index */
  46. int event_min;
  47. int event_max;
  48. int flags;
  49. client_ptr_t proc; /* hook function */
  50. int unicode; /* is it a unicode hook? */
  51. WCHAR *module; /* module name for global hooks */
  52. data_size_t module_size;
  53. };
  54. #define WH_WINEVENT (WH_MAXHOOK+1)
  55. #define NB_HOOKS (WH_WINEVENT-WH_MINHOOK+1)
  56. #define HOOK_ENTRY(p) LIST_ENTRY( (p), struct hook, chain )
  57. struct hook_table
  58. {
  59. struct object obj; /* object header */
  60. struct list hooks[NB_HOOKS]; /* array of hook chains */
  61. int counts[NB_HOOKS]; /* use counts for each hook chain */
  62. };
  63. static void hook_table_dump( struct object *obj, int verbose );
  64. static void hook_table_destroy( struct object *obj );
  65. static const struct object_ops hook_table_ops =
  66. {
  67. sizeof(struct hook_table), /* size */
  68. hook_table_dump, /* dump */
  69. no_get_type, /* get_type */
  70. no_add_queue, /* add_queue */
  71. NULL, /* remove_queue */
  72. NULL, /* signaled */
  73. NULL, /* get_esync_fd */
  74. NULL, /* satisfied */
  75. no_signal, /* signal */
  76. no_get_fd, /* get_fd */
  77. no_map_access, /* map_access */
  78. default_get_sd, /* get_sd */
  79. default_set_sd, /* set_sd */
  80. no_lookup_name, /* lookup_name */
  81. no_link_name, /* link_name */
  82. NULL, /* unlink_name */
  83. no_open_file, /* open_file */
  84. no_kernel_obj_list, /* get_kernel_obj_list */
  85. no_alloc_handle, /* alloc_handle */
  86. no_close_handle, /* close_handle */
  87. hook_table_destroy /* destroy */
  88. };
  89. /* create a new hook table */
  90. static struct hook_table *alloc_hook_table(void)
  91. {
  92. struct hook_table *table;
  93. int i;
  94. if ((table = alloc_object( &hook_table_ops )))
  95. {
  96. for (i = 0; i < NB_HOOKS; i++)
  97. {
  98. list_init( &table->hooks[i] );
  99. table->counts[i] = 0;
  100. }
  101. }
  102. return table;
  103. }
  104. static struct hook_table *get_global_hooks( struct thread *thread )
  105. {
  106. struct hook_table *table;
  107. struct desktop *desktop;
  108. if (!thread->desktop) return NULL;
  109. if (!(desktop = get_thread_desktop( thread, 0 ))) return NULL;
  110. table = desktop->global_hooks;
  111. release_object( desktop );
  112. return table;
  113. }
  114. /* create a new hook and add it to the specified table */
  115. static struct hook *add_hook( struct desktop *desktop, struct thread *thread, int index, int global )
  116. {
  117. struct hook *hook;
  118. struct hook_table *table = global ? desktop->global_hooks : get_queue_hooks(thread);
  119. if (!table)
  120. {
  121. if (!(table = alloc_hook_table())) return NULL;
  122. if (global) desktop->global_hooks = table;
  123. else set_queue_hooks( thread, table );
  124. }
  125. if (!(hook = mem_alloc( sizeof(*hook) ))) return NULL;
  126. if (!(hook->handle = alloc_user_handle( hook, USER_HOOK )))
  127. {
  128. free( hook );
  129. return NULL;
  130. }
  131. hook->thread = thread ? (struct thread *)grab_object( thread ) : NULL;
  132. hook->table = table;
  133. hook->index = index;
  134. list_add_head( &table->hooks[index], &hook->chain );
  135. if (thread) thread->desktop_users++;
  136. return hook;
  137. }
  138. /* free a hook, removing it from its chain */
  139. static void free_hook( struct hook *hook )
  140. {
  141. free_user_handle( hook->handle );
  142. free( hook->module );
  143. if (hook->thread)
  144. {
  145. assert( hook->thread->desktop_users > 0 );
  146. hook->thread->desktop_users--;
  147. release_object( hook->thread );
  148. }
  149. if (hook->process) release_object( hook->process );
  150. release_object( hook->owner );
  151. list_remove( &hook->chain );
  152. free( hook );
  153. }
  154. /* find a hook from its index and proc */
  155. static struct hook *find_hook( struct thread *thread, int index, client_ptr_t proc )
  156. {
  157. struct list *p;
  158. struct hook_table *table = get_queue_hooks( thread );
  159. if (table)
  160. {
  161. LIST_FOR_EACH( p, &table->hooks[index] )
  162. {
  163. struct hook *hook = HOOK_ENTRY( p );
  164. if (hook->proc == proc) return hook;
  165. }
  166. }
  167. return NULL;
  168. }
  169. /* get the first hook in the chain */
  170. static inline struct hook *get_first_hook( struct hook_table *table, int index )
  171. {
  172. struct list *elem = list_head( &table->hooks[index] );
  173. return elem ? HOOK_ENTRY( elem ) : NULL;
  174. }
  175. /* check if a given hook should run in the owner thread instead of the current thread */
  176. static inline int run_hook_in_owner_thread( struct hook *hook )
  177. {
  178. if ((hook->index == WH_MOUSE_LL - WH_MINHOOK ||
  179. hook->index == WH_KEYBOARD_LL - WH_MINHOOK))
  180. return hook->owner != current;
  181. return 0;
  182. }
  183. /* check if a given hook should run in the current thread */
  184. static inline int run_hook_in_current_thread( struct hook *hook )
  185. {
  186. if (hook->process && hook->process != current->process) return 0;
  187. if ((hook->flags & WINEVENT_SKIPOWNPROCESS) && hook->process == current->process) return 0;
  188. if (hook->thread && hook->thread != current) return 0;
  189. if ((hook->flags & WINEVENT_SKIPOWNTHREAD) && hook->thread == current) return 0;
  190. /* don't run low-level hooks in processes suspended for debugging */
  191. if (run_hook_in_owner_thread( hook ) && hook->owner->process->suspend) return 0;
  192. return 1;
  193. }
  194. /* find the first non-deleted hook in the chain */
  195. static inline struct hook *get_first_valid_hook( struct hook_table *table, int index,
  196. int event, user_handle_t win,
  197. int object_id, int child_id )
  198. {
  199. struct hook *hook = get_first_hook( table, index );
  200. while (hook)
  201. {
  202. if (hook->proc && run_hook_in_current_thread( hook ))
  203. {
  204. if (event >= hook->event_min && event <= hook->event_max)
  205. {
  206. if (hook->flags & WINEVENT_INCONTEXT) return hook;
  207. /* only winevent hooks may be out of context */
  208. assert(hook->index + WH_MINHOOK == WH_WINEVENT);
  209. post_win_event( hook->owner, event, win, object_id, child_id,
  210. hook->proc, hook->module, hook->module_size,
  211. hook->handle );
  212. }
  213. }
  214. hook = HOOK_ENTRY( list_next( &table->hooks[index], &hook->chain ) );
  215. }
  216. return hook;
  217. }
  218. /* find the next hook in the chain, skipping the deleted ones */
  219. static struct hook *get_next_hook( struct thread *thread, struct hook *hook, int event,
  220. user_handle_t win, int object_id, int child_id )
  221. {
  222. struct hook_table *global_hooks, *table = hook->table;
  223. int index = hook->index;
  224. while ((hook = HOOK_ENTRY( list_next( &table->hooks[index], &hook->chain ) )))
  225. {
  226. if (hook->proc && run_hook_in_current_thread( hook ))
  227. {
  228. if (event >= hook->event_min && event <= hook->event_max)
  229. {
  230. if (hook->flags & WINEVENT_INCONTEXT) return hook;
  231. /* only winevent hooks may be out of context */
  232. assert(hook->index + WH_MINHOOK == WH_WINEVENT);
  233. post_win_event( hook->owner, event, win, object_id, child_id,
  234. hook->proc, hook->module, hook->module_size,
  235. hook->handle );
  236. }
  237. }
  238. }
  239. global_hooks = get_global_hooks( thread );
  240. if (global_hooks && table != global_hooks) /* now search through the global table */
  241. {
  242. hook = get_first_valid_hook( global_hooks, index, event, win, object_id, child_id );
  243. }
  244. return hook;
  245. }
  246. static void hook_table_dump( struct object *obj, int verbose )
  247. {
  248. /* struct hook_table *table = (struct hook_table *)obj; */
  249. fprintf( stderr, "Hook table\n" );
  250. }
  251. static void hook_table_destroy( struct object *obj )
  252. {
  253. int i;
  254. struct hook *hook;
  255. struct hook_table *table = (struct hook_table *)obj;
  256. for (i = 0; i < NB_HOOKS; i++)
  257. {
  258. while ((hook = get_first_hook( table, i )) != NULL) free_hook( hook );
  259. }
  260. }
  261. /* remove a hook, freeing it if the chain is not in use */
  262. static void remove_hook( struct hook *hook )
  263. {
  264. if (hook->table->counts[hook->index])
  265. hook->proc = 0; /* chain is in use, just mark it and return */
  266. else
  267. free_hook( hook );
  268. }
  269. /* release a hook chain, removing deleted hooks if the use count drops to 0 */
  270. static void release_hook_chain( struct hook_table *table, int index )
  271. {
  272. if (!table->counts[index]) /* use count shouldn't already be 0 */
  273. {
  274. set_error( STATUS_INVALID_PARAMETER );
  275. return;
  276. }
  277. if (!--table->counts[index])
  278. {
  279. struct hook *hook = get_first_hook( table, index );
  280. while (hook)
  281. {
  282. struct hook *next = HOOK_ENTRY( list_next( &table->hooks[hook->index], &hook->chain ) );
  283. if (!hook->proc) free_hook( hook );
  284. hook = next;
  285. }
  286. }
  287. }
  288. /* remove all global hooks owned by a given thread */
  289. void remove_thread_hooks( struct thread *thread )
  290. {
  291. struct hook_table *global_hooks = get_global_hooks( thread );
  292. int index;
  293. if (!global_hooks) return;
  294. /* only low-level keyboard/mouse global hooks can be owned by a thread */
  295. for (index = WH_KEYBOARD_LL - WH_MINHOOK; index <= WH_MOUSE_LL - WH_MINHOOK; index++)
  296. {
  297. struct hook *hook = get_first_hook( global_hooks, index );
  298. while (hook)
  299. {
  300. struct hook *next = HOOK_ENTRY( list_next( &global_hooks->hooks[index], &hook->chain ) );
  301. if (hook->thread == thread) remove_hook( hook );
  302. hook = next;
  303. }
  304. }
  305. }
  306. /* get a bitmap of active hooks in a hook table */
  307. static int is_hook_active( struct hook_table *table, int index )
  308. {
  309. struct hook *hook = get_first_hook( table, index );
  310. while (hook)
  311. {
  312. if (hook->proc && run_hook_in_current_thread( hook )) return 1;
  313. hook = HOOK_ENTRY( list_next( &table->hooks[index], &hook->chain ) );
  314. }
  315. return 0;
  316. }
  317. /* get a bitmap of all active hooks for the current thread */
  318. unsigned int get_active_hooks(void)
  319. {
  320. struct hook_table *table = get_queue_hooks( current );
  321. struct hook_table *global_hooks = get_global_hooks( current );
  322. unsigned int ret = 1u << 31; /* set high bit to indicate that the bitmap is valid */
  323. int id;
  324. for (id = WH_MINHOOK; id <= WH_WINEVENT; id++)
  325. {
  326. if ((table && is_hook_active( table, id - WH_MINHOOK )) ||
  327. (global_hooks && is_hook_active( global_hooks, id - WH_MINHOOK )))
  328. ret |= 1 << (id - WH_MINHOOK);
  329. }
  330. return ret;
  331. }
  332. /* return the thread that owns the first global hook */
  333. struct thread *get_first_global_hook( int id )
  334. {
  335. struct hook *hook;
  336. struct hook_table *global_hooks = get_global_hooks( current );
  337. if (!global_hooks) return NULL;
  338. if (!(hook = get_first_valid_hook( global_hooks, id - WH_MINHOOK, EVENT_MIN, 0, 0, 0 ))) return NULL;
  339. return hook->owner;
  340. }
  341. /* set a window hook */
  342. DECL_HANDLER(set_hook)
  343. {
  344. struct process *process = NULL;
  345. struct thread *thread = NULL;
  346. struct desktop *desktop;
  347. struct hook *hook;
  348. WCHAR *module;
  349. int global;
  350. data_size_t module_size = get_req_data_size();
  351. if (!req->proc || req->id < WH_MINHOOK || req->id > WH_WINEVENT)
  352. {
  353. set_error( STATUS_INVALID_PARAMETER );
  354. return;
  355. }
  356. if (!(desktop = get_thread_desktop( current, DESKTOP_HOOKCONTROL ))) return;
  357. if (req->pid && !(process = get_process_from_id( req->pid ))) goto done;
  358. if (req->tid)
  359. {
  360. if (!(thread = get_thread_from_id( req->tid ))) goto done;
  361. if (process && process != thread->process)
  362. {
  363. set_error( STATUS_INVALID_PARAMETER );
  364. goto done;
  365. }
  366. }
  367. if (req->id == WH_KEYBOARD_LL || req->id == WH_MOUSE_LL)
  368. {
  369. /* low-level hardware hooks are special: always global, but without a module */
  370. if (thread)
  371. {
  372. set_error( STATUS_INVALID_PARAMETER );
  373. goto done;
  374. }
  375. module = NULL;
  376. global = 1;
  377. }
  378. else if (!req->tid)
  379. {
  380. /* out of context hooks do not need a module handle */
  381. if (!module_size && (req->flags & WINEVENT_INCONTEXT))
  382. {
  383. set_error( STATUS_INVALID_PARAMETER );
  384. goto done;
  385. }
  386. if (!(module = memdup( get_req_data(), module_size ))) goto done;
  387. global = 1;
  388. }
  389. else
  390. {
  391. /* module is optional only if hook is in current process */
  392. if (!module_size)
  393. {
  394. module = NULL;
  395. if (thread->process != current->process)
  396. {
  397. set_error( STATUS_INVALID_PARAMETER );
  398. goto done;
  399. }
  400. }
  401. else if (!(module = memdup( get_req_data(), module_size ))) goto done;
  402. global = 0;
  403. }
  404. if ((hook = add_hook( desktop, thread, req->id - WH_MINHOOK, global )))
  405. {
  406. hook->owner = (struct thread *)grab_object( current );
  407. hook->process = process ? (struct process *)grab_object( process ) : NULL;
  408. hook->event_min = req->event_min;
  409. hook->event_max = req->event_max;
  410. hook->flags = req->flags;
  411. hook->proc = req->proc;
  412. hook->unicode = req->unicode;
  413. hook->module = module;
  414. hook->module_size = module_size;
  415. reply->handle = hook->handle;
  416. reply->active_hooks = get_active_hooks();
  417. }
  418. else free( module );
  419. done:
  420. if (process) release_object( process );
  421. if (thread) release_object( thread );
  422. release_object( desktop );
  423. }
  424. /* remove a window hook */
  425. DECL_HANDLER(remove_hook)
  426. {
  427. struct hook *hook;
  428. if (req->handle)
  429. {
  430. if (!(hook = get_user_object( req->handle, USER_HOOK )))
  431. {
  432. set_error( STATUS_INVALID_HANDLE );
  433. return;
  434. }
  435. }
  436. else
  437. {
  438. if (!req->proc || req->id < WH_MINHOOK || req->id > WH_WINEVENT)
  439. {
  440. set_error( STATUS_INVALID_PARAMETER );
  441. return;
  442. }
  443. if (!(hook = find_hook( current, req->id - WH_MINHOOK, req->proc )))
  444. {
  445. set_error( STATUS_INVALID_PARAMETER );
  446. return;
  447. }
  448. }
  449. remove_hook( hook );
  450. reply->active_hooks = get_active_hooks();
  451. }
  452. /* start calling a hook chain */
  453. DECL_HANDLER(start_hook_chain)
  454. {
  455. struct hook *hook;
  456. struct hook_table *table = get_queue_hooks( current );
  457. struct hook_table *global_table = get_global_hooks( current );
  458. if (req->id < WH_MINHOOK || req->id > WH_WINEVENT)
  459. {
  460. set_error( STATUS_INVALID_PARAMETER );
  461. return;
  462. }
  463. reply->active_hooks = get_active_hooks();
  464. if (!table || !(hook = get_first_valid_hook( table, req->id - WH_MINHOOK, req->event,
  465. req->window, req->object_id, req->child_id )))
  466. {
  467. /* try global table */
  468. if (!global_table || !(hook = get_first_valid_hook( global_table, req->id - WH_MINHOOK, req->event,
  469. req->window, req->object_id, req->child_id )))
  470. return; /* no hook set */
  471. }
  472. if (run_hook_in_owner_thread( hook ))
  473. {
  474. reply->pid = get_process_id( hook->owner->process );
  475. reply->tid = get_thread_id( hook->owner );
  476. }
  477. else
  478. {
  479. reply->pid = 0;
  480. reply->tid = 0;
  481. }
  482. reply->proc = hook->proc;
  483. reply->handle = hook->handle;
  484. reply->unicode = hook->unicode;
  485. if (table) table->counts[hook->index]++;
  486. if (global_table) global_table->counts[hook->index]++;
  487. if (hook->module) set_reply_data( hook->module, hook->module_size );
  488. }
  489. /* finished calling a hook chain */
  490. DECL_HANDLER(finish_hook_chain)
  491. {
  492. struct hook_table *table = get_queue_hooks( current );
  493. struct hook_table *global_hooks = get_global_hooks( current );
  494. int index = req->id - WH_MINHOOK;
  495. if (req->id < WH_MINHOOK || req->id > WH_WINEVENT)
  496. {
  497. set_error( STATUS_INVALID_PARAMETER );
  498. return;
  499. }
  500. if (table) release_hook_chain( table, index );
  501. if (global_hooks) release_hook_chain( global_hooks, index );
  502. }
  503. /* get the hook information */
  504. DECL_HANDLER(get_hook_info)
  505. {
  506. struct hook *hook;
  507. if (!(hook = get_user_object( req->handle, USER_HOOK ))) return;
  508. if (hook->thread && (hook->thread != current))
  509. {
  510. set_error( STATUS_INVALID_HANDLE );
  511. return;
  512. }
  513. if (req->get_next && !(hook = get_next_hook( current, hook, req->event, req->window,
  514. req->object_id, req->child_id )))
  515. return;
  516. reply->handle = hook->handle;
  517. reply->id = hook->index + WH_MINHOOK;
  518. reply->unicode = hook->unicode;
  519. if (hook->module) set_reply_data( hook->module, min(hook->module_size,get_reply_max_size()) );
  520. if (run_hook_in_owner_thread( hook ))
  521. {
  522. reply->pid = get_process_id( hook->owner->process );
  523. reply->tid = get_thread_id( hook->owner );
  524. }
  525. else
  526. {
  527. reply->pid = 0;
  528. reply->tid = 0;
  529. }
  530. reply->proc = hook->proc;
  531. }