summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml4
-rw-r--r--src/internals.rs137
-rw-r--r--src/lib.rs186
-rw-r--r--src/mock/mod.rs465
4 files changed, 471 insertions, 321 deletions
diff --git a/Cargo.toml b/Cargo.toml
index a88a2b1..cde21f6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,5 +18,5 @@ nightly_tests = ["dep:selfref"]
 [dependencies]
 libc = "0.2"
 impl_trait = "0.1"
-ltptr = "0.1.1"
-selfref = { version = "0.1", optional = true }
+ltptr = "0.1.2"
+selfref = { version = "0.1.3", optional = true }
diff --git a/src/internals.rs b/src/internals.rs
index d1b714f..ec96299 100644
--- a/src/internals.rs
+++ b/src/internals.rs
@@ -20,30 +20,39 @@
 //! This also includes the hexchat_plugin struct, from hexchat-plugin.h. Note that we use the
 //! struct even on non-Windows platforms because it's a lot easier that way. Should be safe, tho.
 
+use std::marker::PhantomData;
+use std::marker::PhantomPinned;
+
 use libc;
 
-// apparently this is the right way to do these
-#[repr(i8)]
-pub enum HexchatList {
-    __One,
-    __Two,
+use ltptr::*;
+
+// apparently this is the new right way to do these
+#[repr(C)]
+pub struct HexchatList {
+    _data: libc::c_void,
+    // remove Send/Sync/etc
+    _marker: PhantomData<(*mut u8, PhantomPinned)>
 }
-#[repr(i8)]
-pub enum HexchatHook {
-    __One,
-    __Two,
+#[repr(C)]
+pub struct HexchatHook {
+    _data: libc::c_void,
+    // remove Send/Sync/etc
+    _marker: PhantomData<(*mut u8, PhantomPinned)>
 }
-#[repr(i8)]
-pub enum HexchatContext {
-    __One,
-    __Two,
+#[repr(C)]
+pub struct HexchatContext {
+    _data: libc::c_void,
+    // remove Send/Sync/etc
+    _marker: PhantomData<(*mut u8, PhantomPinned)>
 }
 
 // not in hexchat-plugin.h
-#[repr(i8)]
-pub enum PluginGuiHandle {
-    __One,
-    __Two,
+#[repr(C)]
+pub struct PluginGuiHandle {
+    _data: libc::c_void,
+    // remove Send/Sync/etc
+    _marker: PhantomData<(*mut u8, PhantomPinned)>
 }
 
 #[repr(C)]
@@ -51,147 +60,143 @@ pub struct HexchatEventAttrs {
     pub server_time_utc: libc::time_t,
 }
 
-pub type HexchatPlugin = Ph;
-
 #[repr(C)]
-pub struct Ph {
-    pub hexchat_hook_command: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+pub struct Ph<'ph> {
+    pub(crate) hexchat_hook_command: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             name: *const libc::c_char,
             pri: libc::c_int,
             /* CALLBACK */
             callback: unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, user_data: *mut libc::c_void) -> libc::c_int,
             help_text: *const libc::c_char,
             userdata: *mut libc::c_void) -> *const HexchatHook,
-    pub hexchat_hook_server: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_hook_server: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             name: *const libc::c_char,
             pri: libc::c_int,
             /* CALLBACK */
             callback: unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, user_data: *mut libc::c_void) -> libc::c_int,
             userdata: *mut libc::c_void) -> *const HexchatHook,
-    pub hexchat_hook_print: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_hook_print: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             name: *const libc::c_char,
             pri: libc::c_int,
             /* CALLBACK */
             callback: unsafe extern "C" fn(word: *const *const libc::c_char, user_data: *mut libc::c_void) -> libc::c_int,
             userdata: *mut libc::c_void) -> *const HexchatHook,
-    pub hexchat_hook_timer: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_hook_timer: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             timeout: libc::c_int,
             /* CALLBACK */
             callback: unsafe extern "C" fn(user_data: *mut libc::c_void) -> libc::c_int,
             userdata: *mut libc::c_void) -> *const HexchatHook,
-    pub hexchat_hook_fd: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_hook_fd: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             fd: libc::c_int,
             flags: libc::c_int,
             /* CALLBACK */
             callback: unsafe extern "C" fn(fd: libc::c_int, flags: libc::c_int, user_data: *mut libc::c_void) -> libc::c_int,
             userdata: *mut libc::c_void) -> *const HexchatHook,
-    pub hexchat_unhook: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_unhook: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             hook: *const HexchatHook) -> *const libc::c_void,
-    pub hexchat_print: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_print: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             text: *const libc::c_char),
     // this is VERY NAUGHTY.
+    // NOTE this is an *owned* pointer, so {Const,Mut}LtPtr don't apply here.
     // TODO see if hexchat's gonna provide a proper userdata field at some point.
-    // it appears this function isn't used anywhere by hexchat so we reuse its pointer.
-    // on linux, it's a dummy anyway.
-    // another option would've been to use one of the printf functions.
     // TODO test this on platforms hexchat doesn't build on, like AVR.
-    pub userdata: *mut libc::c_void,
-    /*pub hexchat_printf_do_not_use: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) userdata: *mut crate::PhUserdata<'ph>,
+    /*pub(crate) hexchat_printf_do_not_use: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             format: *const libc::c_char, ...),*/
-    pub hexchat_command: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_command: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             command: *const libc::c_char),
-    pub hexchat_commandf_do_not_use: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_commandf_do_not_use: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             format: *const libc::c_char, ...),
-    pub hexchat_nickcmp: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_nickcmp: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             s1: *const libc::c_char,
             s2: *const libc::c_char) -> libc::c_int,
-    pub hexchat_set_context: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_set_context: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             ctx: *const HexchatContext) -> libc::c_int,
-    pub hexchat_find_context: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_find_context: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             servname: *const libc::c_char,
             channel: *const libc::c_char) -> *const HexchatContext,
-    pub hexchat_get_context: unsafe extern "C" fn(ph: *mut HexchatPlugin) -> *const HexchatContext,
-    pub hexchat_get_info: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_get_context: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>) -> *const HexchatContext,
+    pub(crate) hexchat_get_info: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             id: *const libc::c_char) -> *const libc::c_char,
-    pub hexchat_get_prefs: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_get_prefs: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             name: *const libc::c_char,
             string: *mut *const libc::c_char,
             integer: *mut libc::c_int) -> libc::c_int,
-    pub hexchat_list_get: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_list_get: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             name: *const libc::c_char) -> *mut HexchatList,
-    pub hexchat_list_free: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_list_free: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             xlist: *const HexchatList),
-    pub hexchat_list_fields: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_list_fields: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             name: *const libc::c_char) -> *const *const libc::c_char,
-    pub hexchat_list_next: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_list_next: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             xlist: *const HexchatList) -> libc::c_int,
-    pub hexchat_list_str: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_list_str: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             xlist: *const HexchatList,
             name: *const libc::c_char) -> *const libc::c_char,
-    pub hexchat_list_int: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_list_int: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             xlist: *const HexchatList,
             name: *const libc::c_char) -> libc::c_int,
-    pub hexchat_plugingui_add: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_plugingui_add: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             filename: *const libc::c_char,
             name: *const libc::c_char,
             desc: *const libc::c_char,
             version: *const libc::c_char,
             reserved: *mut char) -> *const PluginGuiHandle,
-    pub hexchat_plugingui_remove: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_plugingui_remove: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             handle: *const PluginGuiHandle),
-    pub hexchat_emit_print: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_emit_print: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             event_name: *const libc::c_char, ...) -> libc::c_int,
-    pub hexchat_read_fd: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_read_fd: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             src: *const libc::c_void,
             buf: *mut char,
             len: *mut libc::c_int) -> libc::c_int,
-    pub hexchat_list_time: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_list_time: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             xlist: *const HexchatList,
             name: *const libc::c_char) -> libc::time_t,
-    pub hexchat_gettext: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_gettext: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             msgid: *const libc::c_char) -> *const libc::c_char,
-    pub hexchat_send_modes: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_send_modes: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             targets: *mut *const libc::c_char,
             ntargets: libc::c_int,
             modes_per_line: libc::c_int,
             sign: libc::c_char,
             mode: libc::c_char),
-    pub hexchat_strip: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_strip: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             string: *const libc::c_char,
             len: libc::c_int,
             flags: libc::c_int) -> *const libc::c_char,
-    pub hexchat_free: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_free: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             ptr: *const libc::c_void),
-    pub hexchat_pluginpref_set_str: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_pluginpref_set_str: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             var: *const libc::c_char,
             value: *const libc::c_char) -> libc::c_int,
-    pub hexchat_pluginpref_get_str: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_pluginpref_get_str: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             var: *const libc::c_char,
             dest: *mut char) -> libc::c_int,
-    pub hexchat_pluginpref_set_int: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_pluginpref_set_int: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             var: *const libc::c_char,
             value: libc::c_int) -> libc::c_int,
-    pub hexchat_pluginpref_get_int: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_pluginpref_get_int: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             var: *const libc::c_char) -> libc::c_int,
-    pub hexchat_pluginpref_delete: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_pluginpref_delete: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             var: *const libc::c_char) -> libc::c_int,
-    pub hexchat_pluginpref_list: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_pluginpref_list: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             dest: *mut char) -> libc::c_int,
-    pub hexchat_hook_server_attrs: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_hook_server_attrs: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             name: *const libc::c_char,
             pri: libc::c_int,
             /* CALLBACK */
             callback: unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, attrs: *const HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int,
             userdata: *mut libc::c_void) -> *const HexchatHook,
-    pub hexchat_hook_print_attrs: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_hook_print_attrs: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             name: *const libc::c_char,
             pri: libc::c_int,
             /* CALLBACK */
             callback: unsafe extern "C" fn(word: *const *const libc::c_char, attrs: *const HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int,
             userdata: *mut libc::c_void) -> *const HexchatHook,
-    pub hexchat_emit_print_attrs: unsafe extern "C" fn(ph: *mut HexchatPlugin, attrs: *const HexchatEventAttrs,
+    pub(crate) hexchat_emit_print_attrs: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>, attrs: *const HexchatEventAttrs,
             event_name: *const libc::c_char, ...) -> libc::c_int,
-    pub hexchat_event_attrs_create: unsafe extern "C" fn(ph: *mut HexchatPlugin) -> *mut HexchatEventAttrs,
-    pub hexchat_event_attrs_free: unsafe extern "C" fn(ph: *mut HexchatPlugin,
+    pub(crate) hexchat_event_attrs_create: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>) -> *mut HexchatEventAttrs,
+    pub(crate) hexchat_event_attrs_free: unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>,
             attrs: *mut HexchatEventAttrs),
 }
diff --git a/src/lib.rs b/src/lib.rs
index 308559a..51b4459 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -196,18 +196,16 @@
 extern crate impl_trait;
 #[doc(hidden)]
 pub extern crate libc;
-extern crate ltptr;
-
 
 // private macros
 
 /// Calls a function on a PluginHandle struct.
 macro_rules! ph_call {
     ($f:ident($ph:expr, $($args:expr),*)) => {
-        ((*$ph.data.ph).$f)($ph.plugin, $($args),*)
+        ((*$ph.data.as_raw()).$f)($ph.get_plugin(), $($args),*)
     };
     ($f:ident($ph:expr $(,)?)) => {
-        ((*$ph.data.ph).$f)($ph.plugin)
+        ((*$ph.data.as_raw()).$f)($ph.get_plugin())
     };
 }
 
@@ -229,7 +227,8 @@ pub use crate::strip::*;
 pub use crate::word::*;
 
 use crate::internals::HexchatEventAttrs as RawAttrs;
-use crate::internals::Ph as RawPh;
+#[doc(hidden)]
+pub use crate::internals::Ph as RawPh;
 use crate::pluginfo::PluginInfo;
 
 use std::borrow::Cow;
@@ -254,6 +253,8 @@ use std::time::{SystemTime, UNIX_EPOCH, Duration};
 
 #[doc(hidden)]
 pub use libc::{c_char, c_int, c_void, time_t};
+#[doc(hidden)]
+pub use ltptr::MutLtPtr;
 
 // ****** //
 // PUBLIC //
@@ -310,21 +311,6 @@ pub unsafe trait Plugin<'ph> {
 
 // Structs
 
-/// A `*mut RawPh` with a lifetime bolted to it.
-///
-/// This allows us to enforce a non-`'static` lifetime on the `Plugin`.
-// this is NOT public API
-#[doc(hidden)]
-#[repr(transparent)]
-#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
-pub struct LtPhPtr<'ph> {
-    ph: *mut RawPh,
-    // 'ph has to be invariant because RawPh is self-referential.
-    // "ideally" we'd want `&'ph mut RawPh<'ph>`, tho the `*mut` above would've
-    // had the same effect if `RawPh` were `RawPh<'ph>`.
-    _lt: PhantomData<fn(&'ph ()) -> &'ph ()>,
-}
-
 /// A hexchat plugin handle, used to register hooks and interact with hexchat.
 ///
 /// # Examples
@@ -337,8 +323,8 @@ pub struct LtPhPtr<'ph> {
 /// }
 /// ```
 pub struct PluginHandle<'ph> {
-    data: LtPhPtr<'ph>,
-    plugin: *mut RawPh,
+    data: MutLtPtr<'ph, RawPh<'ph>>,
+    guiplugin: *const internals::PluginGuiHandle,
     contexts: Contexts,
     // Used for registration
     info: PluginInfo,
@@ -423,7 +409,7 @@ pub struct EventAttrs<'a> {
 /// Likes to get caught on stuff. Unhooks when dropped.
 #[must_use = "Hooks must be stored somewhere and are automatically unhooked on Drop"]
 pub struct HookHandle<'ph, 'f> where 'f: 'ph {
-    ph: LtPhPtr<'ph>,
+    ph: MutLtPtr<'ph, RawPh<'ph>>,
     hh: *const internals::HexchatHook,
     freed: Rc<Cell<bool>>,
     // this does actually store an Rc<...>, but on the other side of the FFI.
@@ -433,7 +419,7 @@ pub struct HookHandle<'ph, 'f> where 'f: 'ph {
 /// A virtual plugin.
 #[must_use = "Virtual plugins must be stored somewhere and are automatically unregistered on Drop"]
 pub struct PluginEntryHandle<'ph> {
-    ph: LtPhPtr<'ph>,
+    ph: MutLtPtr<'ph, RawPh<'ph>>,
     entry: *const internals::PluginGuiHandle,
 }
 
@@ -442,7 +428,7 @@ pub struct PluginEntryHandle<'ph> {
 pub struct Context<'ph> {
     contexts: Contexts,
     ctx: RcWeak<*const internals::HexchatContext>,
-    _ph: PhantomData<&'ph RawPh>,
+    _ph: PhantomData<MutLtPtr<'ph, RawPh<'ph>>>,
 }
 
 /// The error returned by [`PluginHandle::with_context`] when the context is
@@ -618,7 +604,7 @@ impl<'ph, 'f> Drop for HookHandle<'ph, 'f> where 'f: 'ph {
         }
         self.freed.set(true);
         unsafe {
-            let b = ((*self.ph.ph).hexchat_unhook)(self.ph.ph, self.hh) as *mut HookUd<'f>;
+            let b = ((*self.ph.as_raw()).hexchat_unhook)(self.ph, self.hh) as *mut HookUd<'f>;
             // we assume b is not null. this should be safe.
             // just drop it
             drop(Rc::from_raw(b));
@@ -629,7 +615,7 @@ impl<'ph, 'f> Drop for HookHandle<'ph, 'f> where 'f: 'ph {
 impl<'ph> Drop for PluginEntryHandle<'ph> {
     fn drop(&mut self) {
         unsafe {
-            ((*self.ph.ph).hexchat_plugingui_remove)(self.ph.ph, self.entry);
+            ((*self.ph.as_raw()).hexchat_plugingui_remove)(self.ph, self.entry);
         }
     }
 }
@@ -655,7 +641,10 @@ impl_trait! {
 /// # Safety
 ///
 /// `ph` must be a valid pointer (see `std::ptr::read`).
-unsafe fn log_panic(ph: *mut RawPh, e: Box<dyn std::any::Any + Send + 'static>) {
+unsafe fn log_panic<'ph>(
+    ph: MutLtPtr<'ph, RawPh<'ph>>,
+    e: Box<dyn std::any::Any + Send + 'static>
+) {
     // if it's a &str or String, just print it
     if let Some(s) = e.downcast_ref::<&str>() {
         hexchat_print_str(ph, s, false);
@@ -667,7 +656,11 @@ unsafe fn log_panic(ph: *mut RawPh, e: Box<dyn std::any::Any + Send + 'static>)
         hexchat_print_str(ph, "couldn't log panic message", false);
         if let Err(e) = catch_unwind(AssertUnwindSafe(|| drop(e))) {
             // eprintln panics, hexchat_print_str doesn't.
-            hexchat_print_str(ph, "ERROR: panicked while trying to log panic!", false);
+            hexchat_print_str(
+                ph,
+                "ERROR: panicked while trying to log panic!",
+                false
+            );
             mem::forget(e);
             std::process::abort();
         }
@@ -679,10 +672,11 @@ unsafe fn log_panic(ph: *mut RawPh, e: Box<dyn std::any::Any + Send + 'static>)
 /// # Safety
 ///
 /// `ph` must be a valid pointer (see `std::ptr::read`).
-unsafe fn call_hook_protected<F: FnOnce() -> Eat + UnwindSafe>(
-    ph: *mut RawPh,
+unsafe fn call_hook_protected<'ph, F>(
+    ph: MutLtPtr<'ph, RawPh<'ph>>,
     f: F
-) -> Eat {
+) -> Eat
+where F: FnOnce() -> Eat + UnwindSafe{
     match catch_unwind(f) {
         Result::Ok(v @ _) => v,
         Result::Err(e @ _) => {
@@ -698,13 +692,28 @@ impl<'ph> PluginHandle<'ph> {
     /// # Safety
     ///
     /// `ph` must be a valid pointer (see `std::ptr::read`).
-    unsafe fn new(data: LtPhPtr<'ph>, info: PluginInfo, contexts: Contexts) -> PluginHandle<'ph> {
+    unsafe fn new(
+        data: MutLtPtr<'ph, RawPh<'ph>>,
+        info: PluginInfo,
+        contexts: Contexts
+    ) -> PluginHandle<'ph> {
         PluginHandle {
-            data, plugin: data.ph, info, contexts
+            data, guiplugin: ptr::null(), info, contexts
+        }
+    }
+
+    pub(crate) fn get_plugin(&self) -> MutLtPtr<'ph, RawPh<'ph>> {
+        if self.guiplugin.is_null() {
+            self.data
+        } else {
+            unsafe {
+                MutLtPtr::<'ph>::from_raw(self.guiplugin as *mut RawPh<'ph>)
+            }
         }
     }
 
-    /// Registers this hexchat plugin. This must be called exactly once when the plugin is loaded.
+    /// Registers this hexchat plugin. This must be called exactly once when
+    /// the plugin is loaded.
     ///
     /// # Panics
     ///
@@ -720,7 +729,7 @@ impl<'ph> PluginHandle<'ph> {
     /// }
     /// ```
     pub fn register(&mut self, name: &str, desc: &str, ver: &str) {
-        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't be registered");
+        assert!(self.guiplugin.is_null(), "PluginEntryHandle can't be registered");
         unsafe {
             let info = self.info;
             if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
@@ -742,7 +751,7 @@ impl<'ph> PluginHandle<'ph> {
     ///
     /// This function panics if this plugin is not registered.
     pub fn get_name(&self) -> &str {
-        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't be registered");
+        assert!(self.guiplugin.is_null(), "PluginEntryHandle can't be registered");
         unsafe {
             let info = self.info;
             if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
@@ -759,7 +768,7 @@ impl<'ph> PluginHandle<'ph> {
     ///
     /// This function panics if this plugin is not registered.
     pub fn get_description(&self) -> &str {
-        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't be registered");
+        assert!(self.guiplugin.is_null(), "PluginEntryHandle can't be registered");
         unsafe {
             let info = self.info;
             if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
@@ -776,7 +785,7 @@ impl<'ph> PluginHandle<'ph> {
     ///
     /// This function panics if this plugin is not registered.
     pub fn get_version(&self) -> &str {
-        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't be registered");
+        assert!(self.guiplugin.is_null(), "PluginEntryHandle can't be registered");
         unsafe {
             let info = self.info;
             if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
@@ -829,7 +838,7 @@ impl<'ph> PluginHandle<'ph> {
         let info = self.info;
         let contexts = Rc::clone(&self.contexts);
         let mut handle = unsafe { Self::new(data, info, contexts) };
-        handle.plugin = entry.entry as *mut RawPh;
+        handle.guiplugin = entry.entry;
         handle.set_context(&self.get_context());
         f(&mut handle)
     }
@@ -911,21 +920,22 @@ impl<'ph> PluginHandle<'ph> {
     /// }
     /// ```
     pub fn hook_command<'f, F>(&self, cmd: &str, pri: i32, help: Option<&str>, cb: F) -> HookHandle<'ph, 'f> where F: Fn(&mut PluginHandle<'ph>, Word, WordEol) -> Eat + 'f + RefUnwindSafe, 'f: 'ph {
-        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't have hooks");
+        assert!(self.guiplugin.is_null(), "PluginEntryHandle can't have hooks");
         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<HookUd> = {
-            let ph = self.data;
+            let ph = AssertUnwindSafe(self.data);
             let info = self.info;
             let contexts = Rc::clone(&self.contexts);
             Rc::new(Box::new(move |word, word_eol, _| {
                 let cb = &cb;
                 let contexts = Rc::clone(&contexts);
+                let ph = AssertUnwindSafe(*ph);
                 unsafe {
-                    call_hook_protected(ph.ph, move || {
-                        let mut ph = PluginHandle::new(ph, info, contexts);
+                    call_hook_protected(*ph, move || {
+                        let mut ph = PluginHandle::new(*ph, info, contexts);
                         let word = Word::new(word);
                         let word_eol = WordEol::new(word_eol);
                         cb(&mut ph, word, word_eol)
@@ -961,26 +971,27 @@ impl<'ph> PluginHandle<'ph> {
     /// }
     /// ```
     pub fn hook_server<'f, F>(&self, cmd: &str, pri: i32, cb: F) -> HookHandle<'ph, 'f> where F: Fn(&mut PluginHandle<'ph>, Word, WordEol) -> Eat + 'f + RefUnwindSafe, 'f: 'ph {
-        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't have hooks");
+        assert!(self.guiplugin.is_null(), "PluginEntryHandle can't have hooks");
         self.hook_server_attrs(cmd, pri, move |ph, w, we, _| cb(ph, w, we))
     }
     /// Sets a server hook, with attributes.
     pub fn hook_server_attrs<'f, F>(&self, cmd: &str, pri: i32, cb: F) -> HookHandle<'ph, 'f> where F: Fn(&mut PluginHandle<'ph>, Word, WordEol, EventAttrs) -> Eat + 'f + RefUnwindSafe, 'f: 'ph {
-        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't have hooks");
+        assert!(self.guiplugin.is_null(), "PluginEntryHandle can't have hooks");
         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<HookUd> = {
-            let ph = self.data;
+            let ph = AssertUnwindSafe(self.data);
             let info = self.info;
             let contexts = Rc::clone(&self.contexts);
             Rc::new(Box::new(move |word, word_eol, attrs| {
                 let cb = &cb;
                 let contexts = Rc::clone(&contexts);
+                let ph = AssertUnwindSafe(*ph);
                 unsafe {
-                    call_hook_protected(ph.ph, move || {
-                        let mut ph = PluginHandle::new(ph, info, contexts);
+                    call_hook_protected(*ph, move || {
+                        let mut ph = PluginHandle::new(*ph, info, contexts);
                         let word = Word::new(word);
                         let word_eol = WordEol::new(word_eol);
                         let attrs = (&*attrs).into();
@@ -1018,7 +1029,7 @@ impl<'ph> PluginHandle<'ph> {
     /// }
     /// ```
     pub fn hook_print<'f, F>(&self, name: &str, pri: i32, cb: F) -> HookHandle<'ph, 'f> where F: Fn(&mut PluginHandle<'ph>, Word) -> Eat + 'f + RefUnwindSafe, 'f: 'ph {
-        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't have hooks");
+        assert!(self.guiplugin.is_null(), "PluginEntryHandle can't have hooks");
         // 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. :/
@@ -1034,15 +1045,16 @@ impl<'ph> PluginHandle<'ph> {
         // we still do our best to be well-behaved.
         let suppress_eat = name.eq_ignore_ascii_case("Close Context");
         let b: Rc<HookUd> = {
-            let ph = self.data;
+            let ph = AssertUnwindSafe(self.data);
             let info = self.info;
             let contexts = Rc::clone(&self.contexts);
             Rc::new(Box::new(move |word, _, _| {
                 let cb = &cb;
                 let contexts = Rc::clone(&contexts);
+                let ph = AssertUnwindSafe(*ph);
                 unsafe {
-                    call_hook_protected(ph.ph, move || {
-                        let mut ph = PluginHandle::new(ph, info, contexts);
+                    call_hook_protected(*ph, move || {
+                        let mut ph = PluginHandle::new(*ph, info, contexts);
                         let word = Word::new(word);
                         match cb(&mut ph, word) {
                             _ if suppress_eat => EAT_NONE,
@@ -1081,21 +1093,22 @@ impl<'ph> PluginHandle<'ph> {
     /// }
     /// ```
     pub fn hook_print_attrs<'f, F>(&self, name: &str, pri: i32, cb: F) -> HookHandle<'ph, 'f> where F: Fn(&mut PluginHandle<'ph>, Word, EventAttrs) -> Eat + 'f + RefUnwindSafe, 'f: 'ph {
-        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't have hooks");
+        assert!(self.guiplugin.is_null(), "PluginEntryHandle can't have hooks");
         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<HookUd> = {
-            let ph = self.data;
+            let ph = AssertUnwindSafe(self.data);
             let info = self.info;
             let contexts = Rc::clone(&self.contexts);
             Rc::new(Box::new(move |word, _, attrs| {
                 let cb = &cb;
                 let contexts = Rc::clone(&contexts);
+                let ph = AssertUnwindSafe(*ph);
                 unsafe {
-                    call_hook_protected(ph.ph, move || {
-                        let mut ph = PluginHandle::new(ph, info, contexts);
+                    call_hook_protected(*ph, move || {
+                        let mut ph = PluginHandle::new(*ph, info, contexts);
                         let word = Word::new(word);
                         let attrs = (&*attrs).into();
                         cb(&mut ph, word, attrs)
@@ -1128,7 +1141,7 @@ impl<'ph> PluginHandle<'ph> {
     /// }
     /// ```
     pub fn hook_timer<'f, F>(&self, timeout: i32, cb: F) -> HookHandle<'ph, 'f> where F: Fn(&mut PluginHandle<'ph>) -> bool + 'f + RefUnwindSafe, 'f: 'ph {
-        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't have hooks");
+        assert!(self.guiplugin.is_null(), "PluginEntryHandle can't have hooks");
         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
@@ -1137,7 +1150,7 @@ impl<'ph> PluginHandle<'ph> {
         // helps us clean up the thing when returning false
         let dropper = Rc::new(Cell::new(None));
         let b: Rc<HookUd> = {
-            let ph = self.data;
+            let ph = AssertUnwindSafe(self.data);
             let info = self.info;
             let contexts = Rc::clone(&self.contexts);
             let freed = AssertUnwindSafe(Rc::clone(&freed));
@@ -1146,8 +1159,9 @@ impl<'ph> PluginHandle<'ph> {
                 let cb = &cb;
                 let contexts = Rc::clone(&contexts);
                 let res = unsafe {
-                    call_hook_protected(ph.ph, move || {
-                        let mut ph = PluginHandle::new(ph, info, contexts);
+                    let ph = AssertUnwindSafe(*ph);
+                    call_hook_protected(*ph, move || {
+                        let mut ph = PluginHandle::new(*ph, info, contexts);
                         if cb(&mut ph) {
                             EAT_HEXCHAT
                         } else {
@@ -1163,7 +1177,7 @@ impl<'ph> PluginHandle<'ph> {
                         // 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.ph, || {
+                        call_hook_protected(*ph, || {
                             drop(Rc::from_raw(dropper.take().unwrap()));
                             EAT_NONE
                         });
@@ -1187,7 +1201,7 @@ impl<'ph> PluginHandle<'ph> {
     pub fn print<T: ToString>(&self, s: T) {
         let s = s.to_string();
         unsafe {
-            hexchat_print_str(self.data.ph, &*s, true);
+            hexchat_print_str(self.data, &*s, true);
         }
     }
 
@@ -1214,7 +1228,7 @@ impl<'ph> PluginHandle<'ph> {
             // "fast" path. hexchat_print_str still has to allocate, and
             // hexchat is slow af.
             unsafe {
-                hexchat_print_str(self.data.ph, s, true);
+                hexchat_print_str(self.data, s, true);
             }
         } else {
             self.print(fmt);
@@ -2091,11 +2105,11 @@ unsafe fn wrap_context<'ph>(ph: &PluginHandle<'ph>, ctx: *const internals::Hexch
 /// # Panics
 ///
 /// Panics if panic_on_nul is true and the string contains embedded nuls.
-unsafe fn hexchat_print_str(ph: *mut RawPh, s: &str, panic_on_nul: bool) {
+unsafe fn hexchat_print_str<'ph>(ph: MutLtPtr<'ph, RawPh<'ph>>, s: &str, panic_on_nul: bool) {
     match CString::new(s) {
         Result::Ok(cs @ _) => {
             let csr: &CStr = &cs;
-            ((*ph).hexchat_print)(ph, csr.as_ptr())
+            ((*ph.as_raw()).hexchat_print)(ph, csr.as_ptr())
         },
         e @ _ => if panic_on_nul {e.unwrap();}, // TODO nul_position?
     }
@@ -2163,8 +2177,8 @@ struct PhUserdata<'ph> {
 /// 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>(ph: LtPhPtr<'ph>, ud: Box<PhUserdata<'ph>>) {
-    (*ph.ph).userdata = Box::into_raw(ud) as *mut c_void;
+unsafe fn put_userdata<'ph>(ph: MutLtPtr<'ph, RawPh<'ph>>, ud: Box<PhUserdata<'ph>>) {
+    (*ph.as_raw()).userdata = Box::into_raw(ud);
 }
 
 /// Pops the userdata from the plugin handle.
@@ -2172,8 +2186,8 @@ unsafe fn put_userdata<'ph>(ph: LtPhPtr<'ph>, ud: Box<PhUserdata<'ph>>) {
 /// # Safety
 ///
 /// This function is unsafe because it doesn't check if the pointer is valid.
-unsafe fn pop_userdata<'ph>(ph: LtPhPtr<'ph>) -> Box<PhUserdata<'ph>> {
-    Box::from_raw(mem::replace(&mut (*ph.ph).userdata, ptr::null_mut()) as *mut PhUserdata<'ph>)
+unsafe fn pop_userdata<'ph>(ph: MutLtPtr<'ph, RawPh<'ph>>) -> Box<PhUserdata<'ph>> {
+    Box::from_raw(mem::replace(&mut (*ph.as_raw()).userdata, ptr::null_mut()))
 }
 
 fn test_pluginpref_var(var: &[u8]) -> Result<(), PluginPrefError> {
@@ -2202,22 +2216,21 @@ fn check_pluginpref_var(var: impl Into<Vec<u8>>) -> Result<CString, PluginPrefEr
 // *********************** //
 
 #[doc(hidden)]
-pub unsafe fn hexchat_plugin_init<'ph, T>(plugin_handle: LtPhPtr<'ph>,
+pub unsafe fn hexchat_plugin_init<'ph, T>(ph: MutLtPtr<'ph, RawPh<'ph>>,
                                      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<'ph> + Default + 'ph {
-    if plugin_handle.ph.is_null() || plugin_name.is_null() || plugin_desc.is_null() || plugin_version.is_null() {
+    if ph.as_raw().is_null() || plugin_name.is_null() || plugin_desc.is_null() || plugin_version.is_null() {
         // we can't really do anything here. just hope this doesn't panic.
         eprintln!("hexchat_plugin_init called with a null pointer that shouldn't be null - broken hexchat");
         std::process::abort();
     }
-    let ph = plugin_handle.ph 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!
-    (*ph).userdata = ptr::null_mut();
+    (*ph.as_raw()).userdata = ptr::null_mut();
     // read the filename so we can pass it on later.
     let filename = if !(*plugin_name).is_null() {
         if let Ok(fname) = CStr::from_ptr(*plugin_name).to_owned().into_string() {
@@ -2239,7 +2252,7 @@ pub unsafe fn hexchat_plugin_init<'ph, T>(plugin_handle: LtPhPtr<'ph>,
     // NOTE: calling hexchat functions with null plugin_name, plugin_desc, plugin_version is a bit
     // dangerous. this particular case is "ok".
     {
-        let ver = ((*ph).hexchat_get_info)(ph, cstr(b"version\0")); // this shouldn't panic
+        let ver = ((*ph.as_raw()).hexchat_get_info)(ph, cstr(b"version\0")); // this shouldn't panic
         let cstr = CStr::from_ptr(ver);
         if let Ok(ver) = cstr.to_str() {
             let mut iter = ver.split('.');
@@ -2262,10 +2275,11 @@ pub unsafe fn hexchat_plugin_init<'ph, T>(plugin_handle: LtPhPtr<'ph>,
         return 0;
     };
     let r: thread::Result<Option<Box<_>>> = {
+        let ausph = AssertUnwindSafe(ph);
         catch_unwind(move || {
             // AssertUnwindSafe not Default at the time of writing this
             let contexts = Rc::new(AssertUnwindSafe(Default::default()));
-            let mut pluginhandle = PluginHandle::new(plugin_handle, pluginfo, contexts);
+            let mut pluginhandle = PluginHandle::new(*ausph, pluginfo, contexts);
             let contexts = Rc::clone(&pluginhandle.contexts);
             // must register this before the plugin registers anything else!
             let context_hook = pluginhandle.hook_print("Close Context", c_int::min_value(), move |ph, _| {
@@ -2293,7 +2307,7 @@ pub unsafe fn hexchat_plugin_init<'ph, T>(plugin_handle: LtPhPtr<'ph>,
     };
     match r {
         Result::Ok(Option::Some(plug @ _)) => {
-            put_userdata(plugin_handle, plug);
+            put_userdata(ph, plug);
             1
         },
         r @ _ => {
@@ -2306,24 +2320,24 @@ pub unsafe fn hexchat_plugin_init<'ph, T>(plugin_handle: LtPhPtr<'ph>,
 }
 
 #[doc(hidden)]
-pub unsafe fn hexchat_plugin_deinit<'ph, T>(plugin_handle: LtPhPtr<'ph>) -> c_int where T: Plugin<'ph> {
+pub unsafe extern "C" fn hexchat_plugin_deinit<'ph, T>(ph: MutLtPtr<'ph, RawPh<'ph>>) -> c_int where T: Plugin<'ph> {
     let mut safe_to_unload = 1;
     // plugin_handle should never be null, but just in case.
-    if !plugin_handle.ph.is_null() {
-        let ph = plugin_handle.ph as *mut RawPh;
+    if !ph.as_raw().is_null() {
         // userdata should also never be null.
-        if !(*ph).userdata.is_null() {
+        if !(*ph.as_raw()).userdata.is_null() {
             {
                 let mut info: Option<PluginInfo> = None;
                 {
+                    let ausph = AssertUnwindSafe(ph);
                     let mut ausinfo = AssertUnwindSafe(&mut info);
                     safe_to_unload = match catch_unwind(move || {
-                        let mut userdata = pop_userdata(plugin_handle);
+                        let mut userdata = pop_userdata(*ausph);
                         let pluginfo = userdata.pluginfo;
                         if let Err(e) = catch_unwind(AssertUnwindSafe(|| {
                             userdata.plug.as_mut().deinit(&mut {
                                 PluginHandle::new(
-                                    plugin_handle,
+                                    *ausph,
                                     pluginfo,
                                     Rc::clone(&userdata.contexts)
                                 )
@@ -2332,7 +2346,7 @@ pub unsafe fn hexchat_plugin_deinit<'ph, T>(plugin_handle: LtPhPtr<'ph>) -> c_in
                             // panics in deinit may be retried.
                             // however, one may need to close hexchat if that
                             // happens.
-                            put_userdata(plugin_handle, userdata);
+                            put_userdata(*ausph, userdata);
                             std::panic::resume_unwind(e);
                         }
                         **ausinfo = Some(pluginfo);
@@ -2366,7 +2380,7 @@ pub unsafe fn hexchat_plugin_deinit<'ph, T>(plugin_handle: LtPhPtr<'ph>) -> c_in
 macro_rules! hexchat_plugin {
     ($l:lifetime, $t:ty) => {
         #[no_mangle]
-        pub unsafe extern "C" fn hexchat_plugin_init<$l>(plugin_handle: $crate::LtPhPtr<$l>,
+        pub unsafe extern "C" fn hexchat_plugin_init<$l>(plugin_handle: $crate::MutLtPtr<$l, $crate::RawPh<$l>>,
                                               plugin_name: *mut *const $crate::c_char,
                                               plugin_desc: *mut *const $crate::c_char,
                                               plugin_version: *mut *const $crate::c_char,
@@ -2374,7 +2388,7 @@ macro_rules! hexchat_plugin {
             $crate::hexchat_plugin_init::<$l, $t>(plugin_handle, plugin_name, plugin_desc, plugin_version, arg)
         }
         #[no_mangle]
-        pub unsafe extern "C" fn hexchat_plugin_deinit<$l>(plugin_handle: $crate::LtPhPtr<$l>) -> $crate::c_int {
+        pub unsafe extern "C" fn hexchat_plugin_deinit<$l>(plugin_handle: $crate::MutLtPtr<$l, $crate::RawPh<$l>>) -> $crate::c_int {
             $crate::hexchat_plugin_deinit::<$l, $t>(plugin_handle)
         }
         // unlike what the documentation states, there's no need to define hexchat_plugin_get_info.
diff --git a/src/mock/mod.rs b/src/mock/mod.rs
index 92de66c..0e4ba03 100644
--- a/src/mock/mod.rs
+++ b/src/mock/mod.rs
@@ -38,8 +38,11 @@
 //!
 //! This API is not stable. Do not rely on this API to be stable.
 
+use crate::internals::Ph;
+
 use std::cell::Cell;
 use std::cell::RefCell;
+use std::cell::UnsafeCell;
 use std::ffi::CStr;
 use std::ffi::CString;
 use std::marker::PhantomData;
@@ -48,24 +51,40 @@ use std::pin::Pin;
 use std::ptr;
 use std::rc::Rc;
 
-use libc::c_char;
+use ::libc::{c_char, c_int};
 
 use ::ltptr::*;
 
-use ::selfref::{Holder, Opaque, SelfRef, opaque};
+use ::selfref::{Holder, NewWith, OperateIn, opaque};
+
+struct Context {
+}
 
-// MUST be repr(C)
+// MUST be repr(C), despite having repr(Rust) types inside it.
+/// A loaded plugin/complete plugin handle, including private fields.
 #[repr(C)]
-struct MockPlugin<'dangling, 'env> {
+struct MockPlugin<'ph, 'env: 'ph> {
     // NOTE: MUST be first thing in the struct.
-    methods: crate::internals::Ph,
-    filename: *const c_char,
-    plugin_name: Cell<*const c_char>,
-    plugin_desc: Cell<*const c_char>,
-    plugin_vers: Cell<*const c_char>,
+    // we actually pass the whole MockPlugin to the plugin, but we pretend it's
+    // just the Ph.
+    methods: Ph<'ph>,
+    filename: CString,
+    plugin_name: *const c_char,
+    plugin_desc: *const c_char,
+    plugin_vers: *const c_char,
     free_strings: bool,
-    env: &'env PluginEnvironment<'dangling, 'env>,
-    _pin: PhantomPinned,
+    env: Pin<&'env PluginEnvironment<'env>>,
+    deinit: Option<unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>) -> c_int>,
+    context: Option<Rc<Context>>,
+}
+
+struct MockPluginK<'env> {
+    _p: PhantomData<&'env PluginEnvironment<'env>>,
+}
+opaque! {
+    impl['env] Opaque for MockPluginK<'env> {
+        type Kind<'ph> = UnsafeCell<MockPlugin<'ph, 'env>>;
+    }
 }
 
 enum MockCallback {
@@ -96,9 +115,9 @@ enum MockCallback {
     HookDeleted,
 }
 
-struct MockHook<'dangling, 'env> {
-    //pl: Rc<Holder<'dangling, MockPluginK<'dangling, 'env>>>,
-    pl: Rc<MockPlugin<'dangling, 'env>>,
+struct MockHook<'env> {
+    pl: Rc<Holder<'env, MockPluginK<'env>>>,
+    //pl: Rc<MockPlugin<'env>>,
     cb: RefCell<MockCallback>,
     name: Option<Box<str>>,
     pri_or_fd: libc::c_int,
@@ -106,32 +125,39 @@ struct MockHook<'dangling, 'env> {
     _pin: PhantomPinned,
 }
 
-pub struct PluginEnvironment<'dangling, 'env> {
-    //plugins: RefCell<Vec<Pin<Rc<Holder<'dangling, MockPluginK<'dangling, 'env>>>>>>,
-    plugins: RefCell<Vec<Pin<Rc<MockPlugin<'dangling, 'env>>>>>,
-    // priority-based hooks
-    //hooks: RefCell<Vec<Pin<Rc<Holder<'dangling, MockHookK<'dangling, 'env>>>>>>,
-    hooks: RefCell<Vec<Pin<Rc<MockHook<'dangling, 'env>>>>>,
-    // fd/timer hooks
-    //extra_hooks: RefCell<Vec<Pin<Rc<Holder<'dangling, MockHookK<'dangling, 'env>>>>>>,
-    extra_hooks: RefCell<Vec<Pin<Rc<MockHook<'dangling, 'env>>>>>,
-    depth: Cell<usize>,
+struct MockHookK<'env> {
+    _p: PhantomData<&'env PluginEnvironment<'env>>,
+}
+opaque! {
+    impl['env] Opaque for MockHookK<'env> {
+        type Kind<'ph> = MockHook<'env>;
+    }
 }
-impl<'dangling, 'env> SelfRef<'env> for PluginEnvironment<'dangling, 'env> {}
 
-pub struct PluginEnvironmentK<'dangling> {
-    _p: PhantomData<&'dangling ()>,
+/// A plugin environment. An emulated hexchat instance.
+// TODO: maybe make this a full plugin host, with <'app>?
+pub struct PluginEnvironment<'env> {
+    /// The loaded plugins.
+    plugins: RefCell<Vec<Pin<Rc<Holder<'env, MockPluginK<'env>>>>>>,
+    /// Priority-based hooks.
+    hooks: RefCell<Vec<Pin<Rc<Holder<'env, MockHookK<'env>>>>>>,
+    /// Timer and fd hooks.
+    extra_hooks: RefCell<Vec<Pin<Rc<Holder<'env, MockHookK<'env>>>>>>,
+    /// Hook recursion depth.
+    depth: Cell<usize>,
+    /// Contexts.
+    contexts: RefCell<Vec<Rc<Context>>>,
 }
+
+pub struct PluginEnvironmentK;
 opaque! {
-    impl['dangling] Opaque for PluginEnvironmentK<'dangling> {
-        type Kind<'env> = PluginEnvironment<'dangling, 'env>;
+    impl Opaque for PluginEnvironmentK {
+        type Kind<'env> = PluginEnvironment<'env>;
     }
 }
 
-pub type GlobalPluginEnvironment<'dangling> = Holder<'dangling, PluginEnvironmentK<'dangling>>;
-
-unsafe extern "C" fn hexchat_hook_command(
-    ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_hook_command<'ph>(
+    ph: MutLtPtr<'ph, Ph<'ph>>,
     name: *const libc::c_char,
     pri: libc::c_int,
     /* CALLBACK */
@@ -139,10 +165,10 @@ unsafe extern "C" fn hexchat_hook_command(
     help_text: *const libc::c_char,
     userdata: *mut libc::c_void
 ) -> *const crate::internals::HexchatHook {
-    let ph = ph as *mut MockPlugin;
+    let ph = MutLtPtr::from_raw(ph.as_raw() as *mut MockPlugin<'ph, '_>);
     todo!();
 }
-unsafe extern "C" fn hexchat_hook_server(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_hook_server<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         name: *const libc::c_char,
         pri: libc::c_int,
         /* CALLBACK */
@@ -150,7 +176,7 @@ unsafe extern "C" fn hexchat_hook_server(ph: *mut crate::internals::HexchatPlugi
         userdata: *mut libc::c_void) -> *const crate::internals::HexchatHook {
     todo!();
 }
-unsafe extern "C" fn hexchat_hook_print(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_hook_print<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         name: *const libc::c_char,
         pri: libc::c_int,
         /* CALLBACK */
@@ -158,14 +184,14 @@ unsafe extern "C" fn hexchat_hook_print(ph: *mut crate::internals::HexchatPlugin
         userdata: *mut libc::c_void) -> *const crate::internals::HexchatHook {
     todo!();
 }
-unsafe extern "C" fn hexchat_hook_timer(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_hook_timer<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         timeout: libc::c_int,
         /* CALLBACK */
         callback: unsafe extern "C" fn(user_data: *mut libc::c_void) -> libc::c_int,
         userdata: *mut libc::c_void) -> *const crate::internals::HexchatHook {
     todo!();
 }
-unsafe extern "C" fn hexchat_hook_fd(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_hook_fd<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         fd: libc::c_int,
         flags: libc::c_int,
         /* CALLBACK */
@@ -173,80 +199,102 @@ unsafe extern "C" fn hexchat_hook_fd(ph: *mut crate::internals::HexchatPlugin,
         userdata: *mut libc::c_void) -> *const crate::internals::HexchatHook {
     todo!();
 }
-unsafe extern "C" fn hexchat_unhook(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_unhook<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         hook: *const crate::internals::HexchatHook) -> *const libc::c_void {
     todo!();
 }
-unsafe extern "C" fn hexchat_print(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_print<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         text: *const libc::c_char) {
     todo!();
 }
-pub unsafe extern "C" fn hexchat_printf(ph: *mut crate::internals::HexchatPlugin,
+pub unsafe extern "C" fn hexchat_printf<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         format: *const libc::c_char, ...) {
     unimplemented!();
 }
-unsafe extern "C" fn hexchat_command(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_command<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         command: *const libc::c_char) {
     todo!();
 }
-unsafe extern "C" fn hexchat_commandf(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_commandf<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         format: *const libc::c_char, ...) {
     unimplemented!();
 }
-unsafe extern "C" fn hexchat_nickcmp(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_nickcmp<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         s1: *const libc::c_char,
         s2: *const libc::c_char) -> libc::c_int {
     todo!();
 }
-unsafe extern "C" fn hexchat_set_context(ph: *mut crate::internals::HexchatPlugin,
-        ctx: *const crate::internals::HexchatContext) -> libc::c_int {
-    todo!();
+unsafe extern "C" fn hexchat_set_context<'ph>(
+    ph: MutLtPtr<'ph, Ph<'ph>>,
+    ctx: *const crate::internals::HexchatContext,
+) -> libc::c_int {
+    let ctx = ctx as *const Context;
+    let ph: *mut MockPlugin<'_, '_> = ph.as_raw() as *mut _;
+    if ctx.is_null() { return 0; }
+    // in C, ctx must NOT be dangling, so we let miri catch it
+    let _ = &*ctx;
+    // in addition to that we also panic if ctx isn't on the list,
+    // because you're supposed to use "Close Context" for cleanup.
+    // if your plugin makes it this far, you're literally passing in garbage.
+    let env: Pin<&PluginEnvironment<'_>> = (*ph).env;
+    if env.contexts.borrow().iter().any(|e| ptr::eq(&**e, ctx)) {
+        (*ph).context = Some(crate::rc_clone_from_raw(ctx));
+        return 1;
+    } else {
+        panic!("garbage in hexchat_set_context - broken plugin")
+    }
 }
-unsafe extern "C" fn hexchat_find_context(ph: *mut crate::internals::HexchatPlugin,
-        servname: *const libc::c_char,
-        channel: *const libc::c_char) -> *const crate::internals::HexchatContext {
+unsafe extern "C" fn hexchat_find_context<'ph>(
+    ph: MutLtPtr<'ph, Ph<'ph>>,
+    servname: *const libc::c_char,
+    channel: *const libc::c_char,
+) -> *const crate::internals::HexchatContext {
     todo!();
 }
-unsafe extern "C" fn hexchat_get_context(ph: *mut crate::internals::HexchatPlugin) -> *const crate::internals::HexchatContext {
-    todo!();
+unsafe extern "C" fn hexchat_get_context<'ph>(
+    ph: MutLtPtr<'ph, Ph<'ph>>,
+) -> *const crate::internals::HexchatContext {
+    let ph: *mut MockPlugin<'_, '_> = ph.as_raw() as *mut _;
+    let ctx = (*ph).context.as_ref().map(|rc| Rc::as_ptr(rc));
+    ctx.unwrap_or(ptr::null()) as *const _
 }
-unsafe extern "C" fn hexchat_get_info(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_get_info<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         id: *const libc::c_char) -> *const libc::c_char {
     todo!();
 }
-unsafe extern "C" fn hexchat_get_prefs(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_get_prefs<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         name: *const libc::c_char,
         string: *mut *const libc::c_char,
         integer: *mut libc::c_int) -> libc::c_int {
     todo!();
 }
-unsafe extern "C" fn hexchat_list_get(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_list_get<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         name: *const libc::c_char) -> *mut crate::internals::HexchatList {
     todo!();
 }
-unsafe extern "C" fn hexchat_list_free(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_list_free<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         xlist: *const crate::internals::HexchatList) {
     todo!();
 }
-unsafe extern "C" fn hexchat_list_fields(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_list_fields<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         name: *const libc::c_char) -> *const *const libc::c_char {
     todo!();
 }
-unsafe extern "C" fn hexchat_list_next(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_list_next<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         xlist: *const crate::internals::HexchatList) -> libc::c_int {
     todo!();
 }
-unsafe extern "C" fn hexchat_list_str(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_list_str<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         xlist: *const crate::internals::HexchatList,
         name: *const libc::c_char) -> *const libc::c_char {
     todo!();
 }
-unsafe extern "C" fn hexchat_list_int(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_list_int<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         xlist: *const crate::internals::HexchatList,
         name: *const libc::c_char) -> libc::c_int {
     todo!();
 }
-unsafe extern "C" fn hexchat_plugingui_add(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_plugingui_add<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         filename: *const libc::c_char,
         name: *const libc::c_char,
         desc: *const libc::c_char,
@@ -254,30 +302,30 @@ unsafe extern "C" fn hexchat_plugingui_add(ph: *mut crate::internals::HexchatPlu
         reserved: *mut char) -> *const crate::internals::PluginGuiHandle {
     todo!();
 }
-unsafe extern "C" fn hexchat_plugingui_remove(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_plugingui_remove<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         handle: *const crate::internals::PluginGuiHandle) {
     todo!();
 }
-unsafe extern "C" fn hexchat_emit_print(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_emit_print<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         event_name: *const libc::c_char, ...) -> libc::c_int {
     todo!();
 }
-unsafe extern "C" fn hexchat_read_fd(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_read_fd<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         src: *const libc::c_void,
         buf: *mut char,
         len: *mut libc::c_int) -> libc::c_int {
     todo!();
 }
-unsafe extern "C" fn hexchat_list_time(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_list_time<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         xlist: *const crate::internals::HexchatList,
         name: *const libc::c_char) -> libc::time_t {
     todo!();
 }
-unsafe extern "C" fn hexchat_gettext(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_gettext<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         msgid: *const libc::c_char) -> *const libc::c_char {
     todo!();
 }
-unsafe extern "C" fn hexchat_send_modes(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_send_modes<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         targets: *mut *const libc::c_char,
         ntargets: libc::c_int,
         modes_per_line: libc::c_int,
@@ -285,44 +333,44 @@ unsafe extern "C" fn hexchat_send_modes(ph: *mut crate::internals::HexchatPlugin
         mode: libc::c_char) {
     todo!();
 }
-unsafe extern "C" fn hexchat_strip(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_strip<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         string: *const libc::c_char,
         len: libc::c_int,
         flags: libc::c_int) -> *const libc::c_char {
     todo!();
 }
-unsafe extern "C" fn hexchat_free(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_free<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         ptr: *const libc::c_void) {
     todo!();
 }
-unsafe extern "C" fn hexchat_pluginpref_set_str(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_pluginpref_set_str<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         var: *const libc::c_char,
         value: *const libc::c_char) -> libc::c_int {
     todo!();
 }
-unsafe extern "C" fn hexchat_pluginpref_get_str(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_pluginpref_get_str<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         var: *const libc::c_char,
         dest: *mut char) -> libc::c_int {
     todo!();
 }
-unsafe extern "C" fn hexchat_pluginpref_set_int(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_pluginpref_set_int<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         var: *const libc::c_char,
         value: libc::c_int) -> libc::c_int {
     todo!();
 }
-unsafe extern "C" fn hexchat_pluginpref_get_int(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_pluginpref_get_int<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         var: *const libc::c_char) -> libc::c_int {
     todo!();
 }
-unsafe extern "C" fn hexchat_pluginpref_delete(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_pluginpref_delete<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         var: *const libc::c_char) -> libc::c_int {
     todo!();
 }
-unsafe extern "C" fn hexchat_pluginpref_list(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_pluginpref_list<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         dest: *mut char) -> libc::c_int {
     todo!();
 }
-unsafe extern "C" fn hexchat_hook_server_attrs(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_hook_server_attrs<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         name: *const libc::c_char,
         pri: libc::c_int,
         /* CALLBACK */
@@ -330,7 +378,7 @@ unsafe extern "C" fn hexchat_hook_server_attrs(ph: *mut crate::internals::Hexcha
         userdata: *mut libc::c_void) -> *const crate::internals::HexchatHook {
     todo!();
 }
-unsafe extern "C" fn hexchat_hook_print_attrs(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_hook_print_attrs<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         name: *const libc::c_char,
         pri: libc::c_int,
         /* CALLBACK */
@@ -338,106 +386,189 @@ unsafe extern "C" fn hexchat_hook_print_attrs(ph: *mut crate::internals::Hexchat
         userdata: *mut libc::c_void) -> *const crate::internals::HexchatHook {
     todo!();
 }
-unsafe extern "C" fn hexchat_emit_print_attrs(ph: *mut crate::internals::HexchatPlugin, attrs: *const crate::internals::HexchatEventAttrs,
+unsafe extern "C" fn hexchat_emit_print_attrs<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, attrs: *const crate::internals::HexchatEventAttrs,
         event_name: *const libc::c_char, ...) -> libc::c_int {
     todo!();
 }
-unsafe extern "C" fn hexchat_event_attrs_create(ph: *mut crate::internals::HexchatPlugin) -> *mut crate::internals::HexchatEventAttrs {
+unsafe extern "C" fn hexchat_event_attrs_create<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>) -> *mut crate::internals::HexchatEventAttrs {
     todo!();
 }
-unsafe extern "C" fn hexchat_event_attrs_free(ph: *mut crate::internals::HexchatPlugin,
+unsafe extern "C" fn hexchat_event_attrs_free<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
         attrs: *mut crate::internals::HexchatEventAttrs) {
     todo!();
 }
 
-impl<'dangling, 'env> PluginEnvironment<'dangling, 'env> {
-    pub fn new() -> Self {
-        Self {
-            plugins: Default::default(),
-            hooks: Default::default(),
-            extra_hooks: Default::default(),
-            depth: Default::default(),
+impl<'env> PluginEnvironment<'env> {
+    /// Creates a new plugin environment.
+    pub fn new() -> Holder<'static, PluginEnvironmentK> {
+        struct NewEnv;
+        impl<'k> NewWith<'k, PluginEnvironmentK> for NewEnv {
+            fn new_with<'env>(self) -> PluginEnvironment<'env> {
+                PluginEnvironment {
+                    plugins: Default::default(),
+                    hooks: Default::default(),
+                    extra_hooks: Default::default(),
+                    depth: Default::default(),
+                    contexts: Default::default(),
+                }
+            }
+        }
+        Holder::new_with(NewEnv)
+    }
+
+    /// Loads a plugin.
+    pub fn load_plugin<T: PluginFactory>(
+        self: Pin<&'env Self>,
+        filename: CString,
+        arg: Option<&CStr>,
+    ) -> Result<(), ()> {
+        let plug = MockPlugin::new(self, filename);
+        // add the plugin
+        self.plugins.borrow_mut().push(plug.clone());
+
+        struct MockPluginInit<'arg, T> {
+            arg: Option<&'arg CStr>,
+            _p: PhantomData<T>
+        }
+        impl<'k, 'env: 'k, 'arg, T> OperateIn<'k, MockPluginK<'env>>
+        for MockPluginInit<'arg, T>
+        where T: PluginFactory {
+            type Out = bool;
+            fn operate_in<'ph>(
+                self,
+                ph: Pin<&'ph UnsafeCell<MockPlugin<'ph, 'env>>>,
+            ) -> bool where 'k: 'ph {
+                let ptr = ph.get();
+                let arg = self.arg;
+                unsafe {
+                    // setup deinit function
+                    (*ptr).deinit = Some(
+                        crate::hexchat_plugin_deinit::<'ph, T::Plugin<'ph>>
+                    );
+                    crate::hexchat_plugin_init::<'ph, T::Plugin<'ph>>(
+                        MutLtPtr::from_raw(ptr as *mut _),
+                        ptr::addr_of_mut!((*ptr).plugin_name),
+                        ptr::addr_of_mut!((*ptr).plugin_desc),
+                        ptr::addr_of_mut!((*ptr).plugin_vers),
+                        arg.map(|arg| arg.as_ptr()).unwrap_or(ptr::null()),
+                    ) != 0
+                }
+            }
+        }
+        if plug.as_ref().operate_in(MockPluginInit::<T> {
+            arg,
+            _p: PhantomData,
+        }) {
+            Ok(())
+        } else {
+            // remove the plugin - aka retain every plugin that isn't this one
+            self.plugins.borrow_mut().retain(|e| {
+                !ptr::eq::<Holder<_>>(&**e, &*plug)
+            });
+            Err(())
         }
     }
 }
 
-//
-//impl MockPlugin {
-//    pub fn new<T: for<'ph> crate::Plugin<'ph> + Default>(
-//        env: Pin<&PluginEnvironment>,
-//        filename: CString,
-//        arg: Option<&CStr>,
-//    ) -> Option<Pin<Box<MockPlugin>>> {
-//        let filename = filename.into_raw();
-//        let mut plug = Box::pin(MockPlugin {
-//            methods: crate::internals::Ph {
-//                hexchat_hook_command,
-//                hexchat_hook_server,
-//                hexchat_hook_print,
-//                hexchat_hook_timer,
-//                hexchat_hook_fd,
-//                hexchat_unhook,
-//                hexchat_print,
-//                userdata: hexchat_printf as *mut libc::c_void,
-//                hexchat_command,
-//                hexchat_commandf_do_not_use: hexchat_commandf,
-//                hexchat_nickcmp,
-//                hexchat_set_context,
-//                hexchat_find_context,
-//                hexchat_get_context,
-//                hexchat_get_info,
-//                hexchat_get_prefs,
-//                hexchat_list_get,
-//                hexchat_list_free,
-//                hexchat_list_fields,
-//                hexchat_list_next,
-//                hexchat_list_str,
-//                hexchat_list_int,
-//                hexchat_plugingui_add,
-//                hexchat_plugingui_remove,
-//                hexchat_emit_print,
-//                hexchat_read_fd,
-//                hexchat_list_time,
-//                hexchat_gettext,
-//                hexchat_send_modes,
-//                hexchat_strip,
-//                hexchat_free,
-//                hexchat_pluginpref_set_str,
-//                hexchat_pluginpref_get_str,
-//                hexchat_pluginpref_set_int,
-//                hexchat_pluginpref_get_int,
-//                hexchat_pluginpref_delete,
-//                hexchat_pluginpref_list,
-//                hexchat_hook_server_attrs,
-//                hexchat_hook_print_attrs,
-//                hexchat_emit_print_attrs,
-//                hexchat_event_attrs_create,
-//                hexchat_event_attrs_free,
-//            },
-//            filename: filename,
-//            plugin_name: filename,
-//            plugin_desc: ptr::null(),
-//            plugin_vers: ptr::null(),
-//            free_strings: false,
-//            env: &*env,
-//            _pin: PhantomPinned,
-//        });
-//        if unsafe {
-//            let ptr: *mut MockPlugin = &mut *plug;
-//            crate::hexchat_plugin_init::<'_, T>(
-//                crate::LtPhPtr {
-//                    ph: ptr as *mut crate::internals::Ph,
-//                    _lt: PhantomData,
-//                },
-//                ptr::addr_of_mut!((*ptr).plugin_name),
-//                ptr::addr_of_mut!((*ptr).plugin_desc),
-//                ptr::addr_of_mut!((*ptr).plugin_vers),
-//                arg.map(|arg| arg.as_ptr()).unwrap_or(ptr::null()),
-//            ) != 0
-//        } {
-//            Some(plug)
-//        } else {
-//            None
-//        }
-//    }
-//}
+/// Helper for making [`MockPlugin`]s from [`crate::Plugin`]s.
+pub trait PluginFactory: 'static {
+    /// The plugin type.
+    type Plugin<'a>: crate::Plugin<'a> + Default;
+}
+
+impl<'ph, 'env> MockPlugin<'ph, 'env> {
+    /// Creates a MockPlugin, but does not load it (yet).
+    fn new(
+        env: Pin<&'env PluginEnvironment<'env>>,
+        filename: CString,
+    ) -> Pin<Rc<Holder<'env, MockPluginK<'env>>>> {
+        struct MockPluginNewWith<'env> {
+            filename: CString,
+            env: Pin<&'env PluginEnvironment<'env>>
+        }
+        impl<'k, 'env> NewWith<'k, MockPluginK<'env>>
+        for MockPluginNewWith<'env>
+        where 'env: 'k {
+            fn new_with<'ph>(self) -> UnsafeCell<MockPlugin<'ph, 'env>>
+            where 'k: 'ph {
+                let filename = self.filename;
+                let env = self.env;
+                UnsafeCell::new(MockPlugin {
+                    methods: Ph {
+                        hexchat_hook_command,
+                        hexchat_hook_server,
+                        hexchat_hook_print,
+                        hexchat_hook_timer,
+                        hexchat_hook_fd,
+                        hexchat_unhook,
+                        hexchat_print,
+                        userdata: hexchat_printf as *mut _,
+                        hexchat_command,
+                        hexchat_commandf_do_not_use: hexchat_commandf,
+                        hexchat_nickcmp,
+                        hexchat_set_context,
+                        hexchat_find_context,
+                        hexchat_get_context,
+                        hexchat_get_info,
+                        hexchat_get_prefs,
+                        hexchat_list_get,
+                        hexchat_list_free,
+                        hexchat_list_fields,
+                        hexchat_list_next,
+                        hexchat_list_str,
+                        hexchat_list_int,
+                        hexchat_plugingui_add,
+                        hexchat_plugingui_remove,
+                        hexchat_emit_print,
+                        hexchat_read_fd,
+                        hexchat_list_time,
+                        hexchat_gettext,
+                        hexchat_send_modes,
+                        hexchat_strip,
+                        hexchat_free,
+                        hexchat_pluginpref_set_str,
+                        hexchat_pluginpref_get_str,
+                        hexchat_pluginpref_set_int,
+                        hexchat_pluginpref_get_int,
+                        hexchat_pluginpref_delete,
+                        hexchat_pluginpref_list,
+                        hexchat_hook_server_attrs,
+                        hexchat_hook_print_attrs,
+                        hexchat_emit_print_attrs,
+                        hexchat_event_attrs_create,
+                        hexchat_event_attrs_free,
+                    },
+                    filename: filename,
+                    plugin_name: ptr::null(), // not initialized yet!
+                    plugin_desc: ptr::null(),
+                    plugin_vers: ptr::null(),
+                    free_strings: false,
+                    env: env,
+                    deinit: None,
+                    context: None,
+                })
+            }
+        }
+        let plug = Rc::pin(Holder::new_with(MockPluginNewWith {
+            filename,
+            env,
+        }));
+        struct MockPluginPreInit;
+        impl<'k, 'env: 'k> OperateIn<'k, MockPluginK<'env>>
+        for MockPluginPreInit {
+            type Out = ();
+            fn operate_in<'ph>(
+                self,
+                ph: Pin<&'ph UnsafeCell<MockPlugin<'ph, 'env>>>,
+            ) where 'k: 'ph {
+                let ptr = ph.get();
+                unsafe {
+                    // initialize plugin_name from filename(!)
+                    (*ptr).plugin_name = (*ptr).filename.as_ptr();
+                }
+            }
+        }
+        plug.as_ref().operate_in(MockPluginPreInit);
+        plug
+    }
+}