summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorSoniEx2 <endermoneymod@gmail.com>2021-06-26 22:33:40 -0300
committerSoniEx2 <endermoneymod@gmail.com>2021-06-26 22:33:40 -0300
commit3b38f6e4f418baafe48989a90ce17661a6c2966f (patch)
tree2655b7d121180ef045159fd62f7bd84aab320ae7
parentbace9b0d34de842544e9ffa5989f433ec177dde7 (diff)
Clean up hooks (a bit)
-rw-r--r--src/lib.rs569
1 files changed, 279 insertions, 290 deletions
diff --git a/src/lib.rs b/src/lib.rs
index c8b0d80..a4faec7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -30,16 +30,16 @@
 //! 
 //! # Examples
 //!
-//! ```
+//! ```no_run
 //! #[macro_use]
 //! extern crate hexchat_plugin;
 //!
 //! use std::sync::Mutex;
-//! use hexchat_plugin::{Plugin, PluginHandle, CommandHookHandle};
+//! use hexchat_plugin::{Plugin, PluginHandle, HookHandle};
 //!
 //! #[derive(Default)]
 //! struct MyPlugin {
-//!     cmd: Mutex<Option<CommandHookHandle>>
+//!     cmd: Mutex<Option<HookHandle>>
 //! }
 //!
 //! impl Plugin for MyPlugin {
@@ -169,13 +169,16 @@ pub use infoid::InfoId;
 pub use word::*;
 
 use pluginfo::PluginInfo;
+use internals::Ph as RawPh;
+use internals::HexchatEventAttrs as RawAttrs;
 
+use std::borrow::Cow;
 use std::cell::Cell;
 use std::ffi::{CString, CStr};
 use std::marker::PhantomData;
 use std::mem;
 use std::mem::ManuallyDrop;
-use std::panic::catch_unwind;
+use std::panic::{AssertUnwindSafe, RefUnwindSafe, UnwindSafe, catch_unwind};
 use std::ptr;
 use std::rc::Rc;
 use std::rc::Weak as RcWeak;
@@ -183,6 +186,9 @@ use std::str::FromStr;
 use std::thread;
 use std::time::{SystemTime, UNIX_EPOCH, Duration};
 
+#[doc(hidden)]
+pub use libc::{c_char, c_int, c_void, time_t};
+
 // ****** //
 // PUBLIC //
 // ****** //
@@ -222,7 +228,7 @@ pub trait Plugin {
 ///
 /// # Examples
 ///
-/// ```
+/// ```no_run
 /// use hexchat_plugin::{PluginHandle};
 ///
 /// fn init(ph: &mut PluginHandle) {
@@ -230,7 +236,7 @@ pub trait Plugin {
 /// }
 /// ```
 pub struct PluginHandle {
-    ph: *mut internals::Ph,
+    ph: *mut RawPh,
     skip_pri_ck: bool,
     // Used for registration
     info: PluginInfo,
@@ -251,49 +257,21 @@ pub struct EventAttrs<'a> {
     _dummy: PhantomData<&'a ()>,
 }
 
-/// A command hook handle.
-#[must_use = "Hooks must be stored somewhere and are automatically unhooked on Drop"]
-pub struct CommandHookHandle {
-    ph: *mut internals::Ph,
-    hh: *const internals::HexchatHook,
-    // this does actually store an Rc<...>, but on the other side of the FFI.
-    _f: PhantomData<Rc<CommandHookUd>>,
-}
-
-/// A server hook handle.
-#[must_use = "Hooks must be stored somewhere and are automatically unhooked on Drop"]
-pub struct ServerHookHandle {
-    ph: *mut internals::Ph,
-    hh: *const internals::HexchatHook,
-    // this does actually store an Rc<...>, but on the other side of the FFI.
-    _f: PhantomData<Rc<ServerHookUd>>,
-}
-
-/// A print hook handle.
+/// A hook handle.
 #[must_use = "Hooks must be stored somewhere and are automatically unhooked on Drop"]
-pub struct PrintHookHandle {
-    ph: *mut internals::Ph,
+pub struct HookHandle {
+    ph: *mut RawPh,
     hh: *const internals::HexchatHook,
+    freed: Rc<Cell<bool>>,
     // this does actually store an Rc<...>, but on the other side of the FFI.
-    _f: PhantomData<Rc<PrintHookUd>>,
-}
-
-/// A timer hook handle.
-#[must_use = "Hooks must be stored somewhere and are automatically unhooked on Drop"]
-pub struct TimerHookHandle {
-    ph: *mut internals::Ph,
-    hh: *const internals::HexchatHook,
-    // avoids issues
-    alive: Rc<Cell<bool>>,
-    // this does actually store an Rc<...>, but on the other side of the FFI.
-    _f: PhantomData<Rc<TimerHookUd>>,
+    _f: PhantomData<Rc<HookUd>>,
 }
 
 /// A context.
 #[derive(Clone)]
 pub struct Context {
     ctx: RcWeak<*const internals::HexchatContext>, // may be null
-    closure: Rc<Cell<Option<PrintHookHandle>>>,
+    closure: Rc<Cell<Option<HookHandle>>>,
 }
 
 // #[derive(Debug)] // doesn't work
@@ -310,55 +288,54 @@ impl<F: FnOnce(EnsureValidContext) -> R, R> InvalidContextError<F, R> {
         self.0
     }
 }
-
-impl Drop for CommandHookHandle {
-    fn drop(&mut self) {
-        unsafe {
-            let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut CommandHookUd;
-            // we assume b is not null. this should be safe.
-            // just drop it
-            drop(Rc::from_raw(b));
-        }
-    }
-}
-impl Drop for ServerHookHandle {
+impl Drop for HookHandle {
     fn drop(&mut self) {
-        unsafe {
-            let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut ServerHookUd;
-            // we assume b is not null. this should be safe.
-            // just drop it
-            drop(Rc::from_raw(b));
+        if self.freed.get() {
+            // already free'd.
+            return;
         }
-    }
-}
-impl Drop for PrintHookHandle {
-    fn drop(&mut self) {
+        self.freed.set(true);
         unsafe {
-            let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut PrintHookUd;
+            let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut HookUd;
             // we assume b is not null. this should be safe.
             // just drop it
             drop(Rc::from_raw(b));
         }
     }
 }
-impl Drop for TimerHookHandle {
-    fn drop(&mut self) {
-        if !self.alive.get() {
-            // already free'd.
-            return;
-        }
-        self.alive.set(false);
-        unsafe {
-            let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut TimerHookUd;
-            // we assume b is not null. this should be safe.
-            // just drop it
-            drop(Rc::from_raw(b));
+
+/// Handles a hook panic at the C-Rust ABI boundary.
+///
+/// # Safety
+///
+/// `ph` must be a valid pointer (see `std::ptr::read`).
+unsafe fn call_hook_protected<F: FnOnce() -> Eat + UnwindSafe>(
+    ph: *mut RawPh,
+    f: F
+) -> Eat {
+    match catch_unwind(f) {
+        Result::Ok(v @ _) => v,
+        Result::Err(e @ _) => {
+            // if it's a &str or String, just print it
+            if let Some(s) = e.downcast_ref::<&str>() {
+                hexchat_print_str(ph, s, false);
+            } else if let Some(s) = e.downcast_ref::<String>() {
+                hexchat_print_str(ph, &s, false);
+            } else if let Some(s) = e.downcast_ref::<Cow<'static, str>>() {
+                hexchat_print_str(ph, &s, false);
+            }
+            EAT_NONE
         }
     }
 }
 
 impl PluginHandle {
-    fn new(ph: *mut internals::Ph, info: PluginInfo) -> PluginHandle {
+    /// Wraps the raw handle.
+    ///
+    /// # Safety
+    ///
+    /// `ph` must be a valid pointer (see `std::ptr::read`).
+    unsafe fn new(ph: *mut RawPh, info: PluginInfo) -> PluginHandle {
         PluginHandle {
             ph, info, skip_pri_ck: false,
         }
@@ -372,7 +349,7 @@ impl PluginHandle {
     ///
     /// # Examples
     ///
-    /// ```
+    /// ```no_run
     /// use hexchat_plugin::PluginHandle;
     ///
     /// fn init(ph: &mut PluginHandle) {
@@ -515,10 +492,10 @@ impl PluginHandle {
     ///
     /// # Examples
     ///
-    /// ```
-    /// use hexchat_plugin::{PluginHandle, CommandHookHandle};
+    /// ```no_run
+    /// use hexchat_plugin::{PluginHandle, HookHandle};
     ///
-    /// fn register_commands(ph: &mut PluginHandle) -> Vec<CommandHookHandle> {
+    /// fn register_commands(ph: &mut PluginHandle) -> Vec<HookHandle> {
     ///     vec![
     ///     ph.hook_command("hello-world", |ph, arg, arg_eol| {
     ///         ph.print("Hello, World!");
@@ -527,47 +504,43 @@ impl PluginHandle {
     ///     ]
     /// }
     /// ```
-    pub fn hook_command<F>(&mut self, cmd: &str, cb: F, pri: i32, help: Option<&str>) -> CommandHookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + ::std::panic::RefUnwindSafe {
-        unsafe extern "C" fn callback(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, ud: *mut libc::c_void) -> libc::c_int {
-            // hook may unhook itself.
-            // however, we don't wanna free it until it has returned.
-            let f: Rc<CommandHookUd> = rc_clone_from_raw(ud as *const CommandHookUd);
-            let ph = f.1;
-            match catch_unwind(move || {
-                let word = Word::new(word);
-                let word_eol = WordEol::new(word_eol);
-                (f.0)(&mut PluginHandle::new(f.1, f.2), word, word_eol).do_eat as libc::c_int
-            }) {
-                Result::Ok(v @ _) => v,
-                Result::Err(e @ _) => {
-                    // if it's a &str or String, just print it
-                    if let Some(estr) = e.downcast_ref::<&str>() {
-                        hexchat_print_str(ph, estr, false);
-                    } else if let Some(estring) = e.downcast_ref::<String>() {
-                        hexchat_print_str(ph, &estring, false);
-                    }
-                    0 // EAT_NONE
-                }
-            }
+    pub fn hook_command<F>(&mut self, cmd: &str, cb: F, pri: i32, help: Option<&str>) -> HookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + RefUnwindSafe {
+        unsafe extern "C" fn callback(word: *const *const c_char, word_eol: *const *const c_char, ud: *mut c_void) -> c_int {
+            let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd);
+            (f)(word, word_eol, ptr::null()).do_eat as c_int
         }
-        let b: Rc<CommandHookUd> = Rc::new((Box::new(cb), self.ph, self.info));
+        let b: Rc<HookUd> = {
+            let ph = self.ph;
+            let info = self.info;
+            Rc::new(Box::new(move |word, word_eol, _| {
+                let cb = &cb;
+                unsafe {
+                    call_hook_protected(ph, move || {
+                        let mut ph = PluginHandle::new(ph, info);
+                        let word = Word::new(word);
+                        let word_eol = WordEol::new(word_eol);
+                        cb(&mut ph, word, word_eol)
+                    })
+                }
+            }))
+        };
         let name = CString::new(cmd).unwrap();
         let help_text = help.map(CString::new).map(Result::unwrap);
         let bp = Rc::into_raw(b);
         unsafe {
-            let res = ((*self.ph).hexchat_hook_command)(self.ph, name.as_ptr(), pri as libc::c_int, callback, help_text.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()), bp as *mut _);
+            let res = ((*self.ph).hexchat_hook_command)(self.ph, name.as_ptr(), pri as c_int, callback, help_text.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()), bp as *mut _);
             assert!(!res.is_null());
-            CommandHookHandle { ph: self.ph, hh: res, _f: PhantomData }
+            HookHandle { ph: self.ph, hh: res, freed: Default::default(), _f: PhantomData }
         }
     }
     /// Sets a server hook.
     ///
     /// # Examples
     ///
-    /// ```
-    /// use hexchat_plugin::{PluginHandle, ServerHookHandle};
+    /// ```no_run
+    /// use hexchat_plugin::{PluginHandle, HookHandle};
     ///
-    /// fn register_server_hooks(ph: &mut PluginHandle) -> Vec<ServerHookHandle> {
+    /// fn register_server_hooks(ph: &mut PluginHandle) -> Vec<HookHandle> {
     ///     vec![
     ///     ph.hook_server("PRIVMSG", |ph, word, word_eol| {
     ///         if word.len() > 0 && word[0].starts_with('@') {
@@ -578,50 +551,47 @@ impl PluginHandle {
     ///     ]
     /// }
     /// ```
-    pub fn hook_server<F>(&mut self, cmd: &str, cb: F, pri: i32) -> ServerHookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + ::std::panic::RefUnwindSafe {
+    pub fn hook_server<F>(&mut self, cmd: &str, cb: F, pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + RefUnwindSafe {
         self.hook_server_attrs(cmd, move |ph, w, we, _| cb(ph, w, we), pri)
     }
     /// Sets a server hook, with attributes.
-    pub fn hook_server_attrs<F>(&mut self, cmd: &str, cb: F, pri: i32) -> ServerHookHandle where F: Fn(&mut PluginHandle, Word, WordEol, EventAttrs) -> Eat + 'static + ::std::panic::RefUnwindSafe {
-        unsafe extern "C" fn callback(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, attrs: *const internals::HexchatEventAttrs, ud: *mut libc::c_void) -> libc::c_int {
-            // hook may unhook itself.
-            // however, we don't wanna free it until it has returned.
-            let f: Rc<ServerHookUd> = rc_clone_from_raw(ud as *const ServerHookUd);
-            let ph = f.1;
-            match catch_unwind(move || {
-                let word = Word::new(word);
-                let word_eol = WordEol::new(word_eol);
-                (f.0)(&mut PluginHandle::new(f.1, f.2), word, word_eol, (&*attrs).into()).do_eat as libc::c_int
-            }) {
-                Result::Ok(v @ _) => v,
-                Result::Err(e @ _) => {
-                    // if it's a &str or String, just print it
-                    if let Some(estr) = e.downcast_ref::<&str>() {
-                        hexchat_print_str(ph, estr, false);
-                    } else if let Some(estring) = e.downcast_ref::<String>() {
-                        hexchat_print_str(ph, &estring, false);
-                    }
-                    0 // EAT_NONE
-                }
-            }
+    pub fn hook_server_attrs<F>(&mut self, cmd: &str, cb: F, pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word, WordEol, EventAttrs) -> Eat + 'static + RefUnwindSafe {
+        unsafe extern "C" fn callback(word: *const *const c_char, word_eol: *const *const c_char, attrs: *const RawAttrs, ud: *mut c_void) -> c_int {
+            let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd);
+            (f)(word, word_eol, attrs).do_eat as c_int
         }
-        let b: Rc<ServerHookUd> = Rc::new((Box::new(cb), self.ph, self.info));
+        let b: Rc<HookUd> = {
+            let ph = self.ph;
+            let info = self.info;
+            Rc::new(Box::new(move |word, word_eol, attrs| {
+                let cb = &cb;
+                unsafe {
+                    call_hook_protected(ph, move || {
+                        let mut ph = PluginHandle::new(ph, info);
+                        let word = Word::new(word);
+                        let word_eol = WordEol::new(word_eol);
+                        let attrs = (&*attrs).into();
+                        cb(&mut ph, word, word_eol, attrs)
+                    })
+                }
+            }))
+        };
         let name = CString::new(cmd).unwrap();
         let bp = Rc::into_raw(b);
         unsafe {
-            let res = ((*self.ph).hexchat_hook_server_attrs)(self.ph, name.as_ptr(), pri as libc::c_int, callback, bp as *mut _);
+            let res = ((*self.ph).hexchat_hook_server_attrs)(self.ph, name.as_ptr(), pri as c_int, callback, bp as *mut _);
             assert!(!res.is_null());
-            ServerHookHandle { ph: self.ph, hh: res, _f: PhantomData }
+            HookHandle { ph: self.ph, hh: res, freed: Default::default(), _f: PhantomData }
         }
     }
     /// Sets a print hook.
     ///
     /// # Examples
     ///
-    /// ```
-    /// use hexchat_plugin::{PluginHandle, PrintHookHandle};
+    /// ```no_run
+    /// use hexchat_plugin::{PluginHandle, HookHandle};
     ///
-    /// fn register_print_hooks(ph: &mut PluginHandle) -> Vec<PrintHookHandle> {
+    /// fn register_print_hooks(ph: &mut PluginHandle) -> Vec<HookHandle> {
     ///     vec![
     ///     ph.hook_print("Channel Message", |ph, arg| {
     ///         if let Some(nick) = arg.get(0) {
@@ -634,84 +604,96 @@ impl PluginHandle {
     ///     ]
     /// }
     /// ```
-    pub fn hook_print<F>(&mut self, name: &str, cb: F, mut pri: i32) -> PrintHookHandle where F: Fn(&mut PluginHandle, Word) -> Eat + 'static + ::std::panic::RefUnwindSafe {
+    pub fn hook_print<F>(&mut self, name: &str, cb: F, mut pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word) -> Eat + 'static + RefUnwindSafe {
         // hmm, is there any way to avoid this code duplication?
         // hook_print is special because dummy prints (keypresses, Close Context) are handled
         // through here, but never through hook_print_attrs. :/
-        unsafe extern "C" fn callback(word: *const *const libc::c_char, ud: *mut libc::c_void) -> libc::c_int {
-            // hook may unhook itself.
-            // however, we don't wanna free it until it has returned.
-            let f: Rc<PrintHookUd> = rc_clone_from_raw(ud as *const PrintHookUd);
-            let ph = f.1;
-            match catch_unwind(move || {
-                let word = Word::new(word);
-                (f.0)(&mut PluginHandle::new(f.1, f.2), word, EventAttrs::new()).do_eat as libc::c_int
-            }) {
-                Result::Ok(v @ _) => v,
-                Result::Err(e @ _) => {
-                    // if it's a &str or String, just print it
-                    if let Some(estr) = e.downcast_ref::<&str>() {
-                        hexchat_print_str(ph, estr, false);
-                    } else if let Some(estring) = e.downcast_ref::<String>() {
-                        hexchat_print_str(ph, &estring, false);
-                    }
-                    0 // EAT_NONE
-                }
-            }
+        unsafe extern "C" fn callback(word: *const *const c_char, ud: *mut c_void) -> c_int {
+            let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd);
+            (f)(word, ptr::null(), ptr::null()).do_eat as c_int
         }
-        if name == "Close Context" && (pri as libc::c_int == libc::c_int::min_value()) && !self.skip_pri_ck {
+        if name == "Close Context" && (pri as c_int == c_int::min_value()) && !self.skip_pri_ck {
             self.print("Warning: Attempted to hook Close Context with priority INT_MIN. Adjusting to INT_MIN+1.");
-            pri = (libc::c_int::min_value() + 1) as i32;
+            pri = (c_int::min_value() + 1) as i32;
         }
-        let b: Rc<PrintHookUd> = Rc::new((Box::new(move |ph, w, _| cb(ph, w)), self.ph, self.info));
+        let b: Rc<HookUd> = {
+            let ph = self.ph;
+            let info = self.info;
+            Rc::new(Box::new(move |word, _, _| {
+                let cb = &cb;
+                unsafe {
+                    call_hook_protected(ph, move || {
+                        let mut ph = PluginHandle::new(ph, info);
+                        let word = Word::new(word);
+                        cb(&mut ph, word)
+                    })
+                }
+            }))
+        };
         let name = CString::new(name).unwrap();
         let bp = Rc::into_raw(b);
         unsafe {
-            let res = ((*self.ph).hexchat_hook_print)(self.ph, name.as_ptr(), pri as libc::c_int, callback, bp as *mut _);
+            let res = ((*self.ph).hexchat_hook_print)(self.ph, name.as_ptr(), pri as c_int, callback, bp as *mut _);
             assert!(!res.is_null());
-            PrintHookHandle { ph: self.ph, hh: res, _f: PhantomData }
+            HookHandle { ph: self.ph, hh: res, freed: Default::default(), _f: PhantomData }
         }
     }
     /// Sets a print hook, with attributes.
-    pub fn hook_print_attrs<F>(&mut self, name: &str, cb: F, pri: i32) -> PrintHookHandle where F: Fn(&mut PluginHandle, Word, EventAttrs) -> Eat + 'static + ::std::panic::RefUnwindSafe {
-        unsafe extern "C" fn callback(word: *const *const libc::c_char, attrs: *const internals::HexchatEventAttrs, ud: *mut libc::c_void) -> libc::c_int {
-            // hook may unhook itself.
-            // however, we don't wanna free it until it has returned.
-            let f: Rc<PrintHookUd> = rc_clone_from_raw(ud as *const PrintHookUd);
-            let ph = f.1;
-            match catch_unwind(move || {
-                let word = Word::new(word);
-                (f.0)(&mut PluginHandle::new(f.1, f.2), word, (&*attrs).into()).do_eat as libc::c_int
-            }) {
-                Result::Ok(v @ _) => v,
-                Result::Err(e @ _) => {
-                    // if it's a &str or String, just print it
-                    if let Some(estr) = e.downcast_ref::<&str>() {
-                        hexchat_print_str(ph, estr, false);
-                    } else if let Some(estring) = e.downcast_ref::<String>() {
-                        hexchat_print_str(ph, &estring, false);
-                    }
-                    0 // EAT_NONE
-                }
-            }
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// use hexchat_plugin::{PluginHandle, HookHandle};
+    ///
+    /// fn register_print_hooks(ph: &mut PluginHandle) -> Vec<HookHandle> {
+    ///     vec![
+    ///     ph.hook_print_attrs("Channel Message", |ph, arg, attrs| {
+    ///         if let Some(nick) = arg.get(0) {
+    ///             if *nick == "KnOwN_SpAmMeR" {
+    ///                 return hexchat_plugin::EAT_ALL
+    ///             }
+    ///         }
+    ///         hexchat_plugin::EAT_NONE
+    ///     }, hexchat_plugin::PRI_NORM),
+    ///     ]
+    /// }
+    /// ```
+    pub fn hook_print_attrs<F>(&mut self, name: &str, cb: F, pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word, EventAttrs) -> Eat + 'static + RefUnwindSafe {
+        unsafe extern "C" fn callback(word: *const *const c_char, attrs: *const RawAttrs, ud: *mut c_void) -> c_int {
+            let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd);
+            (f)(word, ptr::null(), attrs).do_eat as c_int
         }
-        let b: Rc<PrintHookUd> = Rc::new((Box::new(cb), self.ph, self.info));
+        let b: Rc<HookUd> = {
+            let ph = self.ph;
+            let info = self.info;
+            Rc::new(Box::new(move |word, _, attrs| {
+                let cb = &cb;
+                unsafe {
+                    call_hook_protected(ph, move || {
+                        let mut ph = PluginHandle::new(ph, info);
+                        let word = Word::new(word);
+                        let attrs = (&*attrs).into();
+                        cb(&mut ph, word, attrs)
+                    })
+                }
+            }))
+        };
         let name = CString::new(name).unwrap();
         let bp = Rc::into_raw(b);
         unsafe {
-            let res = ((*self.ph).hexchat_hook_print_attrs)(self.ph, name.as_ptr(), pri as libc::c_int, callback, bp as *mut _);
+            let res = ((*self.ph).hexchat_hook_print_attrs)(self.ph, name.as_ptr(), pri as c_int, callback, bp as *mut _);
             assert!(!res.is_null());
-            PrintHookHandle { ph: self.ph, hh: res, _f: PhantomData }
+            HookHandle { ph: self.ph, hh: res, freed: Default::default(), _f: PhantomData }
         }
     }
     /// Sets a timer hook
     ///
     /// # Examples
     ///
-    /// ```
-    /// use hexchat_plugin::{PluginHandle, TimerHookHandle};
+    /// ```no_run
+    /// use hexchat_plugin::{PluginHandle, HookHandle};
     ///
-    /// fn register_timers(ph: &mut PluginHandle) -> Vec<TimerHookHandle> {
+    /// fn register_timers(ph: &mut PluginHandle) -> Vec<HookHandle> {
     ///     vec![
     ///     ph.hook_timer(2000, |ph| {
     ///         ph.print("timer up!");
@@ -720,63 +702,60 @@ impl PluginHandle {
     ///     ]
     /// }
     /// ```
-    pub fn hook_timer<F>(&mut self, timeout: i32, cb: F) -> TimerHookHandle where F: Fn(&mut PluginHandle) -> bool + 'static + ::std::panic::RefUnwindSafe {
-        unsafe extern "C" fn callback(ud: *mut libc::c_void) -> libc::c_int {
-            // hook may unhook itself.
-            // however, we don't wanna free it until it has returned.
-            let f: Rc<TimerHookUd> = rc_clone_from_raw(ud as *const TimerHookUd);
-            let alive = f.1.clone(); // clone the alive because why not
-            let f = f.0.clone();
-            let ph = f.1;
-            // we could technically free the Rc<TimerHookUd> here, I guess
-            match catch_unwind(move || {
-                (f.0)(&mut PluginHandle::new(f.1, f.2))
-            }) {
-                Result::Ok(true) => 1,
-                Result::Ok(false) => {
-                    // avoid double-free
-                    if !alive.get() {
-                        return 0;
-                    }
-                    // mark it no longer alive
-                    alive.set(false);
-                    // HexChat will automatically free the hook.
-                    // we just need to free the userdata.
-                    drop(Rc::from_raw(ud as *const TimerHookUd));
-                    0
-                },
-                Result::Err(e @ _) => {
-                    // if it's a &str or String, just print it
-                    if let Some(estr) = e.downcast_ref::<&str>() {
-                        hexchat_print_str(ph, estr, false);
-                    } else if let Some(estring) = e.downcast_ref::<String>() {
-                        hexchat_print_str(ph, &estring, false);
-                    }
-                    // avoid double-free
-                    if !alive.get() {
-                        return 0;
+    pub fn hook_timer<F>(&mut self, timeout: i32, cb: F) -> HookHandle where F: Fn(&mut PluginHandle) -> bool + 'static + RefUnwindSafe {
+        unsafe extern "C" fn callback(ud: *mut c_void) -> c_int {
+            let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd);
+            (f)(ptr::null(), ptr::null(), ptr::null()).do_eat as c_int
+        }
+        let freed = Rc::new(Cell::new(false));
+        // helps us clean up the thing when returning false
+        let dropper = Rc::new(Cell::new(None));
+        let b: Rc<HookUd> = {
+            let ph = self.ph;
+            let info = self.info;
+            let freed = AssertUnwindSafe(Rc::clone(&freed));
+            let dropper = AssertUnwindSafe(Rc::clone(&dropper));
+            Rc::new(Box::new(move |_, _, _| {
+                let cb = &cb;
+                let res = unsafe {
+                    call_hook_protected(ph, move || {
+                        let mut ph = PluginHandle::new(ph, info);
+                        if cb(&mut ph) {
+                            EAT_HEXCHAT
+                        } else {
+                            EAT_NONE
+                        }
+                    })
+                };
+                if res == EAT_NONE && !freed.get() {
+                    freed.set(true);
+                    unsafe {
+                        // drop may panic
+                        // and so may unwrap
+                        // but we must not panic
+                        // (kinda silly to abuse call_hook_protected here
+                        // but hey, it works and it helps with stuff)
+                        call_hook_protected(ph, || {
+                            drop(Rc::from_raw(dropper.take().unwrap()));
+                            EAT_NONE
+                        });
                     }
-                    // mark it no longer alive
-                    alive.set(false);
-                    // HexChat will automatically free the hook.
-                    // we just need to free the userdata.
-                    drop(Rc::from_raw(ud as *const TimerHookUd));
-                    0
                 }
-            }
-        }
-        let alive = Rc::new(Cell::new(true));
-        let b: Rc<TimerHookUd> = Rc::new((Rc::new((Box::new(cb), self.ph, self.info)), alive.clone()));
+                res
+            }))
+        };
         let bp = Rc::into_raw(b);
+        dropper.set(Some(bp));
         unsafe {
-            let res = ((*self.ph).hexchat_hook_timer)(self.ph, timeout as libc::c_int, callback, bp as *mut _);
+            let res = ((*self.ph).hexchat_hook_timer)(self.ph, timeout as c_int, callback, bp as *mut _);
             assert!(!res.is_null());
-            TimerHookHandle { ph: self.ph, hh: res, alive, _f: PhantomData }
+            HookHandle { ph: self.ph, hh: res, freed: freed, _f: PhantomData }
         }
     }
 
     /// Prints to the hexchat buffer.
     // this checks the context internally. if it didn't, it wouldn't be safe to have here.
+    // TODO write_fmt instead.
     pub fn print<T: ToString>(&mut self, s: T) {
         let s = s.to_string();
         unsafe {
@@ -785,7 +764,7 @@ impl PluginHandle {
     }
 
     /// Returns information on the current context.
-    pub fn get_info(&mut self, id: InfoId) -> Option<String> {
+    pub fn get_info<'a>(&'a mut self, id: InfoId) -> Option<&'a str> {
         let ph = self.ph;
         let id_cstring = CString::new(&*id.name()).unwrap();
         unsafe {
@@ -793,7 +772,7 @@ impl PluginHandle {
             if res.is_null() {
                 None
             } else {
-                let s = CStr::from_ptr(res).to_owned().into_string();
+                let s = CStr::from_ptr(res).to_str();
                 Some(s.expect("non-utf8 word - broken hexchat"))
             }
         }
@@ -834,8 +813,8 @@ impl<'a> EventAttrs<'a> {
     }
 }
 
-impl<'a> From<&'a internals::HexchatEventAttrs> for EventAttrs<'a> {
-    fn from(other: &'a internals::HexchatEventAttrs) -> EventAttrs<'a> {
+impl<'a> From<&'a RawAttrs> for EventAttrs<'a> {
+    fn from(other: &'a RawAttrs) -> EventAttrs<'a> {
         EventAttrs {
             server_time: if other.server_time_utc > 0 { Some(UNIX_EPOCH + Duration::from_secs(other.server_time_utc as u64)) } else { None },
             _dummy: PhantomData,
@@ -912,11 +891,11 @@ impl<'a> EnsureValidContext<'a> {
         assert!(mode.is_ascii(), "mode must be ascii");
         assert!(mpl >= 0, "mpl must be non-negative");
         let v: Vec<CString> = iter.into_iter().map(|s| CString::new(s).unwrap()).collect();
-        let mut v2: Vec<*const libc::c_char> = (&v).iter().map(|x| x.as_ptr()).collect();
-        let arr: &mut [*const libc::c_char] = &mut *v2;
+        let mut v2: Vec<*const c_char> = (&v).iter().map(|x| x.as_ptr()).collect();
+        let arr: &mut [*const c_char] = &mut *v2;
         unsafe {
-            ((*ph).hexchat_send_modes)(ph, arr.as_mut_ptr(), arr.len() as libc::c_int,
-                mpl as libc::c_int, sign as libc::c_char, mode as libc::c_char)
+            ((*ph).hexchat_send_modes)(ph, arr.as_mut_ptr(), arr.len() as c_int,
+                mpl as c_int, sign as c_char, mode as c_char)
         }
     }
 
@@ -948,7 +927,7 @@ impl<'a> EnsureValidContext<'a> {
                 panic!("too many arguments to emit_print (max 4), or iterator not fused");
             }
         }
-        let mut argv: [*const libc::c_char; 5] = [ptr::null(); 5];
+        let mut argv: [*const c_char; 5] = [ptr::null(); 5];
         for i in 0..4 {
             argv[i] = args_cs[i].as_ref().map_or(ptr::null(), |s| s.as_ptr());
         }
@@ -974,7 +953,7 @@ impl<'a> EnsureValidContext<'a> {
                 panic!("too many arguments to emit_print_attrs (max 4), or iterator not fused");
             }
         }
-        let mut argv: [*const libc::c_char; 5] = [ptr::null(); 5];
+        let mut argv: [*const c_char; 5] = [ptr::null(); 5];
         for i in 0..4 {
             argv[i] = args_cs[i].as_ref().map_or(ptr::null(), |s| s.as_ptr());
         }
@@ -1008,22 +987,30 @@ impl<'a> EnsureValidContext<'a> {
     }
 
     /// Sets a command hook
-    pub fn hook_command<F>(&mut self, cmd: &str, cb: F, pri: i32, help: Option<&str>) -> CommandHookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + ::std::panic::RefUnwindSafe {
+    pub fn hook_command<F>(&mut self, cmd: &str, cb: F, pri: i32, help: Option<&str>) -> HookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + RefUnwindSafe {
         self.ph.hook_command(cmd, cb, pri, help)
     }
     /// Sets a server hook
-    pub fn hook_server<F>(&mut self, cmd: &str, cb: F, pri: i32) -> ServerHookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + ::std::panic::RefUnwindSafe {
+    pub fn hook_server<F>(&mut self, cmd: &str, cb: F, pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + RefUnwindSafe {
         self.ph.hook_server(cmd, cb, pri)
     }
+    /// Sets a server hook with attributes
+    pub fn hook_server_attrs<F>(&mut self, cmd: &str, cb: F, pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word, WordEol, EventAttrs) -> Eat + 'static + RefUnwindSafe {
+        self.ph.hook_server_attrs(cmd, cb, pri)
+    }
     /// Sets a print hook
-    pub fn hook_print<F>(&mut self, name: &str, cb: F, pri: i32) -> PrintHookHandle where F: Fn(&mut PluginHandle, Word) -> Eat + 'static + ::std::panic::RefUnwindSafe {
+    pub fn hook_print<F>(&mut self, name: &str, cb: F, pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word) -> Eat + 'static + RefUnwindSafe {
         self.ph.hook_print(name, cb, pri)
     }
+    /// Sets a print hook with attributes
+    pub fn hook_print_attrs<F>(&mut self, name: &str, cb: F, pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word, EventAttrs) -> Eat + 'static + RefUnwindSafe {
+        self.ph.hook_print_attrs(name, cb, pri)
+    }
     /// Sets a timer hook
-    pub fn hook_timer<F>(&mut self, timeout: i32, cb: F) -> TimerHookHandle where F: Fn(&mut PluginHandle) -> bool + 'static + ::std::panic::RefUnwindSafe {
+    pub fn hook_timer<F>(&mut self, timeout: i32, cb: F) -> HookHandle where F: Fn(&mut PluginHandle) -> bool + 'static + RefUnwindSafe {
         self.ph.hook_timer(timeout, cb)
     }
-    pub fn get_info(&mut self, id: InfoId) -> Option<String> {
+    pub fn get_info<'b>(&'b mut self, id: InfoId) -> Option<&'b str> {
         self.ph.get_info(id)
     }
 }
@@ -1033,19 +1020,21 @@ impl<'a> EnsureValidContext<'a> {
 // ******* //
 
 // Type aliases, used for safety type checking.
-/// Userdata type used by a command hook.
+// /// Userdata type used by a command hook.
 // We actually do want RefUnwindSafe. This function may be called multiple times, and it's not
 // automatically invalidated if it panics, so it may be called again after it panics. If you need
 // mutable state, std provides std::sync::Mutex which has poisoning. Other interior mutability with
 // poisoning could also be used. std doesn't have anything for single-threaded performance (yet),
 // but hexchat isn't particularly performance-critical.
-type CommandHookUd = (Box<dyn Fn(&mut PluginHandle, Word, WordEol) -> Eat + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo);
-/// Userdata type used by a server hook.
-type ServerHookUd = (Box<dyn Fn(&mut PluginHandle, Word, WordEol, EventAttrs) -> Eat + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo);
-/// Userdata type used by a print hook.
-type PrintHookUd = (Box<dyn Fn(&mut PluginHandle, Word, EventAttrs) -> Eat + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo);
-/// Userdata type used by a timer hook.
-type TimerHookUd = (Rc<(Box<dyn Fn(&mut PluginHandle) -> bool + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo)>, Rc<Cell<bool>>);
+// type CommandHookUd = Box<dyn Fn(Word, WordEol) -> Eat + ::std::panic::RefUnwindSafe>;
+// /// Userdata type used by a server hook.
+// type ServerHookUd = Box<dyn Fn(Word, WordEol, EventAttrs) -> Eat + ::std::panic::RefUnwindSafe>;
+// /// Userdata type used by a print hook.
+// type PrintHookUd = Box<dyn Fn(Word, EventAttrs) -> Eat + ::std::panic::RefUnwindSafe>;
+// /// Userdata type used by a timer hook.
+// type TimerHookUd = Box<dyn Fn() -> bool + ::std::panic::RefUnwindSafe>;
+/// Userdata type used by a hook
+type HookUd = Box<dyn Fn(*const *const c_char, *const *const c_char, *const RawAttrs) -> Eat + RefUnwindSafe>;
 
 /// The contents of an empty CStr.
 ///
@@ -1059,7 +1048,7 @@ const EMPTY_CSTRING_DATA: &[u8] = b"\0";
 ///
 /// Panics if b contains embedded nuls.
 // TODO const fn, once that's possible
-fn cstr(b: &'static [u8]) -> *const libc::c_char {
+fn cstr(b: &'static [u8]) -> *const c_char {
     CStr::from_bytes_with_nul(b).unwrap().as_ptr()
 }
 
@@ -1079,20 +1068,20 @@ unsafe fn rc_clone_from_raw<T>(ptr: *const T) -> Rc<T> {
 ///
 /// This function doesn't validate the context.
 unsafe fn wrap_context(ph: &mut PluginHandle, ctx: *const internals::HexchatContext) -> Context {
-    let ctxp = std::panic::AssertUnwindSafe(Rc::new(ctx));
+    let ctxp = AssertUnwindSafe(Rc::new(ctx));
     let weak_ctxp = Rc::downgrade(&ctxp); // calling the Closure should drop the Context (sort of)
-    let closure: Rc<Cell<Option<PrintHookHandle>>> = Rc::new(Cell::new(None));
-    let hook = std::panic::AssertUnwindSafe(Rc::downgrade(&closure)); // dropping the Context should drop the Closure
+    let closure: Rc<Cell<Option<HookHandle>>> = Rc::new(Cell::new(None));
+    let hook = AssertUnwindSafe(Rc::downgrade(&closure)); // dropping the Context should drop the Closure
     ph.skip_pri_ck = true;
     closure.set(Some(ph.hook_print("Close Context", move |ph, _| {
         // need to be careful not to recurse or leak memory
         let ph = ph.ph;
         let ctx = ((*ph).hexchat_get_context)(ph);
         if **ctxp == ctx {
-            let _: Option<PrintHookHandle> = hook.upgrade().unwrap().replace(None);
+            let _: Option<HookHandle> = hook.upgrade().unwrap().replace(None);
         }
         EAT_NONE
-    }, libc::c_int::min_value())));
+    }, c_int::min_value())));
     ph.skip_pri_ck = false;
     Context { ctx: weak_ctxp, closure }
 }
@@ -1106,7 +1095,7 @@ unsafe fn wrap_context(ph: &mut PluginHandle, ctx: *const internals::HexchatCont
 /// # Panics
 ///
 /// Panics if panic_on_nul is true and the string contains embedded nuls.
-unsafe fn hexchat_print_str(ph: *mut internals::Ph, s: &str, panic_on_nul: bool) {
+unsafe fn hexchat_print_str(ph: *mut RawPh, s: &str, panic_on_nul: bool) {
     match CString::new(s) {
         Result::Ok(cs @ _) => {
             let csr: &CStr = &cs;
@@ -1116,20 +1105,20 @@ unsafe fn hexchat_print_str(ph: *mut internals::Ph, s: &str, panic_on_nul: bool)
     }
 }
 
-/// Helper to manage owned internals::HexchatEventAttrs
-struct HexchatEventAttrsHelper(*mut internals::HexchatEventAttrs, *mut internals::Ph);
+/// Helper to manage owned RawAttrs
+struct HexchatEventAttrsHelper(*mut RawAttrs, *mut RawPh);
 
 impl HexchatEventAttrsHelper {
-    fn new(ph: *mut internals::Ph) -> Self {
+    fn new(ph: *mut RawPh) -> Self {
         HexchatEventAttrsHelper(unsafe { ((*ph).hexchat_event_attrs_create)(ph) }, ph)
     }
 
-    fn new_with(ph: *mut internals::Ph, attrs: EventAttrs) -> Self {
+    fn new_with(ph: *mut RawPh, attrs: EventAttrs) -> Self {
         let helper = Self::new(ph);
         let v = attrs.server_time.or(Some(UNIX_EPOCH)).map(|st| match st.duration_since(UNIX_EPOCH) {
             Ok(n) => n.as_secs(),
             Err(_) => 0
-        }).filter(|&st| st < (libc::time_t::max_value() as u64)).unwrap() as libc::time_t;
+        }).filter(|&st| st < (time_t::max_value() as u64)).unwrap() as time_t;
         unsafe { (*helper.0).server_time_utc = v; }
         helper
     }
@@ -1156,8 +1145,8 @@ struct PhUserdata {
 /// This function is unsafe because it doesn't check if the pointer is valid.
 ///
 /// Improper use of this function can leak memory.
-unsafe fn put_userdata(ph: *mut internals::Ph, ud: Rc<PhUserdata>) {
-    (*ph).userdata = Rc::into_raw(ud) as *mut libc::c_void;
+unsafe fn put_userdata(ph: *mut RawPh, ud: Rc<PhUserdata>) {
+    (*ph).userdata = Rc::into_raw(ud) as *mut c_void;
 }
 
 // /// Clones the userdata from the plugin handle.
@@ -1165,7 +1154,7 @@ unsafe fn put_userdata(ph: *mut internals::Ph, ud: Rc<PhUserdata>) {
 // /// # Safety
 // ///
 // /// This function is unsafe because it doesn't check if the pointer is valid.
-// unsafe fn clone_userdata(ph: *mut internals::Ph) -> Option<Rc<PhUserdata>> {
+// unsafe fn clone_userdata(ph: *mut RawPh) -> Option<Rc<PhUserdata>> {
 //     let ptr = (*ph).userdata as *const PhUserdata;
 //     if ptr.is_null() {
 //         None
@@ -1179,7 +1168,7 @@ unsafe fn put_userdata(ph: *mut internals::Ph, ud: Rc<PhUserdata>) {
 /// # Safety
 ///
 /// This function is unsafe because it doesn't check if the pointer is valid.
-unsafe fn pop_userdata(ph: *mut internals::Ph) -> Rc<PhUserdata> {
+unsafe fn pop_userdata(ph: *mut RawPh) -> Rc<PhUserdata> {
     Rc::from_raw(mem::replace(&mut (*ph).userdata, ptr::null_mut()) as *mut PhUserdata)
 }
 
@@ -1188,11 +1177,11 @@ unsafe fn pop_userdata(ph: *mut internals::Ph) -> Rc<PhUserdata> {
 // *********************** //
 
 #[doc(hidden)]
-pub unsafe fn hexchat_plugin_init<T>(plugin_handle: *mut libc::c_void,
-                                     plugin_name: *mut *const libc::c_char,
-                                     plugin_desc: *mut *const libc::c_char,
-                                     plugin_version: *mut *const libc::c_char,
-                                     arg: *const libc::c_char) -> libc::c_int
+pub unsafe fn hexchat_plugin_init<T>(plugin_handle: *mut c_void,
+                                     plugin_name: *mut *const c_char,
+                                     plugin_desc: *mut *const c_char,
+                                     plugin_version: *mut *const c_char,
+                                     arg: *const c_char) -> c_int
                                      where T: Plugin + Default + 'static {
     if plugin_handle.is_null() || plugin_name.is_null() || plugin_desc.is_null() || plugin_version.is_null() {
         // we can't really do anything here.
@@ -1200,7 +1189,7 @@ pub unsafe fn hexchat_plugin_init<T>(plugin_handle: *mut libc::c_void,
         // TODO maybe call abort.
         return 0;
     }
-    let ph = plugin_handle as *mut internals::Ph;
+    let ph = plugin_handle as *mut RawPh;
     // clear the "userdata" field first thing - if the deinit function gets called (wrong hexchat
     // version, other issues), we don't wanna try to drop the hexchat_dummy or hexchat_read_fd
     // function as if it were a Box!
@@ -1290,17 +1279,17 @@ pub unsafe fn hexchat_plugin_init<T>(plugin_handle: *mut libc::c_void,
 }
 
 #[doc(hidden)]
-pub unsafe fn hexchat_plugin_deinit<T>(plugin_handle: *mut libc::c_void) -> libc::c_int where T: Plugin {
+pub unsafe fn hexchat_plugin_deinit<T>(plugin_handle: *mut c_void) -> c_int where T: Plugin {
     let mut safe_to_unload = 1;
     // plugin_handle should never be null, but just in case.
     if !plugin_handle.is_null() {
-        let ph = plugin_handle as *mut internals::Ph;
+        let ph = plugin_handle as *mut RawPh;
         // userdata should also never be null.
         if !(*ph).userdata.is_null() {
             {
                 let mut info: Option<PluginInfo> = None;
                 {
-                    let mut ausinfo = ::std::panic::AssertUnwindSafe(&mut info);
+                    let mut ausinfo = AssertUnwindSafe(&mut info);
                     safe_to_unload = if catch_unwind(move || {
                         let userdata = pop_userdata(ph);
                         **ausinfo = Some(userdata.pluginfo);
@@ -1327,15 +1316,15 @@ pub unsafe fn hexchat_plugin_deinit<T>(plugin_handle: *mut libc::c_void) -> libc
 macro_rules! hexchat_plugin {
     ($t:ty) => {
         #[no_mangle]
-        pub unsafe extern "C" fn hexchat_plugin_init(plugin_handle: *mut $crate::libc::c_void,
-                                              plugin_name: *mut *const $crate::libc::c_char,
-                                              plugin_desc: *mut *const $crate::libc::c_char,
-                                              plugin_version: *mut *const $crate::libc::c_char,
-                                              arg: *const $crate::libc::c_char) -> $crate::libc::c_int {
+        pub unsafe extern "C" fn hexchat_plugin_init(plugin_handle: *mut $crate::c_void,
+                                              plugin_name: *mut *const $crate::c_char,
+                                              plugin_desc: *mut *const $crate::c_char,
+                                              plugin_version: *mut *const $crate::c_char,
+                                              arg: *const $crate::c_char) -> $crate::c_int {
             $crate::hexchat_plugin_init::<$t>(plugin_handle, plugin_name, plugin_desc, plugin_version, arg)
         }
         #[no_mangle]
-        pub unsafe extern "C" fn hexchat_plugin_deinit(plugin_handle: *mut $crate::libc::c_void) -> $crate::libc::c_int {
+        pub unsafe extern "C" fn hexchat_plugin_deinit(plugin_handle: *mut $crate::c_void) -> $crate::c_int {
             $crate::hexchat_plugin_deinit::<$t>(plugin_handle)
         }
         // unlike what the documentation states, there's no need to define hexchat_plugin_get_info.