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