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