hexchat_api/hook.rs
1
2//! This object wraps hook pointers returned when callbacks are registered
3//! with Hexchat. The Rust-style hook can be used to unhook commands directly
4//! via its `unhook()` function. This object protects against attempts to
5//! unhook the same callback more than once, which can crash Hexchat.
6//!
7//! The `unhook()` function returns the user_data that was registered with
8//! the associated callback, passing ownership to the caller. Invoking
9//! `unhook()` more than once returns `None`.
10//!
11//! The hooks can be cloned. Internally, clones safely share the same hook
12//! pointer. When hooks go out of scope, they do not remove their associated
13//! commands. Hooks can be ignored by the plugin if there is no need to
14//! unhook commands. The most relevant use of a hook could be to cancel
15//! timer callbacks.
16
17use libc::c_void;
18use std::ptr::null;
19use std::sync::RwLock;
20use std::sync::Arc;
21
22use send_wrapper::SendWrapper;
23
24use crate::callback_data::*;
25use crate::hexchat_entry_points::PHEXCHAT;
26use crate::user_data::*;
27
28/// A synchronized global list of the hooks. This gets initialized when a
29/// plugin is loaded from within the `lib_hexchat_plugin_init()` function
30/// before the plugin author's registered init function is invoked.
31///
32static HOOK_LIST: RwLock<Option<Vec<Hook>>> = RwLock::new(None);
33
34use UserData::*;
35
36struct HookData {
37 hook_ptr : *const c_void,
38 cbd_box_ptr : *const c_void,
39}
40
41/// A wrapper for Hexchat callback hooks. These hooks are returned when
42/// registering callbacks and can be used to unregister (unhook) them.
43/// `Hook`s can be cloned to share a reference to the same callback hook.
44///
45#[derive(Clone)]
46pub struct Hook {
47 data: Arc<RwLock<Option<SendWrapper<HookData>>>>,
48}
49
50unsafe impl Send for Hook {}
51
52impl Hook {
53 /// Constructor. `hook` is a hook returned by Hexchat when registering a
54 /// C-facing callback.
55 ///
56 pub (crate) fn new() -> Self {
57
58 let hook = Hook {
59 data: Arc::new(
60 RwLock::new(
61 Some(
62 SendWrapper::new(
63 HookData {
64 hook_ptr : null::<c_void>(),
65 cbd_box_ptr : null::<c_void>(),
66 })))),
67 };
68
69 if let Some(hook_list) = HOOK_LIST.write().unwrap().as_mut() {
70 // Clean up dead hooks.
71 hook_list.retain(|h|
72 !h.data.read().unwrap().as_ref().unwrap().hook_ptr.is_null()
73 );
74
75 // Store newly created hook in global list.
76 hook_list.push(hook.clone());
77 }
78
79 hook
80 }
81
82 /// Sets the value of the internal hook pointer. This is used by the hooking
83 /// functions in hexchat.rs.
84 ///
85 pub (crate) fn set(&self, ptr: *const c_void) {
86 if let Some(_hook_list_locked) = HOOK_LIST.read().unwrap().as_ref() {
87 // Lock the global list, and set the internal pointer.
88 self.data.write().unwrap().as_mut().unwrap().hook_ptr = ptr;
89 }
90 }
91
92 /// Sets the Hook's internal pointer to the raw Box pointer that references
93 /// the `CallbackData`. We have to keep our own reference to any `user_data`
94 /// passed to Hexchat, because it doesn't seem to be playing nice during
95 /// unload, where it should be returning our user data on `unhook()` -
96 /// but it doesn't seem to be doing that.
97 ///
98 pub (crate) fn set_cbd(&self, ptr: *const c_void) {
99 if let Some(_hook_list_locked) = HOOK_LIST.read().unwrap().as_ref() {
100 // Lock the global list, and set the internal pointer.
101 self.data.write().unwrap().as_mut().unwrap().cbd_box_ptr = ptr;
102 }
103 }
104
105 /// Unhooks the related callback from Hexchat. The user_data object is
106 /// returned. Subsequent calls to `unhook()` will return `None`. The
107 /// callback that was registered with Hexchat will be unhooked and dropped.
108 /// Ownership of the `user_data` will be passed to the caller.
109 /// # Returns
110 /// * The user data that was registered with the callback using one of the
111 /// hexchat hook functions.
112 ///
113 pub fn unhook(&self) -> UserData {
114 unsafe {
115 if let Some(_hook_list) = HOOK_LIST.read().unwrap().as_ref() {
116 let ptr_data = &mut self.data.write().unwrap();
117
118 // Determine if the Hook is still alive (non-null ptr).
119 if !ptr_data.as_ref().unwrap().hook_ptr.is_null() {
120
121 // Unhook the callback.
122 let hc = &*PHEXCHAT;
123 let hp = ptr_data.as_ref().unwrap().hook_ptr;
124 let _ = (hc.c_unhook)(hc, hp);
125
126 // ^ _ should be our user_data, but we can't rely on Hexchat
127 // to return a valid user_data pointer on unload, so we have
128 // to maintain it ourselves.
129
130 // Null the hook pointer.
131 ptr_data.as_mut().unwrap().hook_ptr = null::<c_void>();
132
133 // Reconstitute the CallbackData Box.
134 let cd = ptr_data.as_ref().unwrap().cbd_box_ptr;
135 let cd = &mut (*(cd as *mut CallbackData));
136 let cd = &mut Box::from_raw(cd);
137
138 // Give the caller the `user_data` the plugin registered
139 // with the callback.
140 return cd.take_data();
141 }
142 }
143 NoData
144 }
145 }
146
147 /// Called automatically within `lib_hexchat_plugin_init()` when a plugin is
148 /// loaded. This initializes the synchronized global static hook list.
149 ///
150 pub (crate) fn init() {
151 if let Ok(mut wlock) = HOOK_LIST.write() {
152 *wlock = Some(Vec::new());
153 }
154 }
155
156 /// Called when a plugin is unloaded by Hexchat. This happens when the user
157 /// opens the "Plugins and Scripts" dialog and unloads/reloads the plugin,
158 /// or the user issues one of the slash "/" commands to perform the same
159 /// operation. This function iterates over each hook, calling their
160 /// `unhook()` method which grabs ownership of the `CallbackData` objects
161 /// and drops them as they go out of scope ensuring their destructors
162 /// are called.
163 ///
164 pub (crate) fn deinit() {
165 if let Some(hook_list) = HOOK_LIST.read().unwrap().as_ref() {
166 for hook in hook_list {
167 hook.unhook();
168 }
169 }
170 // This causes the `RwLock` and hook vector to be dropped.
171 // plugin authors need to ensure that no threads are running when
172 // their plugins are unloading - or one may try to access the lock
173 // and hook vector after they've been destroyed.
174 if let Ok(mut hook_list_opt) = HOOK_LIST.write() {
175 hook_list_opt.take();
176 }
177 }
178}
179
180
181