hexchat_unsafe_plugin/
lib.rs

1// Hexchat Plugin API Bindings for Rust
2// Copyright (C) 2018, 2021, 2022 Soni L.
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as
6// published by the Free Software Foundation, either version 3 of the
7// License, or (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Write hexchat plugins in Rust!
18//!
19//! This library provides safe API bindings for hexchat, but doesn't attempt to fix hexchat's own
20//! bugs. It makes no effort to stop you from unloading your own code while it's still running, for
21//! example.
22//!
23//! When using this library, it's strongly recommended to avoid heap-allocated statics (static
24//! mutexes, lazy_static, etc). This is because it's currently impossible to deallocate those on
25//! plugin unload. This can be worked around by placing those statics as fields in your plugin
26//! struct.
27//!
28//! This caveat does not apply to static assets (`static FOO: &'static str`, for example), but it
29//! does apply to thread-local storage.
30//!
31//! # Panics
32//!
33//! Unless otherwise stated, all functions in this crate taking strings (be
34//! that `&str`, `String`, etc) panic when the string contains embedded `NUL`
35//! characters (`\0`).
36//! 
37//! # Examples
38//!
39//! ```no_run
40//! #[macro_use]
41//! extern crate hexchat_unsafe_plugin;
42//!
43//! use std::pin::Pin;
44//! use std::sync::Mutex;
45//! use hexchat_unsafe_plugin::{Plugin, PluginHandle, HookHandle};
46//!
47//! #[derive(Default)]
48//! struct MyPlugin<'ph> {
49//!     cmd: Mutex<Option<HookHandle<'ph, 'ph>>>
50//! }
51//!
52//! unsafe impl<'ph> Plugin<'ph> for MyPlugin<'ph> {
53//!     fn init(self: Pin<&mut Self>, ph: &mut PluginHandle<'ph>, filename: &str, arg: Option<&str>) -> bool {
54//!         ph.register("myplugin", "0.1.0", "my simple plugin");
55//!         *self.cmd.lock().unwrap() = Some(ph.hook_command("hello-world", hexchat_unsafe_plugin::PRI_NORM, Some("prints 'Hello, World!'"), |ph, arg, arg_eol| {
56//!             ph.print("Hello, World!");
57//!             hexchat_unsafe_plugin::EAT_ALL
58//!         }));
59//!         true
60//!     }
61//! }
62//!
63//! hexchat_plugin!('ph, MyPlugin<'ph>);
64//!
65//! # fn main() { } // satisfy the compiler, we can't actually run the code
66//! ```
67
68/*
69 * Some info about how HexChat does things:
70 *
71 * All strings passed across the C API are UTF-8.
72 * - Except `hexchat_get_info(ph, "libdirfs")`, so we need to be careful with
73 *     that one.
74 *
75 * The pointers
76 * `name: *mut *const char, desc: *mut *const char, vers: *mut *const char`
77 * point to inside the ph - that is, they're aliased. Thus, we must never
78 * convert a ph to an & or &mut except as part of retrieving or setting values
79 * in it (e.g. `(*ph).hexchat_get_info` or `(*ph).userdata = value`).
80 *
81 * `hexchat_plugin_get_info` is never used, so we omit it. It would be
82 * impractical not to.
83 *
84 * These cause UB:
85 * - `hexchat_command` may invalidate the plugin context.
86 * - `hexchat_find_context` and `hexchat_nickcmp` use the plugin context
87 *     without checking it.
88 * - `hexchat_get_prefs` uses the plugin context if name == "state_cursor" or
89 *     "id" (or anything with the same hash).
90 * - `hexchat_list_get` uses the plugin context if name == "notify" (or
91 *     anything with the same hash).
92 * - `hexchat_list_str`, `hexchat_list_int`, 
93 * - `hexchat_emit_print`, `hexchat_emit_print_attrs` use the plugin context.
94 * - `hexchat_send_modes` uses the plugin context.
95 * We need to wrap them (or, alternatively, hexchat_command). However, there's
96 * no (safe) way to get a valid context afterwards.
97 * - Actually that's a lie. Hexchat does a trick to give you a context as part
98 *     of the channel list.
99 *     We can use that to our advantage. I'm not sure if it's better to wrap
100 *     hexchat_command or the other functions, tho.
101 *     (Do we want to walk a linked list every time we use hexchat_command?
102 *     I'd think hexchat_command is the most used API function... Plus,
103 *     emit_print could indirectly invalidate the context as well.)
104 *
105 * `hexchat_send_modes` should only be used with channels; however, it doesn't
106 * cause UB - it just doesn't work.
107 *
108 * `hexchat_pluginpref_get_int`, `hexchat_pluginpref_get_str`,
109 * `hexchat_pluginpref_set_int`, `hexchat_pluginpref_set_str` cannot be used
110 * while `name`, `desc`, `vers` are null.
111 *
112 * `hexchat_plugin_init` receives an arg string. it may be null. this isn't
113 * documented anywhere.
114 *
115 * We can add the "close context" hook as the first thing when registering a
116 * plugin, and invalidate known contexts from it. this is because hexchat
117 * always adds new hooks with the same priority before old hooks.
118 *
119 * Borrowing can be scary. Things that can invalidate borrows must be &mut, but
120 * borrows by themselves are generally fine. Unless they're not.
121 *
122 * Some get_info calls may be unsound on some platforms when threads are
123 * involved, as they use getenv. This should be fixed by libc. We still need to
124 * copy them into a String/CString.
125 */
126
127/*
128 * Some info about how we do things:
129 *
130 * DO NOT CALL printf/commandf/etc FAMILY OF FUNCTIONS. You can't avoid allocations, so just
131 * allocate some CStrings on the Rust side. It has the exact same effect, since those functions
132 * allocate internally anyway.
133 */
134
135/*
136 * Big list o' TODO:
137 * -[ ] Finish API support. [PRI-HIGH]
138 *     -[x] word
139 *     -[x] word_eol
140 *     -[x] HEXCHAT_PRI_{HIGHEST, HIGH, NORM, LOW, LOWEST}
141 *     -[x] HEXCHAT_EAT_{NONE, HEXCHAT, PLUGIN, ALL}
142 *     -[ ] HEXCHAT_FD_{READ, WRITE, EXCEPTION, NOTSOCKET}
143 *     -[x] hexchat_command (for commandf, use command(&format!("...")), it is equivalent.)
144 *     -[x] hexchat_print (for printf, use print(&format!("...")), it is equivalent.)
145 *     -[x] hexchat_emit_print
146 *     -[x] hexchat_emit_print_attrs
147 *     -[x] hexchat_send_modes
148 *     -[x] hexchat_nickcmp
149 *     -[x] hexchat_strip
150 *     -[x] ~~hexchat_free~~ not available - use Drop impls.
151 *     -[x] ~~hexchat_event_attrs_create~~ not available - converted as needed
152 *     -[x] ~~hexchat_event_attrs_free~~ not available - use Drop impls.
153 *     -[#] hexchat_get_info (with the below as separate methods)
154 *         -[x] libdirfs
155 *         -[ ] gtkwin_ptr
156 *         -[ ] win_ptr
157 *     -[x] hexchat_get_prefs
158 *     -[x] hexchat_list_get
159 *     -[x] ~~hexchat_list_fields~~ not available. no alternative provided.
160 *     -[x] hexchat_list_next
161 *     -[x] hexchat_list_str
162 *     -[x] hexchat_list_int
163 *     -[ ] hexchat_list_time
164 *     -[x] ~~hexchat_list_free~~ not available - use Drop impls.
165 *     -[x] hexchat_hook_command
166 *     -[ ] hexchat_hook_fd
167 *     -[x] hexchat_hook_print
168 *     -[x] hexchat_hook_print_attrs
169 *     -[#] hexchat_hook_server (implemented through _attrs)
170 *     -[x] hexchat_hook_server_attrs
171 *     -[x] hexchat_hook_timer
172 *     -[x] ~~hexchat_unhook~~ not available - use Drop impls
173 *     -[x] hexchat_find_context
174 *     -[x] hexchat_get_context
175 *     -[x] hexchat_set_context
176 *     -[x] hexchat_pluginpref_set_str
177 *     -[x] hexchat_pluginpref_get_str
178 *     -[x] ~~hexchat_pluginpref_set_int~~ not available - use `format!`
179 *     -[x] ~~hexchat_pluginpref_get_int~~ not available - use `str::parse`
180 *     -[x] hexchat_pluginpref_delete
181 *     -[x] hexchat_pluginpref_list
182 *     -[x] hexchat_plugingui_add
183 *     -[x] ~~hexchat_plugingui_remove~~ not available - use Drop impls.
184 * -[x] Wrap contexts within something we keep track of. Mark them invalid when contexts are
185 *     closed.
186 * -[x] Anchor closures on the stack using Rc<T>. Many (most?) hooks are reentrant. As far as I
187 *     know, all of them need this.
188 *     -[x] Additionally, use a Cell<bool> for timers.
189 * -[ ] ???
190 */
191
192#![cfg_attr(feature="nightly_tests", feature(c_variadic))]
193
194#[macro_use]
195extern crate impl_trait;
196#[doc(hidden)]
197pub extern crate libc;
198
199// private macros
200
201/// Calls a function on a PluginHandle struct.
202macro_rules! ph_call {
203    ($f:ident($ph:expr, $($args:expr),*)) => {
204        ((*$ph.data.ph).$f)($ph.plugin, $($args),*)
205    };
206    ($f:ident($ph:expr $(,)?)) => {
207        ((*$ph.data.ph).$f)($ph.plugin)
208    };
209}
210
211mod eat;
212mod extra_tests;
213mod infoid;
214mod internals;
215pub mod list;
216#[doc(hidden)]
217#[cfg(feature="nightly_tests")]
218pub mod mock;
219mod pluginfo;
220mod strip;
221mod word;
222
223pub use eat::*;
224pub use infoid::InfoId;
225pub use strip::*;
226pub use word::*;
227
228use internals::HexchatEventAttrs as RawAttrs;
229use internals::Ph as RawPh;
230use pluginfo::PluginInfo;
231
232use std::borrow::Cow;
233use std::cell::Cell;
234use std::cell::RefCell;
235use std::collections::HashSet;
236use std::convert::TryInto;
237use std::ffi::{CString, CStr};
238use std::fmt;
239use std::marker::PhantomData;
240use std::mem;
241use std::mem::ManuallyDrop;
242use std::mem::MaybeUninit;
243use std::panic::{AssertUnwindSafe, RefUnwindSafe, UnwindSafe, catch_unwind};
244use std::pin::Pin;
245use std::ptr;
246use std::rc::Rc;
247use std::rc::Weak as RcWeak;
248use std::str::FromStr;
249use std::thread;
250use std::time::{SystemTime, UNIX_EPOCH, Duration};
251
252#[doc(hidden)]
253pub use libc::{c_char, c_int, c_void, time_t};
254
255// ****** //
256// PUBLIC //
257// ****** //
258
259// Consts
260
261// PRI_*
262/// Equivalent to HEXCHAT_PRI_HIGHEST
263pub const PRI_HIGHEST: i32 = 127;
264/// Equivalent to HEXCHAT_PRI_HIGH
265pub const PRI_HIGH: i32 = 64;
266/// Equivalent to HEXCHAT_PRI_NORM
267pub const PRI_NORM: i32 = 0;
268/// Equivalent to HEXCHAT_PRI_LOW
269pub const PRI_LOW: i32 = -64;
270/// Equivalent to HEXCHAT_PRI_LOWEST
271pub const PRI_LOWEST: i32 = -128;
272
273// Traits
274
275/// A hexchat plugin.
276///
277/// # Safety
278///
279/// Modern operating systems cannot deal with dynamic unloading when threads
280/// are involved, because we still haven't figured out how to track which code
281/// address started a syscall that spawned a thread for some reason, so there's
282/// no way for the dynamic loader to stop those threads when unloading.
283///
284/// *Fortunately* we can just shift that responsibility onto the unsuspecting
285/// Rust user. Because Rust is a safe language, this makes writing plugins
286/// inherently unsafe!
287///
288/// At least Unsafe Rust is still safer than writing C. So you have that going.
289///
290/// TL;DR: Either don't use threads, or ensure they're dead in `Drop`/`deinit`.
291pub unsafe trait Plugin<'ph> {
292    /// Called to initialize the plugin.
293    fn init(self: Pin<&mut Self>, ph: &mut PluginHandle<'ph>, filename: &str, arg: Option<&str>) -> bool;
294
295    /// Called to deinitialize the plugin.
296    ///
297    /// This is always called immediately prior to Drop::drop.
298    ///
299    /// # A note about unwinding
300    ///
301    /// Panics in deinit will prevent the plugin from being correctly unloaded!
302    /// Be careful!
303    fn deinit(self: Pin<&mut Self>, ph: &mut PluginHandle<'ph>) {
304        let _ = ph;
305    }
306}
307
308// Structs
309
310/// A `*mut RawPh` with a lifetime bolted to it.
311///
312/// This allows us to enforce a non-`'static` lifetime on the `Plugin`.
313// this is NOT public API
314#[doc(hidden)]
315#[repr(transparent)]
316#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
317pub struct LtPhPtr<'ph> {
318    ph: *mut RawPh,
319    // 'ph has to be invariant because RawPh is self-referential.
320    // "ideally" we'd want `&'ph mut RawPh<'ph>`, tho the `*mut` above would've
321    // had the same effect if `RawPh` were `RawPh<'ph>`.
322    _lt: PhantomData<fn(&'ph ()) -> &'ph ()>,
323}
324
325/// A hexchat plugin handle, used to register hooks and interact with hexchat.
326///
327/// # Examples
328///
329/// ```no_run
330/// use hexchat_unsafe_plugin::{PluginHandle};
331///
332/// fn init(ph: &mut PluginHandle) {
333///     ph.register("myplug", "0.1.0", "my awesome plug");
334/// }
335/// ```
336pub struct PluginHandle<'ph> {
337    data: LtPhPtr<'ph>,
338    plugin: *mut RawPh,
339    contexts: Contexts,
340    // Used for registration
341    info: PluginInfo,
342}
343
344/// A setting value, returned by [`ValidContext::get_prefs`].
345#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
346pub enum PrefValue {
347    String(String),
348    Int(i32),
349    Bool(bool),
350}
351
352mod valid_context {
353    use std::ptr;
354
355    use crate::list;
356    use crate::PluginHandle;
357
358    /// A `PluginHandle` operating on a valid context.
359    ///
360    /// Methods that require a valid plugin context are only available through
361    /// this. Methods that may invalidate the plugin context also consume this.
362    ///
363    /// See also [`PluginHandle::ensure_valid_context`].
364    pub struct ValidContext<'a, 'ph: 'a> {
365        // NOTE: NOT mut(!)
366        pub(crate) ph: &'a PluginHandle<'ph>,
367        fields: list::Fields<'a, 'ph, list::Contexts>,
368        _hidden: (),
369    }
370
371    impl<'a, 'ph: 'a> ValidContext<'a, 'ph> {
372        /// Wraps a PluginHandle in a ValidContext.
373        ///
374        /// # Safety
375        ///
376        /// The PluginHandle's context must be valid.
377        pub(crate) unsafe fn new(ph: &'a mut PluginHandle<'ph>) -> Self {
378            static CONTEXTS: list::Contexts = list::Contexts;
379            Self {
380                ph: &*ph,
381                fields: list::Fields {
382                    context: &*ph,
383                    // this isn't actually documented but hexchat explicitly
384                    // supports passing in NULL for the current context.
385                    // this gives access to some properties that aren't
386                    // available through get_info.
387                    list: ptr::null_mut(),
388                    _t: &CONTEXTS,
389                },
390                _hidden: (),
391            }
392        }
393    }
394
395    impl<'a, 'ph: 'a> std::ops::Deref for ValidContext<'a, 'ph> {
396        type Target = list::Fields<'a, 'ph, list::Contexts>;
397        fn deref(&self) -> &Self::Target {
398            &self.fields
399        }
400    }
401}
402pub use valid_context::ValidContext;
403
404/// Event attributes.
405// TODO better docs.
406#[derive(Clone)]
407pub struct EventAttrs<'a> {
408    /// Server time.
409    #[deprecated(since="2.3.0", note="Use raw_server_time instead")]
410    pub server_time: Option<SystemTime>,
411    /// Raw server time. Only does anything if `use_raw_server_time` is set.
412    pub raw_server_time: libc::time_t,
413    /// Whether to pass `server_time` or `raw_server_time` to hexchat.
414    pub use_raw_server_time: bool,
415    _dummy: PhantomData<&'a ()>,
416}
417
418/// A hook handle.
419///
420/// Likes to get caught on stuff. Unhooks when dropped.
421#[must_use = "Hooks must be stored somewhere and are automatically unhooked on Drop"]
422pub struct HookHandle<'ph, 'f> where 'f: 'ph {
423    ph: LtPhPtr<'ph>,
424    hh: *const internals::HexchatHook,
425    freed: Rc<Cell<bool>>,
426    // this does actually store an Rc<...>, but on the other side of the FFI.
427    _f: PhantomData<Rc<HookUd<'f>>>,
428}
429
430/// A virtual plugin.
431#[must_use = "Virtual plugins must be stored somewhere and are automatically unregistered on Drop"]
432pub struct PluginEntryHandle<'ph> {
433    ph: LtPhPtr<'ph>,
434    entry: *const internals::PluginGuiHandle,
435}
436
437/// A context.
438#[derive(Clone)]
439pub struct Context<'ph> {
440    contexts: Contexts,
441    ctx: RcWeak<*const internals::HexchatContext>,
442    _ph: PhantomData<&'ph RawPh>,
443}
444
445/// The error returned by [`PluginHandle::with_context`] when the context is
446/// not valid.
447// #[derive(Debug)] // doesn't work
448pub struct InvalidContextError<F>(F);
449
450/// An iterable list of plugin pref names.
451pub struct PluginPrefList {
452    inner: Vec<u8>,
453}
454
455/// An iterator over `PluginPrefList`.
456pub struct PluginPrefListIter<'a> {
457    list: Option<&'a [u8]>,
458}
459
460// Enums
461
462/// Errors returned by `pluginpref_*` functions.
463#[derive(Debug)]
464pub enum PluginPrefError {
465    /// The var contains a forbidden character: `[ ,=\n]` (space, comma,
466    /// equals, newline), is too long (max 126 bytes), is empty, or contains
467    /// trailing ASCII whitespace.
468    ///
469    /// Returned by anything that interacts with vars. `pluginpref_list` only
470    /// returns these during iteration.
471    InvalidVar,
472    /// The value starts with space.
473    ///
474    /// Returned by `pluginpref_set`.
475    InvalidValue,
476    /// The input (var + value) was too long after encoding.
477    ///
478    /// Returned by `pluginpref_set`.
479    TooLong,
480    /// The returned value was not valid UTF-8.
481    ///
482    /// Returned by `pluginpref_get`.
483    Utf8Error(::std::ffi::IntoStringError),
484    /// The returned var was not valid UTF-8.
485    ///
486    /// Returned while iterating a `pluginpref_list`.
487    VarUtf8Error(::std::str::Utf8Error),
488    /// The operation failed.
489    ///
490    /// Returned by anything that interacts with pluginprefs. Iterating a
491    /// `pluginpref_list` never returns this.
492    Failed,
493}
494
495// ***** //
496// impls //
497// ***** //
498
499impl<'a> Iterator for PluginPrefListIter<'a> {
500    type Item = Result<&'a str, PluginPrefError>;
501
502    fn next(&mut self) -> Option<Self::Item> {
503        let mut splitter = self.list?.splitn(2, |x| *x == 0);
504        let ret = splitter.next().unwrap();
505        let ret = test_pluginpref_var(ret).and_then(|_| {
506            std::str::from_utf8(ret).map_err(|e| PluginPrefError::VarUtf8Error(e))
507        });
508        self.list = splitter.next();
509        Some(ret)
510    }
511}
512
513impl<'a> IntoIterator for &'a PluginPrefList {
514    type Item = Result<&'a str, PluginPrefError>;
515
516    type IntoIter = PluginPrefListIter<'a>;
517
518    fn into_iter(self) -> Self::IntoIter {
519        PluginPrefListIter {
520            list: self.inner.split_last().map(|(_, x)| x),
521        }
522    }
523}
524
525impl<F> InvalidContextError<F> {
526    /// Returns the closure wrapped within this error.
527    pub fn get_closure(self) -> F {
528        self.0
529    }
530}
531
532impl_trait! {
533    impl PrefValue {
534        /// Projects this `PrefValue` as `&str`, if it is a `String`. Returns
535        /// `None` otherwise.
536        pub fn as_str(&self) -> Option<&str> {
537            match self {
538                &Self::String(ref s) => Some(s),
539                _ => None,
540            }
541        }
542
543        /// Projects this `PrefValue` as `i32`, if it is an `Int`. Returns
544        /// `None` otherwise.
545        pub fn as_i32(&self) -> Option<i32> {
546            match self {
547                &Self::Int(i) => Some(i),
548                _ => None,
549            }
550        }
551
552        /// Projects this `PrefValue` as `bool`, if it is a `Bool`. Returns
553        /// `None` otherwise.
554        pub fn as_bool(&self) -> Option<bool> {
555            match self {
556                &Self::Bool(b) => Some(b),
557                _ => None,
558            }
559        }
560
561        /// Consumes this `PrefValue` and attempts to take a `String` out of
562        /// it.
563        pub fn into_string(self) -> Option<String> {
564            match self {
565                Self::String(s) => Some(s),
566                _ => None,
567            }
568        }
569
570        impl trait From<String> {
571            fn from(s: String) -> Self {
572                Self::String(s)
573            }
574        }
575        impl trait From<i32> {
576            fn from(i: i32) -> Self {
577                Self::Int(i)
578            }
579        }
580        impl trait From<bool> {
581            fn from(b: bool) -> Self {
582                Self::Bool(b)
583            }
584        }
585    }
586}
587
588impl<'ph, 'f> HookHandle<'ph, 'f> where 'f: 'ph {
589    /// If this is a timer hook, returns whether the hook has expired.
590    ///
591    /// Otherwise, returns false.
592    ///
593    /// # Examples
594    ///
595    /// ```no_run
596    /// use hexchat_unsafe_plugin::{HookHandle};
597    ///
598    /// /// Remove timers that have expired.
599    /// fn clean_up_timers(timers: &mut Vec<HookHandle<'_, '_>>) {
600    ///     timers.retain(|timer| {
601    ///         !timer.expired()
602    ///     });
603    /// }
604    /// ```
605    pub fn expired(&self) -> bool {
606        self.freed.get()
607    }
608}
609
610impl<'ph, 'f> Drop for HookHandle<'ph, 'f> where 'f: 'ph {
611    fn drop(&mut self) {
612        if self.freed.get() {
613            // already free'd.
614            return;
615        }
616        self.freed.set(true);
617        unsafe {
618            let b = ((*self.ph.ph).hexchat_unhook)(self.ph.ph, self.hh) as *mut HookUd<'f>;
619            // we assume b is not null. this should be safe.
620            // just drop it
621            drop(Rc::from_raw(b));
622        }
623    }
624}
625
626impl<'ph> Drop for PluginEntryHandle<'ph> {
627    fn drop(&mut self) {
628        unsafe {
629            ((*self.ph.ph).hexchat_plugingui_remove)(self.ph.ph, self.entry);
630        }
631    }
632}
633
634impl_trait! {
635    impl<'ph> Context<'ph> {
636        impl trait UnwindSafe {}
637        impl trait RefUnwindSafe {}
638        impl trait Drop {
639            fn drop(&mut self) {
640                // check if we need to clean anything up
641                if self.ctx.strong_count() == 1 && self.ctx.weak_count() == 1 {
642                    let strong = self.ctx.upgrade().unwrap();
643                    self.contexts.borrow_mut().remove(&strong);
644                }
645            }
646        }
647    }
648}
649
650/// Logs a panic message.
651///
652/// # Safety
653///
654/// `ph` must be a valid pointer (see `std::ptr::read`).
655unsafe fn log_panic(ph: *mut RawPh, e: Box<dyn std::any::Any + Send + 'static>) {
656    // if it's a &str or String, just print it
657    if let Some(s) = e.downcast_ref::<&str>() {
658        hexchat_print_str(ph, s, false);
659    } else if let Some(s) = e.downcast_ref::<String>() {
660        hexchat_print_str(ph, &s, false);
661    } else if let Some(s) = e.downcast_ref::<Cow<'static, str>>() {
662        hexchat_print_str(ph, &s, false);
663    } else {
664        hexchat_print_str(ph, "couldn't log panic message", false);
665        if let Err(e) = catch_unwind(AssertUnwindSafe(|| drop(e))) {
666            // eprintln panics, hexchat_print_str doesn't.
667            hexchat_print_str(ph, "ERROR: panicked while trying to log panic!", false);
668            mem::forget(e);
669            std::process::abort();
670        }
671    }
672}
673
674/// Handles a hook panic at the C-Rust ABI boundary.
675///
676/// # Safety
677///
678/// `ph` must be a valid pointer (see `std::ptr::read`).
679unsafe fn call_hook_protected<F: FnOnce() -> Eat + UnwindSafe>(
680    ph: *mut RawPh,
681    f: F
682) -> Eat {
683    match catch_unwind(f) {
684        Result::Ok(v @ _) => v,
685        Result::Err(e @ _) => {
686            log_panic(ph, e);
687            EAT_NONE
688        }
689    }
690}
691
692impl<'ph> PluginHandle<'ph> {
693    /// Wraps the raw handle.
694    ///
695    /// # Safety
696    ///
697    /// `ph` must be a valid pointer (see `std::ptr::read`).
698    unsafe fn new(data: LtPhPtr<'ph>, info: PluginInfo, contexts: Contexts) -> PluginHandle<'ph> {
699        PluginHandle {
700            data, plugin: data.ph, info, contexts
701        }
702    }
703
704    /// Registers this hexchat plugin. This must be called exactly once when the plugin is loaded.
705    ///
706    /// # Panics
707    ///
708    /// This function panics if this plugin is already registered.
709    ///
710    /// # Examples
711    ///
712    /// ```no_run
713    /// use hexchat_unsafe_plugin::PluginHandle;
714    ///
715    /// fn init(ph: &mut PluginHandle<'_>) {
716    ///     ph.register("foo", "0.1.0", "my foo plugin");
717    /// }
718    /// ```
719    pub fn register(&mut self, name: &str, desc: &str, ver: &str) {
720        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't be registered");
721        unsafe {
722            let info = self.info;
723            if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
724                panic!("Attempt to re-register a plugin");
725            }
726            let name = CString::new(name).unwrap();
727            let desc = CString::new(desc).unwrap();
728            let ver = CString::new(ver).unwrap();
729            // these shouldn't panic. if they do, we'll need to free them afterwards.
730            (*info.name) = name.into_raw();
731            (*info.desc) = desc.into_raw();
732            (*info.vers) = ver.into_raw();
733        }
734    }
735
736    /// Returns this plugin's registered name.
737    ///
738    /// # Panics
739    ///
740    /// This function panics if this plugin is not registered.
741    pub fn get_name(&self) -> &str {
742        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't be registered");
743        unsafe {
744            let info = self.info;
745            if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
746                std::str::from_utf8_unchecked(CStr::from_ptr(*info.name).to_bytes())
747            } else {
748                panic!("Attempt to get the name of a plugin that was not yet registered.");
749            }
750        }
751    }
752
753    /// Returns this plugin's registered description.
754    ///
755    /// # Panics
756    ///
757    /// This function panics if this plugin is not registered.
758    pub fn get_description(&self) -> &str {
759        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't be registered");
760        unsafe {
761            let info = self.info;
762            if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
763                std::str::from_utf8_unchecked(CStr::from_ptr(*info.desc).to_bytes())
764            } else {
765                panic!("Attempt to get the description of a plugin that was not yet registered.");
766            }
767        }
768    }
769
770    /// Returns this plugin's registered version.
771    ///
772    /// # Panics
773    ///
774    /// This function panics if this plugin is not registered.
775    pub fn get_version(&self) -> &str {
776        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't be registered");
777        unsafe {
778            let info = self.info;
779            if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
780                std::str::from_utf8_unchecked(CStr::from_ptr(*info.vers).to_bytes())
781            } else {
782                panic!("Attempt to get the version of a plugin that was not yet registered.");
783            }
784        }
785    }
786
787    /// Ensures the current context is valid.
788    ///
789    /// # Panics
790    ///
791    /// This function may panic if it's called while hexchat is closing.
792    pub fn ensure_valid_context<F, R>(&mut self, f: F) -> R where F: for<'a> FnOnce(ValidContext<'a, 'ph>) -> R {
793        let ctx = self.get_context();
794        let res = self.with_context(&ctx, f);
795        match res {
796            Result::Ok(r @ _) => r,
797            Result::Err(e @ _) => {
798                let nctx = self.find_valid_context().expect("ensure_valid_context failed (find_valid_context failed), was hexchat closing?");
799                self.with_context(&nctx, e.get_closure()).ok().expect("ensure_valid_context failed, was hexchat closing?")
800            }
801        }
802    }
803
804    /// Operates on a virtual plugin.
805    ///
806    /// # Examples
807    ///
808    /// ```no_run
809    /// use hexchat_unsafe_plugin::{PluginHandle};
810    ///
811    /// fn contexts_can_be_passed_around(ph: &mut PluginHandle<'_>) {
812    ///     let ctx = ph.get_context();
813    ///     let mut vplug = ph.plugingui_add("foo", "bar", "baz", "qux");
814    ///     ph.for_entry_mut(&mut vplug, |ph| {
815    ///         ph.set_context(&ctx);
816    ///         ph.get_context()
817    ///     });
818    /// }
819    /// ```
820    pub fn for_entry_mut<F, R>(&mut self, entry: &mut PluginEntryHandle<'ph>, f: F) -> R where F: FnOnce(&mut PluginHandle<'ph>) -> R {
821        // we're doing something kinda (very) weird here, but this is the only
822        // way to get and set pluginprefs for virtual plugins. (not that we
823        // support those yet...)
824        // this should be sound?
825        let data = self.data;
826        let info = self.info;
827        let contexts = Rc::clone(&self.contexts);
828        let mut handle = unsafe { Self::new(data, info, contexts) };
829        handle.plugin = entry.entry as *mut RawPh;
830        handle.set_context(&self.get_context());
831        f(&mut handle)
832    }
833
834    /// Registers a virtual plugin.
835    pub fn plugingui_add(&self, filename: &str, name: &str, description: &str, version: &str) -> PluginEntryHandle<'ph> {
836        let filename = CString::new(filename).unwrap();
837        let name = CString::new(name).unwrap();
838        let description = CString::new(description).unwrap();
839        let version = CString::new(version).unwrap();
840        let res = unsafe {
841            ph_call!(hexchat_plugingui_add(self, filename.as_ptr(), name.as_ptr(), description.as_ptr(), version.as_ptr(), ptr::null_mut()))
842        };
843        PluginEntryHandle { ph: self.data, entry: res }
844    }
845
846    /// Returns the current context.
847    ///
848    /// Note: The returned context may be invalid. Use [`Self::set_context`] to
849    /// check.
850    pub fn get_context(&self) -> Context<'ph> {
851        let ctxp = unsafe { ph_call!(hexchat_get_context(self)) };
852        // This needs to be fixed by hexchat. I cannot make the value become
853        // null when it's invalid without invoking UB. This is because I can't
854        // set_context to null.
855        let ok = unsafe { ph_call!(hexchat_set_context(self, ctxp)) };
856        unsafe { wrap_context(self, if ok == 0 { ptr::null() } else { ctxp }) }
857    }
858
859    /// Sets the current context.
860    ///
861    /// Returns `true` if the context is valid, `false` otherwise.
862    pub fn set_context(&mut self, ctx: &Context<'ph>) -> bool {
863        // we could've made this &self but we didn't. avoid breaking API.
864        self.set_context_internal(ctx)
865    }
866
867    /// Do something in a valid context.
868    ///
869    /// Note that this changes the active context and doesn't change it back
870    /// (but that should be fine for most use-cases).
871    ///
872    /// # Errors
873    ///
874    /// Returns `Err(InvalidContextError(f))` if the context is invalid. See
875    /// [`set_context`]. Otherwise, returns `Ok(f(...))`.
876    ///
877    /// Note that `InvalidContextError` contains the original closure. This
878    /// allows you to retry, if you so wish.
879    ///
880    /// [`set_context`]: #method.set_context
881    #[inline]
882    pub fn with_context<F, R>(&mut self, ctx: &Context<'ph>, f: F) -> Result<R, InvalidContextError<F>> where F: for<'a> FnOnce(ValidContext<'a, 'ph>) -> R {
883        if !self.set_context(ctx) {
884            Err(InvalidContextError(f))
885        } else {
886            Ok(f(unsafe { ValidContext::new(self) }))
887        }
888    }
889
890    /// Sets a command hook.
891    ///
892    /// # Panics
893    ///
894    /// Panics if this is a borrowed [`PluginEntryHandle`].
895    ///
896    /// # Examples
897    ///
898    /// ```no_run
899    /// use hexchat_unsafe_plugin::{PluginHandle, HookHandle};
900    ///
901    /// fn register_commands<'ph>(ph: &mut PluginHandle<'ph>) -> Vec<HookHandle<'ph, 'ph>> {
902    ///     vec![
903    ///     ph.hook_command("hello-world", hexchat_unsafe_plugin::PRI_NORM, Some("prints 'Hello, World!'"), |ph, arg, arg_eol| {
904    ///         ph.print("Hello, World!");
905    ///         hexchat_unsafe_plugin::EAT_ALL
906    ///     }),
907    ///     ]
908    /// }
909    /// ```
910    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 {
911        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't have hooks");
912        unsafe extern "C" fn callback(word: *const *const c_char, word_eol: *const *const c_char, ud: *mut c_void) -> c_int {
913            let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd);
914            (f)(word, word_eol, ptr::null()).do_eat as c_int
915        }
916        let b: Rc<HookUd> = {
917            let ph = self.data;
918            let info = self.info;
919            let contexts = Rc::clone(&self.contexts);
920            Rc::new(Box::new(move |word, word_eol, _| {
921                let cb = &cb;
922                let contexts = Rc::clone(&contexts);
923                unsafe {
924                    call_hook_protected(ph.ph, move || {
925                        let mut ph = PluginHandle::new(ph, info, contexts);
926                        let word = Word::new(word);
927                        let word_eol = WordEol::new(word_eol);
928                        cb(&mut ph, word, word_eol)
929                    })
930                }
931            }))
932        };
933        let name = CString::new(cmd).unwrap();
934        let help_text = help.map(CString::new).map(Result::unwrap);
935        let bp = Rc::into_raw(b);
936        unsafe {
937            let res = ph_call!(hexchat_hook_command(self, name.as_ptr(), pri as c_int, callback, help_text.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()), bp as *mut _));
938            assert!(!res.is_null());
939            HookHandle { ph: self.data, hh: res, freed: Default::default(), _f: PhantomData }
940        }
941    }
942    /// Sets a server hook.
943    ///
944    /// # Examples
945    ///
946    /// ```no_run
947    /// use hexchat_unsafe_plugin::{PluginHandle, HookHandle};
948    ///
949    /// fn register_server_hooks<'ph>(ph: &mut PluginHandle<'ph>) -> Vec<HookHandle<'ph, 'ph>> {
950    ///     vec![
951    ///     ph.hook_server("PRIVMSG", hexchat_unsafe_plugin::PRI_NORM, |ph, word, word_eol| {
952    ///         if word.len() > 0 && word[0].starts_with('@') {
953    ///             ph.print("We have message tags!?");
954    ///         }
955    ///         hexchat_unsafe_plugin::EAT_NONE
956    ///     }),
957    ///     ]
958    /// }
959    /// ```
960    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 {
961        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't have hooks");
962        self.hook_server_attrs(cmd, pri, move |ph, w, we, _| cb(ph, w, we))
963    }
964    /// Sets a server hook, with attributes.
965    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 {
966        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't have hooks");
967        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 {
968            let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd);
969            (f)(word, word_eol, attrs).do_eat as c_int
970        }
971        let b: Rc<HookUd> = {
972            let ph = self.data;
973            let info = self.info;
974            let contexts = Rc::clone(&self.contexts);
975            Rc::new(Box::new(move |word, word_eol, attrs| {
976                let cb = &cb;
977                let contexts = Rc::clone(&contexts);
978                unsafe {
979                    call_hook_protected(ph.ph, move || {
980                        let mut ph = PluginHandle::new(ph, info, contexts);
981                        let word = Word::new(word);
982                        let word_eol = WordEol::new(word_eol);
983                        let attrs = (&*attrs).into();
984                        cb(&mut ph, word, word_eol, attrs)
985                    })
986                }
987            }))
988        };
989        let name = CString::new(cmd).unwrap();
990        let bp = Rc::into_raw(b);
991        unsafe {
992            let res = ph_call!(hexchat_hook_server_attrs(self, name.as_ptr(), pri as c_int, callback, bp as *mut _));
993            assert!(!res.is_null());
994            HookHandle { ph: self.data, hh: res, freed: Default::default(), _f: PhantomData }
995        }
996    }
997    /// Sets a print hook.
998    ///
999    /// # Examples
1000    ///
1001    /// ```no_run
1002    /// use hexchat_unsafe_plugin::{PluginHandle, HookHandle};
1003    ///
1004    /// fn register_print_hooks<'ph>(ph: &mut PluginHandle<'ph>) -> Vec<HookHandle<'ph, 'ph>> {
1005    ///     vec![
1006    ///     ph.hook_print("Channel Message", hexchat_unsafe_plugin::PRI_NORM, |ph, arg| {
1007    ///         if let Some(nick) = arg.get(0) {
1008    ///             if *nick == "KnOwN_SpAmMeR" {
1009    ///                 return hexchat_unsafe_plugin::EAT_ALL
1010    ///             }
1011    ///         }
1012    ///         hexchat_unsafe_plugin::EAT_NONE
1013    ///     }),
1014    ///     ]
1015    /// }
1016    /// ```
1017    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 {
1018        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't have hooks");
1019        // hmm, is there any way to avoid this code duplication?
1020        // hook_print is special because dummy prints (keypresses, Close Context) are handled
1021        // through here, but never through hook_print_attrs. :/
1022        unsafe extern "C" fn callback(word: *const *const c_char, ud: *mut c_void) -> c_int {
1023            let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd);
1024            (f)(word, ptr::null(), ptr::null()).do_eat as c_int
1025        }
1026        // we use "Close Context" to clean up dangling pointers.
1027        // hexchat lets plugins/hooks eat "Close Context" from eachother (tho
1028        // not from hexchat, for good reason), so this can still be unsound
1029        // if other plugins eat "Close Context". in those cases, we'd consider
1030        // those plugins to be faulty, tho it'd be nice if hexchat handled it.
1031        // we still do our best to be well-behaved.
1032        let suppress_eat = name.eq_ignore_ascii_case("Close Context");
1033        let b: Rc<HookUd> = {
1034            let ph = self.data;
1035            let info = self.info;
1036            let contexts = Rc::clone(&self.contexts);
1037            Rc::new(Box::new(move |word, _, _| {
1038                let cb = &cb;
1039                let contexts = Rc::clone(&contexts);
1040                unsafe {
1041                    call_hook_protected(ph.ph, move || {
1042                        let mut ph = PluginHandle::new(ph, info, contexts);
1043                        let word = Word::new(word);
1044                        match cb(&mut ph, word) {
1045                            _ if suppress_eat => EAT_NONE,
1046                            eat => eat,
1047                        }
1048                    })
1049                }
1050            }))
1051        };
1052        let name = CString::new(name).unwrap();
1053        let bp = Rc::into_raw(b);
1054        unsafe {
1055            let res = ph_call!(hexchat_hook_print(self, name.as_ptr(), pri as c_int, callback, bp as *mut _));
1056            assert!(!res.is_null());
1057            HookHandle { ph: self.data, hh: res, freed: Default::default(), _f: PhantomData }
1058        }
1059    }
1060    /// Sets a print hook, with attributes.
1061    ///
1062    /// # Examples
1063    ///
1064    /// ```no_run
1065    /// use hexchat_unsafe_plugin::{PluginHandle, HookHandle};
1066    ///
1067    /// fn register_print_hooks<'ph>(ph: &mut PluginHandle<'ph>) -> Vec<HookHandle<'ph, 'ph>> {
1068    ///     vec![
1069    ///     ph.hook_print_attrs("Channel Message", hexchat_unsafe_plugin::PRI_NORM, |ph, arg, attrs| {
1070    ///         if let Some(nick) = arg.get(0) {
1071    ///             if *nick == "KnOwN_SpAmMeR" {
1072    ///                 return hexchat_unsafe_plugin::EAT_ALL
1073    ///             }
1074    ///         }
1075    ///         hexchat_unsafe_plugin::EAT_NONE
1076    ///     }),
1077    ///     ]
1078    /// }
1079    /// ```
1080    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 {
1081        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't have hooks");
1082        unsafe extern "C" fn callback(word: *const *const c_char, attrs: *const RawAttrs, ud: *mut c_void) -> c_int {
1083            let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd);
1084            (f)(word, ptr::null(), attrs).do_eat as c_int
1085        }
1086        let b: Rc<HookUd> = {
1087            let ph = self.data;
1088            let info = self.info;
1089            let contexts = Rc::clone(&self.contexts);
1090            Rc::new(Box::new(move |word, _, attrs| {
1091                let cb = &cb;
1092                let contexts = Rc::clone(&contexts);
1093                unsafe {
1094                    call_hook_protected(ph.ph, move || {
1095                        let mut ph = PluginHandle::new(ph, info, contexts);
1096                        let word = Word::new(word);
1097                        let attrs = (&*attrs).into();
1098                        cb(&mut ph, word, attrs)
1099                    })
1100                }
1101            }))
1102        };
1103        let name = CString::new(name).unwrap();
1104        let bp = Rc::into_raw(b);
1105        unsafe {
1106            let res = ph_call!(hexchat_hook_print_attrs(self, name.as_ptr(), pri as c_int, callback, bp as *mut _));
1107            assert!(!res.is_null());
1108            HookHandle { ph: self.data, hh: res, freed: Default::default(), _f: PhantomData }
1109        }
1110    }
1111    /// Sets a timer hook
1112    ///
1113    /// # Examples
1114    ///
1115    /// ```no_run
1116    /// use hexchat_unsafe_plugin::{PluginHandle, HookHandle};
1117    ///
1118    /// fn register_timers<'ph>(ph: &mut PluginHandle<'ph>) -> Vec<HookHandle<'ph, 'ph>> {
1119    ///     vec![
1120    ///     ph.hook_timer(2000, |ph| {
1121    ///         ph.print("timer up!");
1122    ///         false
1123    ///     }),
1124    ///     ]
1125    /// }
1126    /// ```
1127    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 {
1128        assert_eq!(self.data.ph, self.plugin, "PluginEntryHandle can't have hooks");
1129        unsafe extern "C" fn callback(ud: *mut c_void) -> c_int {
1130            let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd);
1131            (f)(ptr::null(), ptr::null(), ptr::null()).do_eat as c_int
1132        }
1133        let freed = Rc::new(Cell::new(false));
1134        // helps us clean up the thing when returning false
1135        let dropper = Rc::new(Cell::new(None));
1136        let b: Rc<HookUd> = {
1137            let ph = self.data;
1138            let info = self.info;
1139            let contexts = Rc::clone(&self.contexts);
1140            let freed = AssertUnwindSafe(Rc::clone(&freed));
1141            let dropper = AssertUnwindSafe(Rc::clone(&dropper));
1142            Rc::new(Box::new(move |_, _, _| {
1143                let cb = &cb;
1144                let contexts = Rc::clone(&contexts);
1145                let res = unsafe {
1146                    call_hook_protected(ph.ph, move || {
1147                        let mut ph = PluginHandle::new(ph, info, contexts);
1148                        if cb(&mut ph) {
1149                            EAT_HEXCHAT
1150                        } else {
1151                            EAT_NONE
1152                        }
1153                    })
1154                };
1155                if res == EAT_NONE && !freed.get() {
1156                    freed.set(true);
1157                    unsafe {
1158                        // drop may panic
1159                        // and so may unwrap
1160                        // but we must not panic
1161                        // (kinda silly to abuse call_hook_protected here
1162                        // but hey, it works and it helps with stuff)
1163                        call_hook_protected(ph.ph, || {
1164                            drop(Rc::from_raw(dropper.take().unwrap()));
1165                            EAT_NONE
1166                        });
1167                    }
1168                }
1169                res
1170            }))
1171        };
1172        let bp = Rc::into_raw(b);
1173        dropper.set(Some(bp));
1174        unsafe {
1175            let res = ph_call!(hexchat_hook_timer(self, timeout as c_int, callback, bp as *mut _));
1176            assert!(!res.is_null());
1177            HookHandle { ph: self.data, hh: res, freed: freed, _f: PhantomData }
1178        }
1179    }
1180
1181    /// Prints to the hexchat buffer.
1182    // this checks the context internally. if it didn't, it wouldn't be safe to
1183    // have here.
1184    pub fn print<T: ToString>(&self, s: T) {
1185        let s = s.to_string();
1186        unsafe {
1187            hexchat_print_str(self.data.ph, &*s, true);
1188        }
1189    }
1190
1191    /// Prints to this hexchat buffer.
1192    ///
1193    /// Glue for usage of the [`write!`] macro with hexchat.
1194    ///
1195    /// # Panics
1196    ///
1197    /// This panics if any broken formatting trait implementations are used in
1198    /// the format arguments. See also [`format!`].
1199    ///
1200    /// # Examples
1201    ///
1202    /// ```no_run
1203    /// use hexchat_unsafe_plugin::PluginHandle;
1204    ///
1205    /// fn hello_world(ph: &mut PluginHandle<'_>) {
1206    ///     write!(ph, "Hello, world!");
1207    /// }
1208    /// ```
1209    pub fn write_fmt(&self, fmt: fmt::Arguments<'_>) {
1210        if let Some(s) = fmt.as_str() {
1211            // "fast" path. hexchat_print_str still has to allocate, and
1212            // hexchat is slow af.
1213            unsafe {
1214                hexchat_print_str(self.data.ph, s, true);
1215            }
1216        } else {
1217            self.print(fmt);
1218        }
1219    }
1220
1221    /// Returns context and client information.
1222    ///
1223    /// See [`InfoId`] for the kinds of information that can be retrieved.
1224    ///
1225    /// # Examples
1226    ///
1227    /// ```no_run
1228    /// use hexchat_unsafe_plugin::{InfoId, PluginHandle};
1229    ///
1230    /// /// Returns whether we are currently away.
1231    /// fn is_away(ph: &mut PluginHandle<'_>) -> bool {
1232    ///     ph.get_info(InfoId::Away).is_some()
1233    /// }
1234    /// ```
1235    pub fn get_info<'a>(&'a self, id: InfoId) -> Option<Cow<'a, str>> {
1236        let id_cstring = CString::new(&*id.name()).unwrap();
1237        unsafe {
1238            let res = ph_call!(hexchat_get_info(self, id_cstring.as_ptr()));
1239            if res.is_null() {
1240                None
1241            } else {
1242                let s = CStr::from_ptr(res).to_str();
1243                // FIXME: figure out which InfoId's are safe to borrow.
1244                Some(s.expect("broken hexchat").to_owned().into())
1245            }
1246        }
1247    }
1248
1249    /// Returns the path to the plugin directory, used for auto-loading
1250    /// plugins.
1251    ///
1252    /// The returned `CStr` is not guaranteed to be valid UTF-8, but local
1253    /// file system encoding.
1254    ///
1255    /// # Examples
1256    ///
1257    /// ```no_run
1258    /// use std::path::PathBuf;
1259    ///
1260    /// use hexchat_unsafe_plugin::PluginHandle;
1261    ///
1262    /// // On Unix, we can treat this as an array-of-bytes filesystem path.
1263    /// #[cfg(unix)]
1264    /// fn plugin_dir(ph: &PluginHandle<'_>) -> PathBuf {
1265    ///     use std::ffi::OsString;
1266    ///     use std::os::unix::ffi::OsStringExt;
1267    ///
1268    ///     let libdirfs = ph.get_libdirfs().expect("should exist");
1269    ///     OsString::from_vec(libdirfs.into_bytes()).into()
1270    /// }
1271    /// ```
1272    pub fn get_libdirfs(&self) -> Option<CString> {
1273        let id_cstring = CString::new("libdirfs").unwrap();
1274        unsafe {
1275            let res = ph_call!(hexchat_get_info(self, id_cstring.as_ptr()));
1276            if res.is_null() {
1277                None
1278            } else {
1279                Some(CStr::from_ptr(res).to_owned())
1280            }
1281        }
1282    }
1283
1284    /// Strips attributes from text. See [`Strip`] for which attributes can
1285    /// be stripped.
1286    ///
1287    /// # Examples
1288    ///
1289    /// ```no_run
1290    /// use hexchat_unsafe_plugin::{PluginHandle, Strip};
1291    ///
1292    /// /// Removes colors
1293    /// fn strip_colors(ph: &PluginHandle<'_>, s: &str) -> String {
1294    ///     ph.strip(s, Strip::new().colors(true))
1295    /// }
1296    /// ```
1297    #[cfg(feature = "nul_in_strip")]
1298    pub fn strip(&self, s: &str, strip: Strip) -> String {
1299        // ironically the single API where we can pass embedded NULs.
1300        // we also don't need to worry about terminating it with NUL.
1301        let mut out = Vec::with_capacity(s.len());
1302        let in_ptr = s.as_ptr() as *const _;
1303        let in_len = s.len().try_into().unwrap();
1304        let flags = strip.flags();
1305        // NOTE: avoid panicking from here.
1306        let stripped = unsafe {
1307            ph_call!(hexchat_strip(self, in_ptr, in_len, flags))
1308        };
1309        // tho annoyingly we don't know the out len, so we need to count NULs.
1310        let in_nuls = s.as_bytes().into_iter().filter(|&&x| x == 0).count();
1311        let mut out_len = 0;
1312        for _ in 0..=in_nuls {
1313            while unsafe { *stripped.add(out_len) } != 0 {
1314                out_len += 1;
1315            }
1316            out_len += 1;
1317        }
1318        out.extend_from_slice(unsafe {
1319            // take out the extra NUL at the end.
1320            std::slice::from_raw_parts(stripped as *const _, out_len - 1)
1321        });
1322        unsafe {
1323            ph_call!(hexchat_free(self, stripped as *const _));
1324        }
1325        // we can panic again here, tho this should never panic.
1326        String::from_utf8(out).unwrap()
1327    }
1328
1329    /// Strips attributes from text. See [`Strip`] for which attributes can
1330    /// be stripped.
1331    ///
1332    /// # Examples
1333    ///
1334    /// ```no_run
1335    /// use hexchat_unsafe_plugin::{PluginHandle, Strip};
1336    ///
1337    /// /// Removes colors
1338    /// fn strip_colors(ph: &PluginHandle<'_>, s: &str) -> String {
1339    ///     ph.strip(s, Strip::new().colors(true))
1340    /// }
1341    /// ```
1342    #[cfg(not(feature = "nul_in_strip"))]
1343    pub fn strip(&self, s: &str, strip: Strip) -> String {
1344        // make sure s doesn't contain nuls
1345        assert!(!s.as_bytes().contains(&0), "embedded nuls are not allowed");
1346        let mut out = Vec::with_capacity(s.len());
1347        let in_ptr = s.as_ptr() as *const _;
1348        let in_len = s.len().try_into().unwrap();
1349        let flags = strip.flags();
1350        // NOTE: avoid panicking from here.
1351        let stripped = unsafe {
1352            ph_call!(hexchat_strip(self, in_ptr, in_len, flags))
1353        };
1354        out.extend_from_slice(unsafe { CStr::from_ptr(stripped) }.to_bytes());
1355        unsafe {
1356            ph_call!(hexchat_free(self, stripped as *const _));
1357        }
1358        // we can panic again here, tho this should never panic.
1359        String::from_utf8(out).unwrap()
1360    }
1361
1362    /// Sets a pluginpref.
1363    ///
1364    /// Note: If two pluginprefs exist with the same name, but different ASCII
1365    /// case, only one will be available through `pluginpref_get`.
1366    ///
1367    /// # Panics
1368    ///
1369    /// Panics if the plugin is not registered.
1370    ///
1371    /// # Examples
1372    ///
1373    /// ```no_run
1374    /// use hexchat_unsafe_plugin::PluginHandle;
1375    ///
1376    /// fn set_str(ph: &PluginHandle<'_>, val: &str) {
1377    ///     ph.pluginpref_set("string", val);
1378    /// }
1379    ///
1380    /// fn set_int(ph: &PluginHandle<'_>, val: i32) {
1381    ///     ph.pluginpref_set("int", &format!("{}", val));
1382    /// }
1383    /// ```
1384    pub fn pluginpref_set(
1385        &self,
1386        var: &str,
1387        value: &str,
1388    ) -> Result<(), PluginPrefError> {
1389        assert!(!self.info.name.is_null(), "must register plugin first");
1390        if value.starts_with(' ') {
1391            return Err(PluginPrefError::InvalidValue)
1392        }
1393        let var_len = var.len();
1394        let var = check_pluginpref_var(var)?;
1395        let mut val_len = value.len();
1396        // octal is \000 - \377, adds 3 bytes
1397        val_len += 3 * value.bytes().filter(|&x| x < 32 || x > 127).count();
1398        // special chars are \t \n etc, or octal - 2 bytes
1399        val_len -= 2 * value.bytes().filter(|&x| {
1400            matches!(
1401                x,
1402                | b'\n'
1403                | b'\r'
1404                | b'\t'
1405                | b'\x0C' // \f
1406                | b'\x08' // \b
1407            )
1408        }).count();
1409        // additionally \ and " also get an extra byte
1410        val_len += value.bytes().filter(|&x| x == b'"' || x == b'\\').count();
1411        // 3 bytes from " = ", < 512 because 511 + NUL
1412        if var_len + val_len + 3 < 512 {
1413            let value = CString::new(value).unwrap();
1414            let success = unsafe {
1415                ph_call!(
1416                    hexchat_pluginpref_set_str(
1417                        self,
1418                        var.as_ptr(),
1419                        value.as_ptr()
1420                    )
1421                )
1422            } != 0;
1423            if !success {
1424                Err(PluginPrefError::Failed)
1425            } else {
1426                Ok(())
1427            }
1428        } else {
1429            Err(PluginPrefError::TooLong)
1430        }
1431    }
1432
1433    /// Retrieves a pluginpref.
1434    ///
1435    /// # Panics
1436    ///
1437    /// Panics if the plugin is not registered.
1438    ///
1439    /// # Examples
1440    ///
1441    /// ```no_run
1442    /// use hexchat_unsafe_plugin::PluginHandle;
1443    ///
1444    /// fn get_str(ph: &PluginHandle<'_>) -> String {
1445    ///     ph.pluginpref_get("string").unwrap_or(String::new())
1446    /// }
1447    ///
1448    /// fn get_int(ph: &PluginHandle<'_>) -> i32 {
1449    ///     ph.pluginpref_get("int").unwrap_or(String::new()).parse().unwrap_or(-1)
1450    /// }
1451    /// ```
1452    pub fn pluginpref_get(
1453        &self,
1454        var: &str,
1455    ) -> Result<String, PluginPrefError> {
1456        assert!(!self.info.name.is_null(), "must register plugin first");
1457        let var = check_pluginpref_var(var)?;
1458        let mut buffer: [MaybeUninit<c_char>; 512] = unsafe {
1459            MaybeUninit::uninit().assume_init()
1460        };
1461        let success = unsafe {
1462            ph_call!(
1463                hexchat_pluginpref_get_str(
1464                    self,
1465                    var.as_ptr(),
1466                    buffer.as_mut_ptr() as *mut _
1467                )
1468            )
1469        } != 0;
1470        if !success {
1471            Err(PluginPrefError::Failed)
1472        } else {
1473            match unsafe {
1474                CStr::from_ptr(buffer.as_ptr() as *const _)
1475            }.to_owned().into_string() {
1476                Ok(s) => Ok(s),
1477                Err(e) => Err(PluginPrefError::Utf8Error(e)),
1478            }
1479        }
1480    }
1481
1482    /// Removes a pluginpref.
1483    ///
1484    /// # Panics
1485    ///
1486    /// Panics if the plugin is not registered.
1487    ///
1488    /// # Examples
1489    ///
1490    /// ```no_run
1491    /// use hexchat_unsafe_plugin::PluginHandle;
1492    ///
1493    /// fn del_str(ph: &PluginHandle<'_>) {
1494    ///     let _ = ph.pluginpref_delete("string");
1495    /// }
1496    ///
1497    /// fn del_int(ph: &PluginHandle<'_>) {
1498    ///     let _ = ph.pluginpref_delete("int");
1499    /// }
1500    /// ```
1501    pub fn pluginpref_delete(&self, var: &str) -> Result<(), PluginPrefError> {
1502        assert!(!self.info.name.is_null(), "must register plugin first");
1503        let var = check_pluginpref_var(var)?;
1504        let success = unsafe {
1505            ph_call!(hexchat_pluginpref_delete(self, var.as_ptr()))
1506        } != 0;
1507        if !success {
1508            Err(PluginPrefError::Failed)
1509        } else {
1510            Ok(())
1511        }
1512    }
1513
1514    /// Lists pluginprefs.
1515    ///
1516    /// # Panics
1517    ///
1518    /// Panics if the plugin is not registered.
1519    ///
1520    /// # Examples
1521    ///
1522    /// ```no_run
1523    /// use hexchat_unsafe_plugin::PluginHandle;
1524    ///
1525    /// fn list_prefs(ph: &PluginHandle<'_>) {
1526    ///     match ph.pluginpref_list() {
1527    ///         Ok(it) => for pref in &it {
1528    ///             match pref {
1529    ///                 Ok(pref) => write!(ph, "{}", pref),
1530    ///                 _ => (),
1531    ///             }
1532    ///         },
1533    ///         _ => (),
1534    ///     }
1535    /// }
1536    /// ```
1537    pub fn pluginpref_list(&self) -> Result<PluginPrefList, PluginPrefError> {
1538        assert!(!self.info.name.is_null(), "must register plugin first");
1539        let mut buffer: [MaybeUninit<c_char>; 4096] = unsafe {
1540            MaybeUninit::uninit().assume_init()
1541        };
1542        let success = unsafe {
1543            ph_call!(
1544                hexchat_pluginpref_list(self, buffer.as_mut_ptr() as *mut _)
1545            )
1546        } != 0;
1547        if !success {
1548            Err(PluginPrefError::Failed)
1549        } else {
1550            let mut list = PluginPrefList {
1551                inner: unsafe {
1552                    CStr::from_ptr(buffer.as_ptr() as *const _)
1553                }.to_owned().into_bytes(),
1554            };
1555            list.inner.iter_mut().for_each(|x| if *x == b',' { *x = 0; });
1556            Ok(list)
1557        }
1558    }
1559
1560    // ******* //
1561    // PRIVATE //
1562    // ******* //
1563
1564    fn find_valid_context(&self) -> Option<Context<'ph>> {
1565        unsafe {
1566            let channel_key = cstr(b"channels\0");
1567            let context_key = cstr(b"context\0");
1568            // TODO wrap this in a safer API, with proper Drop
1569            #[allow(unused_mut)]
1570            let mut list = ph_call!(hexchat_list_get(self, channel_key));
1571            // hexchat does this thing where it puts a context in a list_str.
1572            // this *is* the proper way to do this, but it looks weird.
1573            let ctx = if ph_call!(hexchat_list_next(self, list)) != 0 {
1574                Some(ph_call!(hexchat_list_str(self, list, context_key)) as *const internals::HexchatContext)
1575            } else {
1576                None
1577            };
1578            ph_call!(hexchat_list_free(self, list));
1579            ctx.map(|ctx| wrap_context(self, ctx))
1580        }
1581    }
1582
1583    /// Same as `set_context` but it takes `&self`.
1584    fn set_context_internal(&self, ctx: &Context<'ph>) -> bool {
1585        if let Some(ctx) = ctx.ctx.upgrade() {
1586            unsafe {
1587                ph_call!(hexchat_set_context(self, *ctx)) != 0
1588            }
1589        } else {
1590            false
1591        }
1592    }
1593}
1594
1595impl<'a> EventAttrs<'a> {
1596    /// Creates a new `EventAttrs`.
1597    #[allow(deprecated)]
1598    pub fn new() -> EventAttrs<'a> {
1599        EventAttrs {
1600            server_time: None,
1601            raw_server_time: 0,
1602            use_raw_server_time: false,
1603            _dummy: PhantomData,
1604        }
1605    }
1606}
1607
1608impl<'a> From<&'a RawAttrs> for EventAttrs<'a> {
1609    #[allow(deprecated)]
1610    fn from(other: &'a RawAttrs) -> EventAttrs<'a> {
1611        EventAttrs {
1612            server_time: if other.server_time_utc > 0 { Some(UNIX_EPOCH + Duration::from_secs(other.server_time_utc as u64)) } else { None },
1613            raw_server_time: other.server_time_utc,
1614            // Defaults to false for API compatibility.
1615            use_raw_server_time: false,
1616            _dummy: PhantomData,
1617        }
1618    }
1619}
1620
1621impl<'a, 'ph: 'a> ValidContext<'a, 'ph> {
1622/*
1623 * These cause UB:
1624 * `hexchat_command` may invalidate the plugin context.
1625 * `hexchat_find_context` and `hexchat_nickcmp` use the plugin context without checking it.
1626 * `hexchat_get_prefs` uses the plugin context if name == "state_cursor" or "id" (or anything with
1627 * the same hash).
1628 * `hexchat_list_get` uses the plugin context if name == "notify" (or anything with the same hash).
1629 * `hexchat_list_str`, `hexchat_list_int`, 
1630 * `hexchat_emit_print`, `hexchat_emit_print_attrs` use the plugin context.
1631 * `hexchat_send_modes` uses the plugin context.
1632 * We need to wrap them (or, alternatively, hexchat_command). However, there's no (safe) way to get
1633 * a valid context afterwards.
1634 * - Actually that's a lie. Hexchat does a trick to give you a context as part of the channel list.
1635 *     We can use that to our advantage. I'm not sure if it's better to wrap hexchat_command or the
1636 *     other functions, tho.
1637 *     (Do we want to walk a linked list every time we use hexchat_command? I'd think
1638 *     hexchat_command is the most used API function... Plus, emit_print could indirectly
1639 *     invalidate the context as well.)
1640 *
1641 * For performance we put them behind an ValidContext - things that don't invalidate the
1642 * context take an `&mut self`, things that do take an `self`.
1643 */
1644
1645    /// Finds an open context for the given servname and channel.
1646    pub fn find_context(&self, servname: Option<&str>, channel: Option<&str>) -> Option<Context<'ph>> {
1647        let ph = &self.ph;
1648        let servname = servname.map(|x| CString::new(x).unwrap());
1649        let channel = channel.map(|x| CString::new(x).unwrap());
1650        let ctx = unsafe {
1651            let sptr = servname.map(|x| x.as_ptr()).unwrap_or(ptr::null());
1652            let cptr = channel.map(|x| x.as_ptr()).unwrap_or(ptr::null());
1653            ph_call!(hexchat_find_context(ph, sptr, cptr))
1654        };
1655        if ctx.is_null() {
1656            None
1657        } else {
1658            Some(unsafe { wrap_context(self.ph, ctx) })
1659        }
1660    }
1661
1662    /// Compares two nicks based on the server's case mapping.
1663    ///
1664    /// ```no_run
1665    /// use hexchat_unsafe_plugin::ValidContext;
1666    ///
1667    /// /// Checks if the two nicks below are equal.
1668    /// fn compare_nicks(context: ValidContext<'_, '_>) -> bool {
1669    ///     context.nickcmp("hello", "HELLO").is_eq()
1670    /// }
1671    /// ```
1672    pub fn nickcmp(&self, nick1: &str, nick2: &str) -> std::cmp::Ordering {
1673        use std::cmp::Ordering;
1674        let ph = &self.ph;
1675        // need to put this in a more permanent position than temporaries
1676        let nick1 = CString::new(nick1).unwrap();
1677        let nick2 = CString::new(nick2).unwrap();
1678        let res = unsafe {
1679            ph_call!(hexchat_nickcmp(ph, nick1.as_ptr(), nick2.as_ptr()))
1680        };
1681        if res < 0 {
1682            Ordering::Less
1683        } else if res > 0 {
1684            Ordering::Greater
1685        } else {
1686            Ordering::Equal
1687        }
1688    }
1689
1690    /// Sends a list of modes to the current channel.
1691    pub fn send_modes<'b, I: IntoIterator<Item=&'b str>>(&mut self, iter: I, mpl: i32, sign: char, mode: char) {
1692        let ph = &self.ph;
1693        assert!(sign == '+' || sign == '-', "sign must be + or -");
1694        assert!(mode.is_ascii(), "mode must be ascii");
1695        assert!(mpl >= 0, "mpl must be non-negative");
1696        let v: Vec<CString> = iter.into_iter().map(|s| CString::new(s).unwrap()).collect();
1697        let mut v2: Vec<*const c_char> = (&v).iter().map(|x| x.as_ptr()).collect();
1698        let arr: &mut [*const c_char] = &mut *v2;
1699        unsafe {
1700            ph_call!(hexchat_send_modes(ph, arr.as_mut_ptr(), arr.len() as c_int,
1701                mpl as c_int, sign as c_char, mode as c_char));
1702        }
1703        // some hexchat forks may invalidate the context here.
1704        // just pretend everything is fine and don't bump the major version.
1705        if !self.set_context(&self.get_context()) {
1706            let context = self.ph.find_valid_context().unwrap();
1707            if !self.set_context(&context) { panic!(); }
1708        }
1709    }
1710
1711    /// Returns a client setting.
1712    ///
1713    /// # Examples
1714    ///
1715    /// ```no_run
1716    /// use hexchat_unsafe_plugin::ValidContext;
1717    ///
1718    /// /// Returns the user's configured main nick.
1719    /// fn main_nick(context: ValidContext<'_, '_>) -> String {
1720    ///     context.get_prefs("irc_nick1").unwrap().into_string().unwrap()
1721    /// }
1722    /// ```
1723    pub fn get_prefs(&self, name: &str) -> Option<PrefValue> {
1724        let ph = &self.ph;
1725        let name = CString::new(name).unwrap();
1726        let mut string = 0 as *const c_char;
1727        let mut int: c_int = 0;
1728        match unsafe {
1729            ph_call!(hexchat_get_prefs(ph, name.as_ptr(), &mut string, &mut int))
1730        } {
1731            0 => None,
1732            1 => Some(PrefValue::String(unsafe {
1733                CStr::from_ptr(string).to_owned().into_string().unwrap()
1734            })),
1735            2 => Some(PrefValue::Int(int as _)),
1736            3 => Some(PrefValue::Bool(int != 0)),
1737            _ => panic!("unsupported type in get_prefs"),
1738        }
1739    }
1740
1741    /// Executes a command.
1742    ///
1743    /// # Examples
1744    ///
1745    /// ```no_run
1746    /// use hexchat_unsafe_plugin::{ValidContext};
1747    ///
1748    /// fn join(context: ValidContext<'_, '_>, channel: &str) {
1749    ///     context.command(&format!("join {}", channel));
1750    /// }
1751    /// ```
1752    pub fn command(self, cmd: &str) {
1753        let ph = self.ph;
1754        // need to put this in a more permanent position than temporaries
1755        let cmd = CString::new(cmd).unwrap();
1756        unsafe {
1757            ph_call!(hexchat_command(ph, cmd.as_ptr()))
1758        }
1759    }
1760
1761    /// Prints an event message, and returns a success status (whether or not
1762    /// the event exists).
1763    ///
1764    /// # Examples
1765    ///
1766    /// ```no_run
1767    /// use hexchat_unsafe_plugin::{ValidContext};
1768    ///
1769    /// fn emit_channel_message(context: ValidContext<'_, '_>, nick: &str, msg: &str) {
1770    ///     context.emit_print("Channel Message", [nick, msg]);
1771    /// }
1772    /// ```
1773    pub fn emit_print<'b, I: IntoIterator<Item=&'b str>>(self, event: &str, args: I) -> bool {
1774        let ph = self.ph;
1775        let event = CString::new(event).unwrap();
1776        let mut args_cs: [Option<CString>; 4] = [None, None, None, None];
1777        {
1778            let mut iter = args.into_iter();
1779            for i in 0..4 {
1780                args_cs[i] = iter.next().map(|x| CString::new(x).unwrap());
1781                if args_cs[i].is_none() {
1782                    break;
1783                }
1784            }
1785            if iter.next().is_some() {
1786                // it's better to panic so as to get bug reports when we need to increase this
1787                panic!("too many arguments to emit_print (max 4), or iterator not fused");
1788            }
1789        }
1790        let mut argv: [*const c_char; 5] = [ptr::null(); 5];
1791        for i in 0..4 {
1792            argv[i] = args_cs[i].as_ref().map_or(ptr::null(), |s| s.as_ptr());
1793        }
1794        unsafe {
1795            ph_call!(hexchat_emit_print(ph, event.as_ptr(), argv[0], argv[1], argv[2], argv[3], argv[4])) != 0
1796        }
1797    }
1798
1799    /// Prints an event message, and returns a success status (whether or not
1800    /// the event exists).
1801    ///
1802    /// Also allows setting event attributes.
1803    ///
1804    /// # Examples
1805    ///
1806    /// ```no_run
1807    /// use std::time::SystemTime;
1808    /// use hexchat_unsafe_plugin::{EventAttrs, ValidContext};
1809    ///
1810    /// fn emit_channel_message_at(context: ValidContext<'_, '_>, time: Option<SystemTime>, nick: &str, msg: &str) {
1811    ///     let mut attrs = EventAttrs::new();
1812    ///     attrs.server_time = time;
1813    ///     context.emit_print_attrs(attrs, "Channel Message", [nick, msg]);
1814    /// }
1815    /// ```
1816    pub fn emit_print_attrs<'b, I: IntoIterator<Item=&'b str>>(self, attrs: EventAttrs, event: &str, args: I) -> bool {
1817        let ph = self.ph;
1818        let event = CString::new(event).unwrap();
1819        let mut args_cs: [Option<CString>; 4] = [None, None, None, None];
1820        {
1821            let mut iter = args.into_iter();
1822            for i in 0..4 {
1823                args_cs[i] = iter.next().map(|x| CString::new(x).unwrap());
1824                if args_cs[i].is_none() {
1825                    break;
1826                }
1827            }
1828            if let Some(_) = iter.next() {
1829                // it's better to panic so as to get bug reports when we need to increase this
1830                panic!("too many arguments to emit_print_attrs (max 4), or iterator not fused");
1831            }
1832        }
1833        let mut argv: [*const c_char; 5] = [ptr::null(); 5];
1834        for i in 0..4 {
1835            argv[i] = args_cs[i].as_ref().map_or(ptr::null(), |s| s.as_ptr());
1836        }
1837        let helper = unsafe { HexchatEventAttrsHelper::new_with(ph, attrs) };
1838        unsafe {
1839            ph_call!(hexchat_emit_print_attrs(ph, helper.0, event.as_ptr(), argv[0], argv[1], argv[2], argv[3], argv[4])) != 0
1840        }
1841    }
1842
1843    /// Retrieves a list.
1844    ///
1845    /// # Examples
1846    ///
1847    /// ```no_run
1848    /// use hexchat_unsafe_plugin::list::Contexts;
1849    /// use hexchat_unsafe_plugin::PluginHandle;
1850    /// 
1851    /// fn print_contexts(ph: &mut PluginHandle<'_>) {
1852    ///     ph.ensure_valid_context(|ph| {
1853    ///         let mut contexts = ph.list(&Contexts);
1854    ///         while let Some(context) = contexts.next() {
1855    ///             write!(ph, "{}", context.name().unwrap());
1856    ///         }
1857    ///     })
1858    /// }
1859    /// ```
1860    pub fn list<T: list::List>(&'a self, t: &'a T) -> list::Entries<'a, 'ph, T> {
1861        let ph = &self.ph;
1862        let list = CString::new(&*t.name()).unwrap();
1863        let list = unsafe {
1864            ph_call!(hexchat_list_get(ph, list.as_ptr()))
1865        };
1866        list::Entries {
1867            context: self.ph,
1868            list: list,
1869            t: t,
1870        }
1871    }
1872
1873    // ******** //
1874    // FORWARDS //
1875    // ******** //
1876    // We can't just deref because then you could recursively ensure valid context and then it'd no
1877    // longer work.
1878
1879    /// Returns the current context.
1880    ///
1881    /// See [`PluginHandle::get_context`].
1882    pub fn get_context(&self) -> Context<'ph> {
1883        self.ph.get_context()
1884    }
1885
1886    /// Sets the current context.
1887    ///
1888    /// Returns `true` if the context is valid, `false` otherwise.
1889    ///
1890    /// See [`PluginHandle::set_context`].
1891    // One would think this is unsound, but if the given context isn't valid,
1892    // the current context is unchanged and still valid.
1893    // But if the given context is valid, it becomes the new, valid, current
1894    // context.
1895    // So either way we're still in a valid context when this returns.
1896    pub fn set_context(&mut self, ctx: &Context<'ph>) -> bool {
1897        self.ph.set_context_internal(ctx)
1898    }
1899
1900    /// Prints to the hexchat buffer.
1901    ///
1902    /// See [`PluginHandle::print`].
1903    pub fn print<T: ToString>(&self, s: T) {
1904        self.ph.print(s)
1905    }
1906
1907    /// Prints to the hexchat buffer.
1908    ///
1909    /// Glue for usage of the [`write!`] macro with hexchat.
1910    ///
1911    /// See [`PluginHandle::write_fmt`].
1912    ///
1913    /// # Panics
1914    ///
1915    /// This panics if any broken formatting trait implementations are used in
1916    /// the format arguments. See also [`format!`].
1917    pub fn write_fmt(&self, fmt: fmt::Arguments<'_>) {
1918        self.ph.write_fmt(fmt)
1919    }
1920
1921    /// Sets a command hook.
1922    ///
1923    /// See [`PluginHandle::hook_command`].
1924    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 {
1925        self.ph.hook_command(cmd, pri, help, cb)
1926    }
1927    /// Sets a server hook.
1928    ///
1929    /// See [`PluginHandle::hook_server`].
1930    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 {
1931        self.ph.hook_server(cmd, pri, cb)
1932    }
1933    /// Sets a server hook with attributes.
1934    ///
1935    /// See [`PluginHandle::hook_server_attrs`].
1936    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 {
1937        self.ph.hook_server_attrs(cmd, pri, cb)
1938    }
1939    /// Sets a print hook.
1940    ///
1941    /// See [`PluginHandle::hook_print`].
1942    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 {
1943        self.ph.hook_print(name, pri, cb)
1944    }
1945    /// Sets a print hook with attributes.
1946    ///
1947    /// See [`PluginHandle::hook_print_attrs`].
1948    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 {
1949        self.ph.hook_print_attrs(name, pri, cb)
1950    }
1951    /// Sets a timer hook.
1952    ///
1953    /// See [`PluginHandle::hook_timer`].
1954    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 {
1955        self.ph.hook_timer(timeout, cb)
1956    }
1957    /// Returns context and client information.
1958    ///
1959    /// See [`PluginHandle::get_info`].
1960    pub fn get_info<'b>(&'b self, id: InfoId) -> Option<Cow<'b, str>> {
1961        self.ph.get_info(id)
1962    }
1963    /// Returns the plugin directory.
1964    ///
1965    /// See [`PluginHandle::get_libdirfs`].
1966    pub fn get_libdirfs(&self) -> Option<CString> {
1967        self.ph.get_libdirfs()
1968    }
1969    /// Strips attributes from text.
1970    ///
1971    /// See [`PluginHandle::strip`].
1972    pub fn strip(&self, s: &str, strip: Strip) -> String {
1973        self.ph.strip(s, strip)
1974    }
1975    /// Sets a pluginpref.
1976    ///
1977    /// See [`PluginHandle::pluginpref_set`].
1978    pub fn pluginpref_set(
1979        &self,
1980        var: &str,
1981        value: &str,
1982    ) -> Result<(), PluginPrefError> {
1983        self.ph.pluginpref_set(var, value)
1984    }
1985    /// Retrieves a pluginpref.
1986    ///
1987    /// See [`PluginHandle::pluginpref_get`].
1988    pub fn pluginpref_get(
1989        &self,
1990        var: &str,
1991    ) -> Result<String, PluginPrefError> {
1992        self.ph.pluginpref_get(var)
1993    }
1994    /// Removes a pluginpref.
1995    ///
1996    /// See [`PluginHandle::pluginpref_delete`].
1997    pub fn pluginpref_delete(&self, var: &str) -> Result<(), PluginPrefError> {
1998        self.ph.pluginpref_delete(var)
1999    }
2000    /// Lists pluginprefs.
2001    ///
2002    /// See [`PluginHandle::pluginpref_list`].
2003    pub fn pluginpref_list(&self) -> Result<PluginPrefList, PluginPrefError> {
2004        self.ph.pluginpref_list()
2005    }
2006}
2007
2008// ******* //
2009// PRIVATE //
2010// ******* //
2011
2012// Type aliases, used for safety type checking.
2013// /// Userdata type used by a command hook.
2014// We actually do want RefUnwindSafe. This function may be called multiple times, and it's not
2015// automatically invalidated if it panics, so it may be called again after it panics. If you need
2016// mutable state, std provides std::sync::Mutex which has poisoning. Other interior mutability with
2017// poisoning could also be used. std doesn't have anything for single-threaded performance (yet),
2018// but hexchat isn't particularly performance-critical.
2019// type CommandHookUd = Box<dyn Fn(Word, WordEol) -> Eat + std::panic::RefUnwindSafe>;
2020// /// Userdata type used by a server hook.
2021// type ServerHookUd = Box<dyn Fn(Word, WordEol, EventAttrs) -> Eat + std::panic::RefUnwindSafe>;
2022// /// Userdata type used by a print hook.
2023// type PrintHookUd = Box<dyn Fn(Word, EventAttrs) -> Eat + std::panic::RefUnwindSafe>;
2024// /// Userdata type used by a timer hook.
2025// type TimerHookUd = Box<dyn Fn() -> bool + std::panic::RefUnwindSafe>;
2026/// Userdata type used by a hook
2027type HookUd<'f> = Box<dyn Fn(*const *const c_char, *const *const c_char, *const RawAttrs) -> Eat + RefUnwindSafe + 'f>;
2028/// Contexts
2029type Contexts = Rc<AssertUnwindSafe<RefCell<HashSet<Rc<*const internals::HexchatContext>>>>>;
2030
2031/// The contents of an empty CStr.
2032///
2033/// This is useful where you need a non-null CStr.
2034// NOTE: MUST BE b"\0"!
2035const EMPTY_CSTRING_DATA: &[u8] = b"\0";
2036
2037/// Converts a nul-terminated const bstring to a C string.
2038///
2039/// # Panics
2040///
2041/// Panics if b contains embedded nuls.
2042// TODO const fn, once that's possible
2043fn cstr(b: &'static [u8]) -> *const c_char {
2044    CStr::from_bytes_with_nul(b).unwrap().as_ptr()
2045}
2046
2047/// Clones an Rc straight from a raw pointer.
2048///
2049/// # Safety
2050///
2051/// This function is unsafe because `ptr` must hame come from `Rc::into_raw`.
2052unsafe fn rc_clone_from_raw<T>(ptr: *const T) -> Rc<T> {
2053    let rc = ManuallyDrop::new(Rc::from_raw(ptr));
2054    Rc::clone(&rc)
2055}
2056
2057/// Converts a **valid** context pointer into a Context (Rust-managed) struct.
2058///
2059/// # Safety
2060///
2061/// This function doesn't validate the context.
2062unsafe fn wrap_context<'ph>(ph: &PluginHandle<'ph>, ctx: *const internals::HexchatContext) -> Context<'ph> {
2063    let contexts = ph.contexts.clone();
2064    if ctx.is_null() {
2065        Context { contexts, ctx: RcWeak::new(), _ph: PhantomData }
2066    } else {
2067        let weak_ctxp = (|| {
2068            // need to drop the borrow(), so use an (|| IIFE)()
2069            contexts.borrow().get(&ctx).map(|x| {
2070                Rc::downgrade(x)
2071            })
2072        })().unwrap_or_else(|| {
2073            let ctxp = Rc::new(ctx);
2074            let weak_ctxp = Rc::downgrade(&ctxp);
2075            contexts.borrow_mut().insert(ctxp);
2076            weak_ctxp
2077        });
2078        Context { contexts, ctx: weak_ctxp, _ph: PhantomData }
2079    }
2080}
2081
2082/// Prints an &str to hexchat, trying to avoid allocations.
2083///
2084/// # Safety
2085///
2086/// This function does not check the passed in argument.
2087///
2088/// # Panics
2089///
2090/// Panics if panic_on_nul is true and the string contains embedded nuls.
2091unsafe fn hexchat_print_str(ph: *mut RawPh, s: &str, panic_on_nul: bool) {
2092    match CString::new(s) {
2093        Result::Ok(cs @ _) => {
2094            let csr: &CStr = &cs;
2095            ((*ph).hexchat_print)(ph, csr.as_ptr())
2096        },
2097        e @ _ => if panic_on_nul {e.unwrap();}, // TODO nul_position?
2098    }
2099}
2100
2101/// Helper to manage owned RawAttrs
2102struct HexchatEventAttrsHelper<'a, 'ph>(*mut RawAttrs, &'a PluginHandle<'ph>) where 'ph: 'a;
2103
2104impl<'a, 'ph> HexchatEventAttrsHelper<'a, 'ph> where 'ph: 'a {
2105    /// Creates a new, empty `HexchatEventAttrsHelper`.
2106    ///
2107    /// # Safety
2108    ///
2109    /// `ph` must be a valid raw plugin handle.
2110    unsafe fn new(ph: &'a PluginHandle<'ph>) -> Self {
2111        HexchatEventAttrsHelper(ph_call!(hexchat_event_attrs_create(ph)), ph)
2112    }
2113
2114    /// Creates a new `HexchatEventAttrsHelper` for a given `EventAttrs`.
2115    ///
2116    /// # Safety
2117    ///
2118    /// `ph` must be a valid raw plugin handle.
2119    unsafe fn new_with(ph: &'a PluginHandle<'ph>, attrs: EventAttrs<'_>) -> Self {
2120        let helper = Self::new(ph);
2121        #[allow(deprecated)]
2122        let v = if attrs.use_raw_server_time {
2123            attrs.raw_server_time
2124        } else {
2125            attrs.server_time.or(Some(UNIX_EPOCH)).map(|st| {
2126                match st.duration_since(UNIX_EPOCH) {
2127                    Ok(n) => n.as_secs(),
2128                    Err(_) => 0
2129                }
2130            }).filter(|&st| {
2131                st < (time_t::max_value() as u64)
2132            }).unwrap() as time_t
2133        };
2134        (*helper.0).server_time_utc = v;
2135        helper
2136    }
2137}
2138
2139impl<'a, 'ph> Drop for HexchatEventAttrsHelper<'a, 'ph> where 'ph: 'a {
2140    fn drop(&mut self) {
2141        unsafe {
2142            ph_call!(hexchat_event_attrs_free(self.1, self.0))
2143        }
2144    }
2145}
2146
2147/// Plugin data stored in the hexchat plugin_handle.
2148struct PhUserdata<'ph> {
2149    plug: Pin<Box<dyn Plugin<'ph> + 'ph>>,
2150    contexts: Contexts,
2151    // this is never read, but we need to not drop it until we can drop it
2152    _context_hook: HookHandle<'ph, 'ph>,
2153    pluginfo: PluginInfo,
2154}
2155
2156/// Puts the userdata in the plugin handle.
2157///
2158/// # Safety
2159///
2160/// This function is unsafe because it doesn't check if the pointer is valid.
2161///
2162/// Improper use of this function can leak memory.
2163unsafe fn put_userdata<'ph>(ph: LtPhPtr<'ph>, ud: Box<PhUserdata<'ph>>) {
2164    (*ph.ph).userdata = Box::into_raw(ud) as *mut c_void;
2165}
2166
2167/// Pops the userdata from the plugin handle.
2168///
2169/// # Safety
2170///
2171/// This function is unsafe because it doesn't check if the pointer is valid.
2172unsafe fn pop_userdata<'ph>(ph: LtPhPtr<'ph>) -> Box<PhUserdata<'ph>> {
2173    Box::from_raw(mem::replace(&mut (*ph.ph).userdata, ptr::null_mut()) as *mut PhUserdata<'ph>)
2174}
2175
2176fn test_pluginpref_var(var: &[u8]) -> Result<(), PluginPrefError> {
2177    if var.len() >= 127
2178    || var.len() < 1
2179    // rust uses the same definition of ascii whitespace
2180    || var.last().unwrap().is_ascii_whitespace()
2181    || var.contains(&b' ')
2182    || var.contains(&b'=')
2183    || var.contains(&b'\n')
2184    || var.contains(&b',') {
2185        Err(PluginPrefError::InvalidVar)
2186    } else {
2187        Ok(())
2188    }
2189}
2190
2191fn check_pluginpref_var(var: impl Into<Vec<u8>>) -> Result<CString, PluginPrefError> {
2192    let var = var.into();
2193    test_pluginpref_var(&var)?;
2194    Ok(CString::new(var).unwrap())
2195}
2196
2197// *********************** //
2198// PUBLIC OUT OF NECESSITY //
2199// *********************** //
2200
2201#[doc(hidden)]
2202pub unsafe fn hexchat_plugin_init<'ph, T>(plugin_handle: LtPhPtr<'ph>,
2203                                     plugin_name: *mut *const c_char,
2204                                     plugin_desc: *mut *const c_char,
2205                                     plugin_version: *mut *const c_char,
2206                                     arg: *const c_char) -> c_int
2207                                     where T: Plugin<'ph> + Default + 'ph {
2208    if plugin_handle.ph.is_null() || plugin_name.is_null() || plugin_desc.is_null() || plugin_version.is_null() {
2209        // we can't really do anything here. just hope this doesn't panic.
2210        eprintln!("hexchat_plugin_init called with a null pointer that shouldn't be null - broken hexchat");
2211        std::process::abort();
2212    }
2213    let ph = plugin_handle.ph as *mut RawPh;
2214    // clear the "userdata" field first thing - if the deinit function gets called (wrong hexchat
2215    // version, other issues), we don't wanna try to drop the hexchat_dummy or hexchat_read_fd
2216    // function as if it were a Box!
2217    (*ph).userdata = ptr::null_mut();
2218    // read the filename so we can pass it on later.
2219    let filename = if !(*plugin_name).is_null() {
2220        if let Ok(fname) = CStr::from_ptr(*plugin_name).to_owned().into_string() {
2221            fname
2222        } else {
2223            hexchat_print_str(ph, "failed to convert filename to utf8 - broken hexchat", false);
2224            return 0;
2225        }
2226    } else {
2227        // no filename specified for some reason, but we can still load
2228        String::new() // empty string
2229    };
2230    // these may be null, unless initialization is successful.
2231    // we set them to null as markers.
2232    *plugin_name = ptr::null();
2233    *plugin_desc = ptr::null();
2234    *plugin_version = ptr::null();
2235    // do some version checks for safety
2236    // NOTE: calling hexchat functions with null plugin_name, plugin_desc, plugin_version is a bit
2237    // dangerous. this particular case is "ok".
2238    {
2239        let ver = ((*ph).hexchat_get_info)(ph, cstr(b"version\0")); // this shouldn't panic
2240        let cstr = CStr::from_ptr(ver);
2241        if let Ok(ver) = cstr.to_str() {
2242            let mut iter = ver.split('.');
2243            let a = iter.next().map(i32::from_str).and_then(Result::ok).unwrap_or(0);
2244            let b = iter.next().map(i32::from_str).and_then(Result::ok).unwrap_or(0);
2245            let c = iter.next().map(i32::from_str).and_then(Result::ok).unwrap_or(0);
2246            // 2.9.6 or greater
2247            if !(a > 2 || (a == 2 && (b > 9 || (b == 9 && (c > 6 || (c == 6)))))) {
2248                return 0;
2249            }
2250        } else {
2251            return 0;
2252        }
2253    }
2254    // we got banned from an IRC network over the following line of code.
2255    hexchat_print_str(ph, "hexchat-unsafe-plugin  Copyright (C) 2018, 2021, 2022 Soni L.  GPL-3.0-or-later  This software is made with love by a queer trans person.", false);
2256    let mut pluginfo = if let Some(pluginfo) = PluginInfo::new(plugin_name, plugin_desc, plugin_version) {
2257        pluginfo
2258    } else {
2259        return 0;
2260    };
2261    let r: thread::Result<Option<Box<_>>> = {
2262        catch_unwind(move || {
2263            // AssertUnwindSafe not Default at the time of writing this
2264            let contexts = Rc::new(AssertUnwindSafe(Default::default()));
2265            let mut pluginhandle = PluginHandle::new(plugin_handle, pluginfo, contexts);
2266            let contexts = Rc::clone(&pluginhandle.contexts);
2267            // must register this before the plugin registers anything else!
2268            let context_hook = pluginhandle.hook_print("Close Context", c_int::min_value(), move |ph, _| {
2269                // just remove the context! it's that simple!
2270                let ctx = ph_call!(hexchat_get_context(ph));
2271                contexts.borrow_mut().remove(&ctx);
2272                EAT_NONE
2273            });
2274            let contexts = Rc::clone(&pluginhandle.contexts);
2275            let mut plug = Box::pin(T::default());
2276            if plug.as_mut().init(&mut pluginhandle, &filename, if !arg.is_null() { Some(CStr::from_ptr(arg).to_str().expect("arg not valid utf-8 - broken hexchat")) } else { None }) {
2277                if !(pluginfo.name.is_null() || pluginfo.desc.is_null() || pluginfo.vers.is_null()) {
2278                    Some(Box::new(PhUserdata { plug, pluginfo, contexts, _context_hook: context_hook }))
2279                } else {
2280                    // TODO log: forgot to call register
2281                    None
2282                }
2283            } else {
2284                if !(pluginfo.name.is_null() || pluginfo.desc.is_null() || pluginfo.vers.is_null()) {
2285                    pluginfo.drop_info()
2286                }
2287                None
2288            }
2289        })
2290    };
2291    match r {
2292        Result::Ok(Option::Some(plug @ _)) => {
2293            put_userdata(plugin_handle, plug);
2294            1
2295        },
2296        r @ _ => {
2297            if let Err(e) = r {
2298                log_panic(ph, e);
2299            }
2300            0
2301        },
2302    }
2303}
2304
2305#[doc(hidden)]
2306pub unsafe fn hexchat_plugin_deinit<'ph, T>(plugin_handle: LtPhPtr<'ph>) -> c_int where T: Plugin<'ph> {
2307    let mut safe_to_unload = 1;
2308    // plugin_handle should never be null, but just in case.
2309    if !plugin_handle.ph.is_null() {
2310        let ph = plugin_handle.ph as *mut RawPh;
2311        // userdata should also never be null.
2312        if !(*ph).userdata.is_null() {
2313            {
2314                let mut info: Option<PluginInfo> = None;
2315                {
2316                    let mut ausinfo = AssertUnwindSafe(&mut info);
2317                    safe_to_unload = match catch_unwind(move || {
2318                        let mut userdata = pop_userdata(plugin_handle);
2319                        let pluginfo = userdata.pluginfo;
2320                        if let Err(e) = catch_unwind(AssertUnwindSafe(|| {
2321                            userdata.plug.as_mut().deinit(&mut {
2322                                PluginHandle::new(
2323                                    plugin_handle,
2324                                    pluginfo,
2325                                    Rc::clone(&userdata.contexts)
2326                                )
2327                            });
2328                        })) {
2329                            // panics in deinit may be retried.
2330                            // however, one may need to close hexchat if that
2331                            // happens.
2332                            put_userdata(plugin_handle, userdata);
2333                            std::panic::resume_unwind(e);
2334                        }
2335                        **ausinfo = Some(pluginfo);
2336                        drop(userdata);
2337                    }) {
2338                        Ok(_) => 1,
2339                        Err(e) => {
2340                            log_panic(ph, e);
2341                            0
2342                        }
2343                    };
2344                }
2345                if let Some(mut info) = info {
2346                    info.drop_info();
2347                    safe_to_unload = 1;
2348                }
2349            }
2350        } else {
2351            hexchat_print_str(ph, "plugin userdata was null, broken hexchat-unsafe-plugin?", false);
2352        }
2353    } else {
2354        // we are once again hoping for the best here.
2355        eprintln!("hexchat_plugin_deinit called with a null plugin_handle - broken hexchat");
2356        std::process::abort();
2357    }
2358    safe_to_unload
2359}
2360
2361/// Exports a hexchat plugin.
2362#[macro_export]
2363macro_rules! hexchat_plugin {
2364    ($l:lifetime, $t:ty) => {
2365        #[no_mangle]
2366        pub unsafe extern "C" fn hexchat_plugin_init<$l>(plugin_handle: $crate::LtPhPtr<$l>,
2367                                              plugin_name: *mut *const $crate::c_char,
2368                                              plugin_desc: *mut *const $crate::c_char,
2369                                              plugin_version: *mut *const $crate::c_char,
2370                                              arg: *const $crate::c_char) -> $crate::c_int {
2371            $crate::hexchat_plugin_init::<$l, $t>(plugin_handle, plugin_name, plugin_desc, plugin_version, arg)
2372        }
2373        #[no_mangle]
2374        pub unsafe extern "C" fn hexchat_plugin_deinit<$l>(plugin_handle: $crate::LtPhPtr<$l>) -> $crate::c_int {
2375            $crate::hexchat_plugin_deinit::<$l, $t>(plugin_handle)
2376        }
2377        // unlike what the documentation states, there's no need to define hexchat_plugin_get_info.
2378        // so we don't. it'd be impossible to make it work well with rust anyway.
2379    };
2380}