hexchat_plugin/
lib.rs

1// Hexchat Plugin API Bindings for Rust
2// Copyright (C) 2018, 2021 Soni L.
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero 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 Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero 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//! # Examples
32//!
33//! ```
34//! #[macro_use]
35//! extern crate hexchat_plugin;
36//!
37//! use std::sync::Mutex;
38//! use hexchat_plugin::{Plugin, PluginHandle, CommandHookHandle};
39//!
40//! #[derive(Default)]
41//! struct MyPlugin {
42//!     cmd: Mutex<Option<CommandHookHandle>>
43//! }
44//!
45//! impl Plugin for MyPlugin {
46//!     fn init(&self, ph: &mut PluginHandle, arg: Option<&str>) -> bool {
47//!         ph.register("myplugin", "0.1.0", "my simple plugin");
48//!         *self.cmd.lock().unwrap() = Some(ph.hook_command("hello-world", |ph, arg, arg_eol| {
49//!             ph.print("Hello, World!");
50//!             hexchat_plugin::EAT_ALL
51//!         }, hexchat_plugin::PRI_NORM, Some("prints 'Hello, World!'")));
52//!         true
53//!     }
54//! }
55//!
56//! hexchat_plugin!(MyPlugin);
57//!
58//! # fn main() { } // satisfy the compiler, we can't actually run the code
59//! ```
60
61/*
62 * Some info about how HexChat does things:
63 *
64 * All strings passed across the C API are UTF-8.
65 * - Except `hexchat_get_info(ph, "libdirfs")`, so we need to be careful with that one.
66 *
67 * The pointers `name: *mut *const char, desc: *mut *const char, vers: *mut *const char` point to
68 * inside the ph - that is, they're aliased. Thus, we must never convert a ph to an & or &mut
69 * except as part of retrieving or setting values in it (e.g. `(*ph).hexchat_get_info` or
70 * `(*ph).userdata = value`).
71 *
72 * `hexchat_plugin_get_info` is never used, so we omit it. It would be impractical not to.
73 *
74 * These cause UB:
75 * `hexchat_command` may invalidate the plugin context.
76 * `hexchat_find_context` and `hexchat_nickcmp` use the plugin context without checking it.
77 * `hexchat_get_prefs` uses the plugin context if name == "state_cursor" or "id" (or anything with
78 * the same hash).
79 * `hexchat_list_get` uses the plugin context if name == "notify" (or anything with the same hash).
80 * `hexchat_list_str`, `hexchat_list_int`, 
81 * `hexchat_emit_print`, `hexchat_emit_print_attrs` use the plugin context.
82 * `hexchat_send_modes` uses the plugin context.
83 * We need to wrap them (or, alternatively, hexchat_command). However, there's no (safe) way to get
84 * a valid context afterwards.
85 * - Actually that's a lie. Hexchat does a trick to give you a context as part of the channel list.
86 *     We can use that to our advantage. I'm not sure if it's better to wrap hexchat_command or the
87 *     other functions, tho.
88 *     (Do we want to walk a linked list every time we use hexchat_command? I'd think
89 *     hexchat_command is the most used API function... Plus, emit_print could indirectly
90 *     invalidate the context as well.)
91 *
92 * `hexchat_send_modes` should only be used with channels; however, it doesn't cause UB - it just
93 * doesn't work.
94 *
95 * `hexchat_pluginpref_get_int`, `hexchat_pluginpref_get_str`, `hexchat_pluginpref_set_int`,
96 * `hexchat_pluginpref_set_str` cannot be used while `name`, `desc`, `vers` are null.
97 *
98 * `hexchat_plugin_init` receives an arg string. it may be null. this isn't documented anywhere.
99 */
100
101/*
102 * Some info about how we do things:
103 *
104 * DO NOT CALL printf/commandf/etc FAMILY OF FUNCTIONS. You can't avoid allocations, so just
105 * allocate some CStrings on the Rust side. It has the exact same effect, since those functions
106 * allocate internally anyway.
107 */
108
109/*
110 * Big list o' TODO:
111 * -[ ] Finish API support. [PRI-HIGH]
112 *     -[x] word
113 *     -[x] word_eol
114 *     -[#] HEXCHAT_PRI_{HIGHEST, HIGH, NORM, LOW, LOWEST}
115 *     -[x] HEXCHAT_EAT_{NONE, HEXCHAT, PLUGIN, ALL}
116 *     -[ ] HEXCHAT_FD_{READ, WRITE, EXCEPTION, NOTSOCKET}
117 *     -[x] hexchat_command (for commandf, use command(&format!("...")), it is equivalent.)
118 *     -[x] hexchat_print (for printf, use print(&format!("...")), it is equivalent.)
119 *     -[x] hexchat_emit_print
120 *     -[x] hexchat_emit_print_attrs
121 *     -[x] hexchat_send_modes
122 *     -[x] hexchat_nickcmp
123 *     -[ ] hexchat_strip
124 *     -[x] ~~hexchat_free~~ not available - use Drop impls.
125 *     -[x] ~~hexchat_event_attrs_create~~ not available - converted as needed
126 *     -[x] ~~hexchat_event_attrs_free~~ not available - use Drop impls.
127 *     -[x] hexchat_get_info
128 *     -[ ] hexchat_get_prefs
129 *     -[ ] hexchat_list_get, hexchat_list_fields, hexchat_list_next, hexchat_list_str,
130 *         hexchat_list_int, hexchat_list_time, hexchat_list_free
131 *     -[x] hexchat_hook_command
132 *     -[ ] hexchat_hook_fd
133 *     -[x] hexchat_hook_print
134 *     -[x] hexchat_hook_print_attrs
135 *     -[#] hexchat_hook_server (implemented through _attrs)
136 *     -[x] hexchat_hook_server_attrs
137 *     -[x] hexchat_hook_timer
138 *     -[x] ~~hexchat_unhook~~ not available - use Drop impls
139 *     -[x] hexchat_find_context
140 *     -[x] hexchat_get_context
141 *     -[x] hexchat_set_context
142 *     -[ ] hexchat_pluginpref_set_str
143 *     -[ ] hexchat_pluginpref_get_str
144 *     -[ ] hexchat_pluginpref_set_int
145 *     -[ ] hexchat_pluginpref_get_int
146 *     -[ ] hexchat_pluginpref_delete
147 *     -[ ] hexchat_pluginpref_list
148 *     -[ ] hexchat_plugingui_add
149 *     -[x] ~~hexchat_plugingui_remove~~ not available - use Drop impls.
150 * -[ ] Wrap contexts within something we keep track of. Mark them invalid when contexts are
151 *     closed. [PRI-MAYBE]
152 * -[x] Anchor closures on the stack using Rc<T>. Many (most?) hooks are reentrant. As far as I
153 *     know, all of them need this.
154 *     -[x] Additionally, use a Cell<bool> for timers.
155 * -[ ] ???
156 */
157
158#[doc(hidden)]
159pub extern crate libc;
160
161mod internals;
162
163use std::borrow::Cow;
164use std::cell::Cell;
165use std::ffi::{CString, CStr};
166use std::marker::PhantomData;
167use std::mem;
168use std::ops;
169use std::panic::catch_unwind;
170use std::ptr;
171use std::rc::Rc;
172use std::rc::Weak as RcWeak;
173use std::str::FromStr;
174use std::thread;
175use std::time::{SystemTime, UNIX_EPOCH, Duration};
176
177// ****** //
178// PUBLIC //
179// ****** //
180
181// Consts
182
183// EAT_*
184/// Equivalent to HEXCHAT_EAT_NONE.
185pub const EAT_NONE: Eat = Eat { do_eat: 0 };
186/// Equivalent to HEXCHAT_EAT_HEXCHAT.
187pub const EAT_HEXCHAT: Eat = Eat { do_eat: 1 };
188/// Equivalent to HEXCHAT_EAT_PLUGIN.
189pub const EAT_PLUGIN: Eat = Eat { do_eat: 2 };
190/// Equivalent to HEXCHAT_EAT_ALL.
191pub const EAT_ALL: Eat = Eat { do_eat: 1 | 2 };
192
193// PRI_*
194/// Equivalent to HEXCHAT_PRI_HIGHEST
195pub const PRI_HIGHEST: i32 = 127;
196/// Equivalent to HEXCHAT_PRI_HIGH
197pub const PRI_HIGH: i32 = 64;
198/// Equivalent to HEXCHAT_PRI_NORM
199pub const PRI_NORM: i32 = 0;
200/// Equivalent to HEXCHAT_PRI_LOW
201pub const PRI_LOW: i32 = -64;
202/// Equivalent to HEXCHAT_PRI_LOWEST
203pub const PRI_LOWEST: i32 = -128;
204
205// Traits
206
207/// A hexchat plugin.
208pub trait Plugin {
209    /// Called to initialize the plugin.
210    fn init(&self, ph: &mut PluginHandle, arg: Option<&str>) -> bool;
211
212    /// Called to deinitialize the plugin.
213    ///
214    /// This is always called immediately prior to Drop::drop.
215    fn deinit(&self, _ph: &mut PluginHandle) {
216    }
217}
218
219// Structs
220
221/// A hexchat plugin handle, used to register hooks and interact with hexchat.
222///
223/// # Examples
224///
225/// ```
226/// use hexchat_plugin::{PluginHandle};
227///
228/// fn init(ph: &mut PluginHandle) {
229///     ph.register("myplug", "0.1.0", "my awesome plug");
230/// }
231/// ```
232pub struct PluginHandle {
233    ph: *mut internals::Ph,
234    skip_pri_ck: bool,
235    // Used for registration
236    info: PluginInfo,
237}
238
239/// Arguments passed to a hook, until the next argument.
240///
241/// # Examples
242/// 
243/// ```
244/// use hexchat_plugin::{PluginHandle, Word, WordEol, Eat};
245///
246/// fn cmd_foo(ph: &mut PluginHandle, arg: Word, arg_eol: WordEol) -> Eat {
247///     if arg.len() < 3 {
248///         ph.print("Not enough arguments");
249///     } else {
250///         ph.print(&format!("{} {} {}", arg[0], arg[1], arg[2]));
251///     }
252///     hexchat_plugin::EAT_ALL
253/// }
254///
255/// fn on_privmsg(ph: &mut PluginHandle, word: Word, word_eol: WordEol) -> Eat {
256///     if word.len() > 0 && word[0].starts_with('@') {
257///         ph.print("We have message tags!?");
258///     }
259///     hexchat_plugin::EAT_NONE
260/// }
261/// ```
262pub struct Word<'a> {
263    word: Vec<&'a str>
264}
265
266/// Arguments passed to a hook, until the end of the line.
267///
268/// # Examples
269/// 
270/// ```
271/// use hexchat_plugin::{PluginHandle, Word, WordEol, Eat};
272///
273/// fn cmd_foo(ph: &mut PluginHandle, arg: Word, arg_eol: WordEol) -> Eat {
274///     if arg.len() < 3 {
275///         ph.print("Not enough arguments");
276///     } else {
277///         ph.print(&format!("{} {} {}", arg[0], arg[1], arg_eol[2]));
278///     }
279///     hexchat_plugin::EAT_ALL
280/// }
281///
282/// fn on_privmsg(ph: &mut PluginHandle, word: Word, word_eol: WordEol) -> Eat {
283///     if word_eol.len() > 0 && word[0].starts_with('@') {
284///         ph.print("We have message tags!?");
285///     }
286///     hexchat_plugin::EAT_NONE
287/// }
288/// ```
289pub struct WordEol<'a> {
290    word_eol: Vec<&'a str>
291}
292
293/// A safety wrapper to ensure you're working with a valid context.
294///
295/// This mechanism attempts to reduce the likelihood of segfaults.
296pub struct EnsureValidContext<'a> {
297    ph: &'a mut PluginHandle,
298}
299
300/// Event attributes.
301#[derive(Clone)]
302pub struct EventAttrs<'a> {
303    /// Server time.
304    pub server_time: Option<SystemTime>,
305    _dummy: PhantomData<&'a ()>,
306}
307
308/// A status indicator for event callbacks. Indicates whether to continue processing, eat hexchat,
309/// eat plugin, or eat all.
310///
311/// Returned by most hooks.
312#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
313pub struct Eat {
314    do_eat: i32,
315}
316
317/// A command hook handle.
318#[must_use = "Hooks must be stored somewhere and are automatically unhooked on Drop"]
319pub struct CommandHookHandle {
320    ph: *mut internals::Ph,
321    hh: *const internals::HexchatHook,
322    // this does actually store an Rc<...>, but on the other side of the FFI.
323    _f: PhantomData<Rc<CommandHookUd>>,
324}
325
326/// A server hook handle.
327#[must_use = "Hooks must be stored somewhere and are automatically unhooked on Drop"]
328pub struct ServerHookHandle {
329    ph: *mut internals::Ph,
330    hh: *const internals::HexchatHook,
331    // this does actually store an Rc<...>, but on the other side of the FFI.
332    _f: PhantomData<Rc<ServerHookUd>>,
333}
334
335/// A print hook handle.
336#[must_use = "Hooks must be stored somewhere and are automatically unhooked on Drop"]
337pub struct PrintHookHandle {
338    ph: *mut internals::Ph,
339    hh: *const internals::HexchatHook,
340    // this does actually store an Rc<...>, but on the other side of the FFI.
341    _f: PhantomData<Rc<PrintHookUd>>,
342}
343
344/// A timer hook handle.
345#[must_use = "Hooks must be stored somewhere and are automatically unhooked on Drop"]
346pub struct TimerHookHandle {
347    ph: *mut internals::Ph,
348    hh: *const internals::HexchatHook,
349    // avoids issues
350    alive: Rc<Cell<bool>>,
351    // this does actually store an Rc<...>, but on the other side of the FFI.
352    _f: PhantomData<Rc<TimerHookUd>>,
353}
354
355/// A context.
356#[derive(Clone)]
357pub struct Context {
358    ctx: RcWeak<*const internals::HexchatContext>, // may be null
359    closure: Rc<Cell<Option<PrintHookHandle>>>,
360}
361
362// #[derive(Debug)] // doesn't work
363pub struct InvalidContextError<F: FnOnce(EnsureValidContext) -> R, R>(F);
364
365// Enums
366
367/// A hexchat_get_info key.
368#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Copy, Clone)]
369pub enum InfoId<'a> {
370    /// Returns the away message, or `None` if the user is not away.
371    Away,
372    /// Returns the current channel name.
373    Channel,
374    /// Returns the current charset.
375    Charset,
376    /// Returns the hexchat configuration directory, e.g. `/home/user/.config/hexchat`.
377    Configdir,
378    /// Returns the text event format string for the given text event name.
379    EventText(&'a str),
380    /// Returns the (real) hostname of the current server.
381    Host,
382    /// Returns the contents of the input box.
383    Inputbox,
384    /// Returns the library directory, e.g. `/usr/lib/hexchat`.
385    ///
386    /// May not always work, as this string isn't necessarily UTF-8, but local file system
387    /// encoding.
388    Libdirfs,
389    /// Returns the channel modes, if known, or `None`.
390    Modes,
391    /// Returns the current network name, or `None`.
392    Network,
393    /// Returns the user's current nick.
394    Nick,
395    /// Returns the user's nickserv password, if any, or `None`
396    Nickserv,
397    /// Returns the current server name, or `None` if you are not connected.
398    Server,
399    /// Returns the current channel topic.
400    Topic,
401    /// Returns the HexChat version string.
402    Version,
403    /// Returns the window status: "active", "hidden" or "normal".
404    WinStatus,
405}
406
407// ***** //
408// impls //
409// ***** //
410
411impl<'a> InfoId<'a> {
412    pub fn name(&self) -> Cow<'static, str> {
413        match *self {
414            InfoId::EventText(s) => {
415                let mut eventtext: String = "event_text ".into();
416                eventtext.push_str(&s);
417                eventtext.into()
418            },
419            InfoId::Away      => "away".into(),
420            InfoId::Channel   => "channel".into(),
421            InfoId::Charset   => "charset".into(),
422            InfoId::Configdir => "configdir".into(),
423            InfoId::Host      => "host".into(),
424            InfoId::Inputbox  => "inputbox".into(),
425            InfoId::Libdirfs  => "libdirfs".into(),
426            InfoId::Modes     => "modes".into(),
427            InfoId::Network   => "network".into(),
428            InfoId::Nick      => "nick".into(),
429            InfoId::Nickserv  => "nickserv".into(),
430            InfoId::Server    => "server".into(),
431            InfoId::Topic     => "topic".into(),
432            InfoId::Version   => "version".into(),
433            InfoId::WinStatus => "win_status".into(),
434        }
435    }
436}
437
438impl<F: FnOnce(EnsureValidContext) -> R, R> InvalidContextError<F, R> {
439    pub fn get_closure(self) -> F {
440        self.0
441    }
442}
443
444impl Drop for CommandHookHandle {
445    fn drop(&mut self) {
446        unsafe {
447            let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut CommandHookUd;
448            // we assume b is not null. this should be safe.
449            // just drop it
450            mem::drop(Rc::from_raw(b));
451        }
452    }
453}
454impl Drop for ServerHookHandle {
455    fn drop(&mut self) {
456        unsafe {
457            let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut ServerHookUd;
458            // we assume b is not null. this should be safe.
459            // just drop it
460            mem::drop(Rc::from_raw(b));
461        }
462    }
463}
464impl Drop for PrintHookHandle {
465    fn drop(&mut self) {
466        unsafe {
467            let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut PrintHookUd;
468            // we assume b is not null. this should be safe.
469            // just drop it
470            mem::drop(Rc::from_raw(b));
471        }
472    }
473}
474impl Drop for TimerHookHandle {
475    fn drop(&mut self) {
476        if !self.alive.get() {
477            // already free'd.
478            return;
479        }
480        self.alive.set(false);
481        unsafe {
482            let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut TimerHookUd;
483            // we assume b is not null. this should be safe.
484            // just drop it
485            mem::drop(Rc::from_raw(b));
486        }
487    }
488}
489
490impl<'a> Word<'a> {
491    unsafe fn new(word: *const *const libc::c_char) -> Word<'a> {
492        let mut vec = vec![];
493        for i in 1..32 {
494            let w = *word.offset(i);
495            if !w.is_null() {
496                vec.push(CStr::from_ptr(w).to_str().expect("non-utf8 word - broken hexchat"))
497            }
498        }
499        while let Some(&"") = vec.last() {
500            vec.pop();
501        }
502        Word { word: vec }
503    }
504}
505
506impl<'a> ops::Deref for Word<'a> {
507    type Target = [&'a str];
508    fn deref(&self) -> &[&'a str] {
509        &self.word
510    }
511}
512
513impl<'a> WordEol<'a> {
514    unsafe fn new(word_eol: *const *const libc::c_char) -> WordEol<'a> {
515        let mut vec = vec![];
516        for i in 1..32 {
517            let w = *word_eol.offset(i);
518            if !w.is_null() {
519                vec.push(CStr::from_ptr(w).to_str().expect("non-utf8 word_eol - broken hexchat"))
520            }
521        }
522        while let Some(&"") = vec.last() {
523            vec.pop();
524        }
525        WordEol { word_eol: vec }
526    }
527}
528
529impl<'a> ops::Deref for WordEol<'a> {
530    type Target = [&'a str];
531    fn deref(&self) -> &[&'a str] {
532        &self.word_eol
533    }
534}
535
536impl PluginHandle {
537    fn new(ph: *mut internals::Ph, info: PluginInfo) -> PluginHandle {
538        PluginHandle {
539            ph, info, skip_pri_ck: false,
540        }
541    }
542
543    /// Registers this hexchat plugin. This must be called exactly once when the plugin is loaded.
544    ///
545    /// # Panics
546    ///
547    /// This function panics if this plugin is already registered.
548    ///
549    /// # Examples
550    ///
551    /// ```
552    /// use hexchat_plugin::PluginHandle;
553    ///
554    /// fn init(ph: &mut PluginHandle) {
555    ///     ph.register("foo", "0.1.0", "my foo plugin");
556    /// }
557    /// ```
558    pub fn register(&mut self, name: &str, desc: &str, ver: &str) {
559        unsafe {
560            let info = self.info;
561            if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
562                panic!("Attempt to re-register a plugin");
563            }
564            let name = CString::new(name).unwrap();
565            let desc = CString::new(desc).unwrap();
566            let ver = CString::new(ver).unwrap();
567            // these shouldn't panic. if they do, we'll need to free them afterwards.
568            (*info.name) = name.into_raw();
569            (*info.desc) = desc.into_raw();
570            (*info.vers) = ver.into_raw();
571        }
572    }
573
574    /// Returns this plugin's registered name.
575    ///
576    /// # Panics
577    ///
578    /// This function panics if this plugin is not registered.
579    pub fn get_name(&self) -> &str {
580        unsafe {
581            let info = self.info;
582            if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
583                std::str::from_utf8_unchecked(CStr::from_ptr(*info.name).to_bytes())
584            } else {
585                panic!("Attempt to get the name of a plugin that was not yet registered.");
586            }
587        }
588    }
589
590    /// Returns this plugin's registered description.
591    ///
592    /// # Panics
593    ///
594    /// This function panics if this plugin is not registered.
595    pub fn get_description(&self) -> &str {
596        unsafe {
597            let info = self.info;
598            if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
599                std::str::from_utf8_unchecked(CStr::from_ptr(*info.desc).to_bytes())
600            } else {
601                panic!("Attempt to get the description of a plugin that was not yet registered.");
602            }
603        }
604    }
605
606    /// Returns this plugin's registered version.
607    ///
608    /// # Panics
609    ///
610    /// This function panics if this plugin is not registered.
611    pub fn get_version(&self) -> &str {
612        unsafe {
613            let info = self.info;
614            if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
615                std::str::from_utf8_unchecked(CStr::from_ptr(*info.vers).to_bytes())
616            } else {
617                panic!("Attempt to get the version of a plugin that was not yet registered.");
618            }
619        }
620    }
621
622    /// Ensures the current context is valid.
623    ///
624    /// # Panics
625    ///
626    /// This function may panic if it's called while hexchat is closing.
627    // NOTE: using a closure is nicer.
628    // TODO check if this is actually safe
629    pub fn ensure_valid_context<F, R>(&mut self, f: F) -> R where F: FnOnce(EnsureValidContext) -> R {
630        let ctx = self.get_context();
631        // need this here because we don't have NLL yet
632        let res = self.with_context(&ctx, f);
633        match res {
634            Result::Ok(r @ _) => r,
635            Result::Err(e @ _) => {
636                let nctx = self.find_valid_context().expect("ensure_valid_context failed (find_valid_context failed), was hexchat closing?");
637                self.with_context(&nctx, e.get_closure()).ok().expect("ensure_valid_context failed, was hexchat closing?")
638            }
639        }
640    }
641
642    /// Returns the current context.
643    ///
644    /// Note: The returned context may be invalid. Use [`set_context`] to check.
645    ///
646    /// [`set_context`]: #method.set_context
647    pub fn get_context(&mut self) -> Context {
648        let ctxp = unsafe { ((*self.ph).hexchat_get_context)(self.ph) };
649        // This needs to be fixed by hexchat. I cannot make the value become null when it's invalid
650        // without invoking UB. This is because I can't set_context to null.
651        let ok = unsafe { ((*self.ph).hexchat_set_context)(self.ph, ctxp) };
652        unsafe { wrap_context(self, if ok == 0 { ptr::null() } else { ctxp }) }
653    }
654
655    /// Sets the current context.
656    ///
657    /// Returns `true` if the context is valid, `false` otherwise.
658    pub fn set_context(&mut self, ctx: &Context) -> bool {
659        if let Some(ctx) = ctx.ctx.upgrade() {
660            unsafe {
661                ((*self.ph).hexchat_set_context)(self.ph, *ctx) != 0
662            }
663        } else {
664            false
665        }
666    }
667
668    /// Do something in a valid context.
669    ///
670    /// Note that this changes the active context and doesn't change it back.
671    ///
672    /// # Errors
673    ///
674    /// Returns `Err(InvalidContextError(f))` if the context is invalid. See [`set_context`]. Otherwise,
675    /// calls `f` and returns `Ok(its result)`.
676    ///
677    /// Note that `InvalidContextError` contains the original closure. This allows you to retry.
678    ///
679    /// [`set_context`]: #method.set_context
680    // this is probably safe to inline, and actually a good idea for ensure_valid_context
681    #[inline]
682    pub fn with_context<F, R>(&mut self, ctx: &Context, f: F) -> Result<R, InvalidContextError<F, R>> where F: FnOnce(EnsureValidContext) -> R {
683        if !self.set_context(ctx) {
684            Err(InvalidContextError(f))
685        } else {
686            Ok(f(EnsureValidContext { ph: self }))
687        }
688    }
689
690    /// Sets a command hook.
691    ///
692    /// # Examples
693    ///
694    /// ```
695    /// use hexchat_plugin::{PluginHandle, CommandHookHandle};
696    ///
697    /// fn register_commands(ph: &mut PluginHandle) -> Vec<CommandHookHandle> {
698    ///     vec![
699    ///     ph.hook_command("hello-world", |ph, arg, arg_eol| {
700    ///         ph.print("Hello, World!");
701    ///         hexchat_plugin::EAT_ALL
702    ///     }, hexchat_plugin::PRI_NORM, Some("prints 'Hello, World!'")),
703    ///     ]
704    /// }
705    /// ```
706    pub fn hook_command<F>(&mut self, cmd: &str, cb: F, pri: i32, help: Option<&str>) -> CommandHookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + ::std::panic::RefUnwindSafe {
707        unsafe extern "C" fn callback(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, ud: *mut libc::c_void) -> libc::c_int {
708            // hook may unhook itself.
709            // however, we don't wanna free it until it has returned.
710            let f: Rc<CommandHookUd> = rc_clone_from_raw(ud as *const CommandHookUd);
711            let ph = f.1;
712            match catch_unwind(move || {
713                let word = Word::new(word);
714                let word_eol = WordEol::new(word_eol);
715                (f.0)(&mut PluginHandle::new(f.1, f.2), word, word_eol).do_eat as libc::c_int
716            }) {
717                Result::Ok(v @ _) => v,
718                Result::Err(e @ _) => {
719                    // if it's a &str or String, just print it
720                    if let Some(estr) = e.downcast_ref::<&str>() {
721                        hexchat_print_str(ph, estr, false);
722                    } else if let Some(estring) = e.downcast_ref::<String>() {
723                        hexchat_print_str(ph, &estring, false);
724                    }
725                    0 // EAT_NONE
726                }
727            }
728        }
729        let b: Rc<CommandHookUd> = Rc::new((Box::new(cb), self.ph, self.info));
730        let name = CString::new(cmd).unwrap();
731        let help_text = help.map(CString::new).map(Result::unwrap);
732        let bp = Rc::into_raw(b);
733        unsafe {
734            let res = ((*self.ph).hexchat_hook_command)(self.ph, name.as_ptr(), pri as libc::c_int, callback, help_text.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()), bp as *mut _);
735            assert!(!res.is_null());
736            CommandHookHandle { ph: self.ph, hh: res, _f: PhantomData }
737        }
738    }
739    /// Sets a server hook.
740    ///
741    /// # Examples
742    ///
743    /// ```
744    /// use hexchat_plugin::{PluginHandle, ServerHookHandle};
745    ///
746    /// fn register_server_hooks(ph: &mut PluginHandle) -> Vec<ServerHookHandle> {
747    ///     vec![
748    ///     ph.hook_server("PRIVMSG", |ph, word, word_eol| {
749    ///         if word.len() > 0 && word[0].starts_with('@') {
750    ///             ph.print("We have message tags!?");
751    ///         }
752    ///         hexchat_plugin::EAT_NONE
753    ///     }, hexchat_plugin::PRI_NORM),
754    ///     ]
755    /// }
756    /// ```
757    pub fn hook_server<F>(&mut self, cmd: &str, cb: F, pri: i32) -> ServerHookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + ::std::panic::RefUnwindSafe {
758        self.hook_server_attrs(cmd, move |ph, w, we, _| cb(ph, w, we), pri)
759    }
760    /// Sets a server hook, with attributes.
761    pub fn hook_server_attrs<F>(&mut self, cmd: &str, cb: F, pri: i32) -> ServerHookHandle where F: Fn(&mut PluginHandle, Word, WordEol, EventAttrs) -> Eat + 'static + ::std::panic::RefUnwindSafe {
762        unsafe extern "C" fn callback(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, attrs: *const internals::HexchatEventAttrs, ud: *mut libc::c_void) -> libc::c_int {
763            // hook may unhook itself.
764            // however, we don't wanna free it until it has returned.
765            let f: Rc<ServerHookUd> = rc_clone_from_raw(ud as *const ServerHookUd);
766            let ph = f.1;
767            match catch_unwind(move || {
768                let word = Word::new(word);
769                let word_eol = WordEol::new(word_eol);
770                (f.0)(&mut PluginHandle::new(f.1, f.2), word, word_eol, (&*attrs).into()).do_eat as libc::c_int
771            }) {
772                Result::Ok(v @ _) => v,
773                Result::Err(e @ _) => {
774                    // if it's a &str or String, just print it
775                    if let Some(estr) = e.downcast_ref::<&str>() {
776                        hexchat_print_str(ph, estr, false);
777                    } else if let Some(estring) = e.downcast_ref::<String>() {
778                        hexchat_print_str(ph, &estring, false);
779                    }
780                    0 // EAT_NONE
781                }
782            }
783        }
784        let b: Rc<ServerHookUd> = Rc::new((Box::new(cb), self.ph, self.info));
785        let name = CString::new(cmd).unwrap();
786        let bp = Rc::into_raw(b);
787        unsafe {
788            let res = ((*self.ph).hexchat_hook_server_attrs)(self.ph, name.as_ptr(), pri as libc::c_int, callback, bp as *mut _);
789            assert!(!res.is_null());
790            ServerHookHandle { ph: self.ph, hh: res, _f: PhantomData }
791        }
792    }
793    /// Sets a print hook.
794    ///
795    /// # Examples
796    ///
797    /// ```
798    /// use hexchat_plugin::{PluginHandle, PrintHookHandle};
799    ///
800    /// fn register_print_hooks(ph: &mut PluginHandle) -> Vec<PrintHookHandle> {
801    ///     vec![
802    ///     ph.hook_print("Channel Message", |ph, arg| {
803    ///         if let Some(nick) = arg.get(0) {
804    ///             if *nick == "KnOwN_SpAmMeR" {
805    ///                 return hexchat_plugin::EAT_ALL
806    ///             }
807    ///         }
808    ///         hexchat_plugin::EAT_NONE
809    ///     }, hexchat_plugin::PRI_NORM),
810    ///     ]
811    /// }
812    /// ```
813    pub fn hook_print<F>(&mut self, name: &str, cb: F, mut pri: i32) -> PrintHookHandle where F: Fn(&mut PluginHandle, Word) -> Eat + 'static + ::std::panic::RefUnwindSafe {
814        // hmm, is there any way to avoid this code duplication?
815        // hook_print is special because dummy prints (keypresses, Close Context) are handled
816        // through here, but never through hook_print_attrs. :/
817        unsafe extern "C" fn callback(word: *const *const libc::c_char, ud: *mut libc::c_void) -> libc::c_int {
818            // hook may unhook itself.
819            // however, we don't wanna free it until it has returned.
820            let f: Rc<PrintHookUd> = rc_clone_from_raw(ud as *const PrintHookUd);
821            let ph = f.1;
822            match catch_unwind(move || {
823                let word = Word::new(word);
824                (f.0)(&mut PluginHandle::new(f.1, f.2), word, EventAttrs::new()).do_eat as libc::c_int
825            }) {
826                Result::Ok(v @ _) => v,
827                Result::Err(e @ _) => {
828                    // if it's a &str or String, just print it
829                    if let Some(estr) = e.downcast_ref::<&str>() {
830                        hexchat_print_str(ph, estr, false);
831                    } else if let Some(estring) = e.downcast_ref::<String>() {
832                        hexchat_print_str(ph, &estring, false);
833                    }
834                    0 // EAT_NONE
835                }
836            }
837        }
838        if name == "Close Context" && (pri as libc::c_int == libc::c_int::min_value()) && !self.skip_pri_ck {
839            self.print("Warning: Attempted to hook Close Context with priority INT_MIN. Adjusting to INT_MIN+1.");
840            pri = (libc::c_int::min_value() + 1) as i32;
841        }
842        let b: Rc<PrintHookUd> = Rc::new((Box::new(move |ph, w, _| cb(ph, w)), self.ph, self.info));
843        let name = CString::new(name).unwrap();
844        let bp = Rc::into_raw(b);
845        unsafe {
846            let res = ((*self.ph).hexchat_hook_print)(self.ph, name.as_ptr(), pri as libc::c_int, callback, bp as *mut _);
847            assert!(!res.is_null());
848            PrintHookHandle { ph: self.ph, hh: res, _f: PhantomData }
849        }
850    }
851    /// Sets a print hook, with attributes.
852    pub fn hook_print_attrs<F>(&mut self, name: &str, cb: F, pri: i32) -> PrintHookHandle where F: Fn(&mut PluginHandle, Word, EventAttrs) -> Eat + 'static + ::std::panic::RefUnwindSafe {
853        unsafe extern "C" fn callback(word: *const *const libc::c_char, attrs: *const internals::HexchatEventAttrs, ud: *mut libc::c_void) -> libc::c_int {
854            // hook may unhook itself.
855            // however, we don't wanna free it until it has returned.
856            let f: Rc<PrintHookUd> = rc_clone_from_raw(ud as *const PrintHookUd);
857            let ph = f.1;
858            match catch_unwind(move || {
859                let word = Word::new(word);
860                (f.0)(&mut PluginHandle::new(f.1, f.2), word, (&*attrs).into()).do_eat as libc::c_int
861            }) {
862                Result::Ok(v @ _) => v,
863                Result::Err(e @ _) => {
864                    // if it's a &str or String, just print it
865                    if let Some(estr) = e.downcast_ref::<&str>() {
866                        hexchat_print_str(ph, estr, false);
867                    } else if let Some(estring) = e.downcast_ref::<String>() {
868                        hexchat_print_str(ph, &estring, false);
869                    }
870                    0 // EAT_NONE
871                }
872            }
873        }
874        let b: Rc<PrintHookUd> = Rc::new((Box::new(cb), self.ph, self.info));
875        let name = CString::new(name).unwrap();
876        let bp = Rc::into_raw(b);
877        unsafe {
878            let res = ((*self.ph).hexchat_hook_print_attrs)(self.ph, name.as_ptr(), pri as libc::c_int, callback, bp as *mut _);
879            assert!(!res.is_null());
880            PrintHookHandle { ph: self.ph, hh: res, _f: PhantomData }
881        }
882    }
883    /// Sets a timer hook
884    ///
885    /// # Examples
886    ///
887    /// ```
888    /// use hexchat_plugin::{PluginHandle, TimerHookHandle};
889    ///
890    /// fn register_timers(ph: &mut PluginHandle) -> Vec<TimerHookHandle> {
891    ///     vec![
892    ///     ph.hook_timer(2000, |ph| {
893    ///         ph.print("timer up!");
894    ///         false
895    ///     }),
896    ///     ]
897    /// }
898    /// ```
899    pub fn hook_timer<F>(&mut self, timeout: i32, cb: F) -> TimerHookHandle where F: Fn(&mut PluginHandle) -> bool + 'static + ::std::panic::RefUnwindSafe {
900        unsafe extern "C" fn callback(ud: *mut libc::c_void) -> libc::c_int {
901            // hook may unhook itself.
902            // however, we don't wanna free it until it has returned.
903            let f: Rc<TimerHookUd> = rc_clone_from_raw(ud as *const TimerHookUd);
904            let alive = f.1.clone(); // clone the alive because why not
905            let f = f.0.clone();
906            let ph = f.1;
907            // we could technically free the Rc<TimerHookUd> here, I guess
908            match catch_unwind(move || {
909                (f.0)(&mut PluginHandle::new(f.1, f.2))
910            }) {
911                Result::Ok(true) => 1,
912                Result::Ok(false) => {
913                    // avoid double-free
914                    if !alive.get() {
915                        return 0;
916                    }
917                    // mark it no longer alive
918                    alive.set(false);
919                    // HexChat will automatically free the hook.
920                    // we just need to free the userdata.
921                    mem::drop(Rc::from_raw(ud as *const TimerHookUd));
922                    0
923                },
924                Result::Err(e @ _) => {
925                    // if it's a &str or String, just print it
926                    if let Some(estr) = e.downcast_ref::<&str>() {
927                        hexchat_print_str(ph, estr, false);
928                    } else if let Some(estring) = e.downcast_ref::<String>() {
929                        hexchat_print_str(ph, &estring, false);
930                    }
931                    // avoid double-free
932                    if !alive.get() {
933                        return 0;
934                    }
935                    // mark it no longer alive
936                    alive.set(false);
937                    // HexChat will automatically free the hook.
938                    // we just need to free the userdata.
939                    mem::drop(Rc::from_raw(ud as *const TimerHookUd));
940                    0
941                }
942            }
943        }
944        let alive = Rc::new(Cell::new(true));
945        let b: Rc<TimerHookUd> = Rc::new((Rc::new((Box::new(cb), self.ph, self.info)), alive.clone()));
946        let bp = Rc::into_raw(b);
947        unsafe {
948            let res = ((*self.ph).hexchat_hook_timer)(self.ph, timeout as libc::c_int, callback, bp as *mut _);
949            assert!(!res.is_null());
950            TimerHookHandle { ph: self.ph, hh: res, alive, _f: PhantomData }
951        }
952    }
953
954    /// Prints to the hexchat buffer.
955    // this checks the context internally. if it didn't, it wouldn't be safe to have here.
956    pub fn print<T: ToString>(&mut self, s: T) {
957        let s = s.to_string();
958        unsafe {
959            hexchat_print_str(self.ph, &*s, true);
960        }
961    }
962
963    /// Returns information on the current context.
964    ///
965    /// Note: `InfoId::Libdirfs` may return `None` or broken results if the result wouldn't be (valid) UTF-8.
966    // TODO this should be `id: InfoId`. fix in 0.3
967    pub fn get_info(&mut self, id: &InfoId) -> Option<String> {
968        let ph = self.ph;
969        let id_cstring = CString::new(&*id.name()).unwrap();
970        unsafe {
971            let res = ((*ph).hexchat_get_info)(ph, id_cstring.as_ptr());
972            if res.is_null() {
973                None
974            } else {
975                let s = CStr::from_ptr(res).to_owned().into_string();
976                if *id != InfoId::Libdirfs {
977                    Some(s.expect("non-utf8 word - broken hexchat"))
978                } else {
979                    s.ok()
980                }
981            }
982        }
983    }
984
985    // ******* //
986    // PRIVATE //
987    // ******* //
988
989    fn find_valid_context(&mut self) -> Option<Context> {
990        unsafe {
991            let ph = self.ph;
992            // TODO wrap this in a safer API, with proper Drop
993            #[allow(unused_mut)]
994            let mut list = ((*ph).hexchat_list_get)(ph, cstr(b"channels\0"));
995            // hexchat does this thing where it puts a context in a list_str.
996            // this *is* the proper way to do this
997            if ((*ph).hexchat_list_next)(ph, list) != 0 {
998                // if this panics we may leak some memory. it's not a big deal tho, as it indicates
999                // a bug in hexchat-plugin.rs.
1000                let ctx = ((*ph).hexchat_list_str)(ph, list, cstr(b"context\0")) as *const internals::HexchatContext;
1001                ((*ph).hexchat_list_free)(ph, list);
1002                Some(wrap_context(self, ctx))
1003            } else {
1004                ((*ph).hexchat_list_free)(ph, list);
1005                None
1006            }
1007        }
1008    }
1009}
1010
1011impl<'a> EventAttrs<'a> {
1012    pub fn new() -> EventAttrs<'a> {
1013        EventAttrs {
1014            server_time: None,
1015            _dummy: PhantomData,
1016        }
1017    }
1018}
1019
1020impl<'a> From<&'a internals::HexchatEventAttrs> for EventAttrs<'a> {
1021    fn from(other: &'a internals::HexchatEventAttrs) -> EventAttrs<'a> {
1022        EventAttrs {
1023            server_time: if other.server_time_utc > 0 { Some(UNIX_EPOCH + Duration::from_secs(other.server_time_utc as u64)) } else { None },
1024            _dummy: PhantomData,
1025        }
1026    }
1027}
1028
1029impl<'a> EnsureValidContext<'a> {
1030/*
1031 * These cause UB:
1032 * `hexchat_command` may invalidate the plugin context.
1033 * `hexchat_find_context` and `hexchat_nickcmp` use the plugin context without checking it.
1034 * `hexchat_get_prefs` uses the plugin context if name == "state_cursor" or "id" (or anything with
1035 * the same hash).
1036 * `hexchat_list_get` uses the plugin context if name == "notify" (or anything with the same hash).
1037 * `hexchat_list_str`, `hexchat_list_int`, 
1038 * `hexchat_emit_print`, `hexchat_emit_print_attrs` use the plugin context.
1039 * `hexchat_send_modes` uses the plugin context.
1040 * We need to wrap them (or, alternatively, hexchat_command). However, there's no (safe) way to get
1041 * a valid context afterwards.
1042 * - Actually that's a lie. Hexchat does a trick to give you a context as part of the channel list.
1043 *     We can use that to our advantage. I'm not sure if it's better to wrap hexchat_command or the
1044 *     other functions, tho.
1045 *     (Do we want to walk a linked list every time we use hexchat_command? I'd think
1046 *     hexchat_command is the most used API function... Plus, emit_print could indirectly
1047 *     invalidate the context as well.)
1048 *
1049 * For performance we put them behind an EnsureValidContext - things that don't invalidate the
1050 * context take an `&mut self`, things that do take an `self`.
1051 */
1052
1053    /// Finds an open context for the given servname and channel.
1054    pub fn find_context(&mut self, servname: Option<&str>, channel: Option<&str>) -> Option<Context> {
1055        // this was a mistake but oh well
1056        let ph = self.ph.ph;
1057        let servname = servname.map(|x| CString::new(x).unwrap());
1058        let channel = channel.map(|x| CString::new(x).unwrap());
1059        let ctx = unsafe {
1060            let sptr = servname.map(|x| x.as_ptr()).unwrap_or(ptr::null());
1061            let cptr = channel.map(|x| x.as_ptr()).unwrap_or(ptr::null());
1062            ((*ph).hexchat_find_context)(ph, sptr, cptr)
1063        };
1064        if ctx.is_null() {
1065            None
1066        } else {
1067            Some(unsafe { wrap_context(self.ph, ctx) })
1068        }
1069    }
1070
1071    /// Compares two nicks based on the server's case mapping.
1072    pub fn nickcmp(&mut self, nick1: &str, nick2: &str) -> ::std::cmp::Ordering {
1073        use std::cmp::Ordering;
1074        // this was a mistake but oh well
1075        let ph = self.ph.ph;
1076        // need to put this in a more permanent position than temporaries
1077        let nick1 = CString::new(nick1).unwrap();
1078        let nick2 = CString::new(nick2).unwrap();
1079        let res = unsafe {
1080            ((*ph).hexchat_nickcmp)(ph, nick1.as_ptr(), nick2.as_ptr())
1081        };
1082        if res < 0 {
1083            Ordering::Less
1084        } else if res > 0 {
1085            Ordering::Greater
1086        } else {
1087            Ordering::Equal
1088        }
1089    }
1090
1091    pub fn send_modes<'b, I: IntoIterator<Item=&'b str>>(&mut self, iter: I, mpl: i32, sign: char, mode: char) {
1092        // this was a mistake but oh well
1093        let ph = self.ph.ph;
1094        assert!(sign == '+' || sign == '-', "sign must be + or -");
1095        assert!(mode.is_ascii(), "mode must be ascii");
1096        assert!(mpl >= 0, "mpl must be non-negative");
1097        let v: Vec<CString> = iter.into_iter().map(|s| CString::new(s).unwrap()).collect();
1098        let mut v2: Vec<*const libc::c_char> = (&v).iter().map(|x| x.as_ptr()).collect();
1099        let arr: &mut [*const libc::c_char] = &mut *v2;
1100        unsafe {
1101            ((*ph).hexchat_send_modes)(ph, arr.as_mut_ptr(), arr.len() as libc::c_int,
1102                mpl as libc::c_int, sign as libc::c_char, mode as libc::c_char)
1103        }
1104    }
1105
1106    /// Executes a command.
1107    pub fn command(self, cmd: &str) {
1108        // this was a mistake but oh well
1109        let ph = self.ph.ph;
1110        // need to put this in a more permanent position than temporaries
1111        let cmd = CString::new(cmd).unwrap();
1112        unsafe {
1113            ((*ph).hexchat_command)(ph, cmd.as_ptr())
1114        }
1115    }
1116
1117    pub fn emit_print<'b, I: IntoIterator<Item=&'b str>>(self, event: &str, args: I) -> bool {
1118        let ph = self.ph.ph;
1119        let event = CString::new(event).unwrap();
1120        let mut args_cs: [Option<CString>; 4] = [None, None, None, None];
1121        {
1122            let mut iter = args.into_iter();
1123            for i in 0..4 {
1124                args_cs[i] = iter.next().map(|x| CString::new(x).unwrap());
1125                if args_cs[i].is_none() {
1126                    break;
1127                }
1128            }
1129            if iter.next().is_some() {
1130                // it's better to panic so as to get bug reports when we need to increase this
1131                panic!("too many arguments to emit_print (max 4), or iterator not fused");
1132            }
1133        }
1134        let mut argv: [*const libc::c_char; 5] = [ptr::null(); 5];
1135        for i in 0..4 {
1136            argv[i] = args_cs[i].as_ref().map_or(ptr::null(), |s| s.as_ptr());
1137        }
1138        unsafe {
1139            ((*ph).hexchat_emit_print)(ph, event.as_ptr(), argv[0], argv[1], argv[2], argv[3], argv[4]) != 0
1140        }
1141    }
1142
1143    pub fn emit_print_attrs<'b, I: IntoIterator<Item=&'b str>>(self, attrs: EventAttrs, event: &str, args: I) -> bool {
1144        let ph = self.ph.ph;
1145        let event = CString::new(event).unwrap();
1146        let mut args_cs: [Option<CString>; 4] = [None, None, None, None];
1147        {
1148            let mut iter = args.into_iter();
1149            for i in 0..4 {
1150                args_cs[i] = iter.next().map(|x| CString::new(x).unwrap());
1151                if args_cs[i].is_none() {
1152                    break;
1153                }
1154            }
1155            if let Some(_) = iter.next() {
1156                // it's better to panic so as to get bug reports when we need to increase this
1157                panic!("too many arguments to emit_print_attrs (max 4), or iterator not fused");
1158            }
1159        }
1160        let mut argv: [*const libc::c_char; 5] = [ptr::null(); 5];
1161        for i in 0..4 {
1162            argv[i] = args_cs[i].as_ref().map_or(ptr::null(), |s| s.as_ptr());
1163        }
1164        let helper = HexchatEventAttrsHelper::new_with(ph, attrs);
1165        unsafe {
1166            ((*ph).hexchat_emit_print_attrs)(ph, helper.0, event.as_ptr(), argv[0], argv[1], argv[2], argv[3], argv[4]) != 0
1167        }
1168    }
1169
1170    // ******** //
1171    // FORWARDS //
1172    // ******** //
1173    // We can't just deref because then you could recursively ensure valid context and then it'd no
1174    // longer work.
1175
1176    pub fn get_context(&mut self) -> Context {
1177        self.ph.get_context()
1178    }
1179
1180    /// Sets the current context.
1181    ///
1182    /// Returns `true` if the context is valid, `false` otherwise.
1183    pub fn set_context(&mut self, ctx: &Context) -> bool {
1184        self.ph.set_context(ctx)
1185    }
1186
1187    /// Prints to the hexchat buffer.
1188    // as of hexchat 2.14.1, this does not call hooks.
1189    pub fn print<T: ToString>(&mut self, s: T) {
1190        self.ph.print(s)
1191    }
1192
1193    /// Sets a command hook
1194    pub fn hook_command<F>(&mut self, cmd: &str, cb: F, pri: i32, help: Option<&str>) -> CommandHookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + ::std::panic::RefUnwindSafe {
1195        self.ph.hook_command(cmd, cb, pri, help)
1196    }
1197    /// Sets a server hook
1198    pub fn hook_server<F>(&mut self, cmd: &str, cb: F, pri: i32) -> ServerHookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + ::std::panic::RefUnwindSafe {
1199        self.ph.hook_server(cmd, cb, pri)
1200    }
1201    /// Sets a print hook
1202    pub fn hook_print<F>(&mut self, name: &str, cb: F, pri: i32) -> PrintHookHandle where F: Fn(&mut PluginHandle, Word) -> Eat + 'static + ::std::panic::RefUnwindSafe {
1203        self.ph.hook_print(name, cb, pri)
1204    }
1205    /// Sets a timer hook
1206    pub fn hook_timer<F>(&mut self, timeout: i32, cb: F) -> TimerHookHandle where F: Fn(&mut PluginHandle) -> bool + 'static + ::std::panic::RefUnwindSafe {
1207        self.ph.hook_timer(timeout, cb)
1208    }
1209    pub fn get_info(&mut self, id: &InfoId) -> Option<String> {
1210        self.ph.get_info(id)
1211    }
1212}
1213
1214// ******* //
1215// PRIVATE //
1216// ******* //
1217
1218// Type aliases, used for safety type checking.
1219/// Userdata type used by a command hook.
1220// We actually do want RefUnwindSafe. This function may be called multiple times, and it's not
1221// automatically invalidated if it panics, so it may be called again after it panics. If you need
1222// mutable state, std provides std::sync::Mutex which has poisoning. Other interior mutability with
1223// poisoning could also be used. std doesn't have anything for single-threaded performance (yet),
1224// but hexchat isn't particularly performance-critical.
1225type CommandHookUd = (Box<dyn Fn(&mut PluginHandle, Word, WordEol) -> Eat + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo);
1226/// Userdata type used by a server hook.
1227type ServerHookUd = (Box<dyn Fn(&mut PluginHandle, Word, WordEol, EventAttrs) -> Eat + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo);
1228/// Userdata type used by a print hook.
1229type PrintHookUd = (Box<dyn Fn(&mut PluginHandle, Word, EventAttrs) -> Eat + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo);
1230/// Userdata type used by a timer hook.
1231type TimerHookUd = (Rc<(Box<dyn Fn(&mut PluginHandle) -> bool + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo)>, Rc<Cell<bool>>);
1232
1233/// The contents of an empty CStr.
1234///
1235/// This is useful where you need a non-null CStr.
1236// NOTE: MUST BE b"\0"!
1237const EMPTY_CSTRING_DATA: &[u8] = b"\0";
1238
1239/// Converts a nul-terminated const bstring to a C string.
1240///
1241/// # Panics
1242///
1243/// Panics if b contains embedded nuls.
1244// TODO const fn, once that's possible
1245fn cstr(b: &'static [u8]) -> *const libc::c_char {
1246    CStr::from_bytes_with_nul(b).unwrap().as_ptr()
1247}
1248
1249/// Clones an Rc straight from a raw pointer.
1250///
1251/// # Safety
1252///
1253/// This function is unsafe because `ptr` must hame come from `Rc::into_raw`.
1254unsafe fn rc_clone_from_raw<T>(ptr: *const T) -> Rc<T> {
1255    // this is a bit confusing to read, but basically, we get an Rc from the raw ptr, and increment
1256    // the refcount.
1257    // The construct mem::forget(rc.clone()) increments the refcount.
1258    let rc = Rc::from_raw(ptr);
1259    mem::forget(rc.clone());
1260    rc
1261}
1262
1263/// Converts a **valid** context pointer into a Context (Rust-managed) struct.
1264///
1265/// # Safety
1266///
1267/// This function doesn't validate the context.
1268unsafe fn wrap_context(ph: &mut PluginHandle, ctx: *const internals::HexchatContext) -> Context {
1269    let ctxp = std::panic::AssertUnwindSafe(Rc::new(ctx));
1270    let weak_ctxp = Rc::downgrade(&ctxp); // calling the Closure should drop the Context (sort of)
1271    let closure: Rc<Cell<Option<PrintHookHandle>>> = Rc::new(Cell::new(None));
1272    let hook = std::panic::AssertUnwindSafe(Rc::downgrade(&closure)); // dropping the Context should drop the Closure
1273    ph.skip_pri_ck = true;
1274    closure.set(Some(ph.hook_print("Close Context", move |ph, _| {
1275        // need to be careful not to recurse or leak memory
1276        let ph = ph.ph;
1277        let ctx = ((*ph).hexchat_get_context)(ph);
1278        if **ctxp == ctx {
1279            let _: Option<PrintHookHandle> = hook.upgrade().unwrap().replace(None);
1280        }
1281        EAT_NONE
1282    }, libc::c_int::min_value())));
1283    ph.skip_pri_ck = false;
1284    Context { ctx: weak_ctxp, closure }
1285}
1286
1287/// Prints an &str to hexchat, trying to avoid allocations.
1288///
1289/// # Safety
1290///
1291/// This function does not check the passed in argument.
1292///
1293/// # Panics
1294///
1295/// Panics if panic_on_nul is true and the string contains embedded nuls.
1296unsafe fn hexchat_print_str(ph: *mut internals::Ph, s: &str, panic_on_nul: bool) {
1297    match CString::new(s) {
1298        Result::Ok(cs @ _) => {
1299            let csr: &CStr = &cs;
1300            ((*ph).hexchat_print)(ph, csr.as_ptr())
1301        },
1302        e @ _ => if panic_on_nul {e.unwrap();}, // TODO nul_position?
1303    }
1304}
1305
1306/// Helper to manage owned internals::HexchatEventAttrs
1307struct HexchatEventAttrsHelper(*mut internals::HexchatEventAttrs, *mut internals::Ph);
1308
1309impl HexchatEventAttrsHelper {
1310    fn new(ph: *mut internals::Ph) -> Self {
1311        HexchatEventAttrsHelper(unsafe { ((*ph).hexchat_event_attrs_create)(ph) }, ph)
1312    }
1313
1314    fn new_with(ph: *mut internals::Ph, attrs: EventAttrs) -> Self {
1315        let helper = Self::new(ph);
1316        let v = attrs.server_time.or(Some(UNIX_EPOCH)).map(|st| match st.duration_since(UNIX_EPOCH) {
1317            Ok(n) => n.as_secs(),
1318            Err(_) => 0
1319        }).filter(|&st| st < (libc::time_t::max_value() as u64)).unwrap() as libc::time_t;
1320        unsafe { (*helper.0).server_time_utc = v; }
1321        helper
1322    }
1323}
1324
1325impl Drop for HexchatEventAttrsHelper {
1326    fn drop(&mut self) {
1327        unsafe {
1328            ((*self.1).hexchat_event_attrs_free)(self.1, self.0)
1329        }
1330    }
1331}
1332
1333/// Holds name, desc, vers
1334// This is kinda naughty - we modify these values after returning from hexchat_plugin_init, during
1335// the deinitialization.
1336// However, if my reading of the HexChat code is correct, this is "ok".
1337#[derive(Copy, Clone)]
1338struct PluginInfo {
1339    name: *mut *const libc::c_char,
1340    desc: *mut *const libc::c_char,
1341    vers: *mut *const libc::c_char,
1342}
1343
1344impl PluginInfo {
1345    /// Creates a PluginInfo.
1346    ///
1347    /// # Panics
1348    ///
1349    /// This function explicitly doesn't panic. Call unwrap() on the result instead.
1350    fn new(name: *mut *const libc::c_char, desc: *mut *const libc::c_char, vers: *mut *const libc::c_char) -> Option<PluginInfo> {
1351        if name.is_null() || desc.is_null() || vers.is_null() || name == desc || desc == vers || name == vers {
1352            None
1353        } else {
1354            Some(unsafe { PluginInfo::new_unchecked(name, desc, vers) })
1355        }
1356    }
1357
1358    /// Creates a PluginInfo without checking the arguments.
1359    ///
1360    /// # Safety
1361    ///
1362    /// This function is unsafe, as it doesn't check the validity of the arguments. You're expected
1363    /// to only pass in non-aliased non-null pointers. Use new if unsure.
1364    unsafe fn new_unchecked(name: *mut *const libc::c_char, desc: *mut *const libc::c_char, vers: *mut *const libc::c_char) -> PluginInfo {
1365        PluginInfo {
1366            name, desc, vers
1367        }
1368    }
1369
1370    /// Drop relevant CStrings.
1371    ///
1372    /// # Safety
1373    ///
1374    /// This function is unsafe because calling it may trigger Undefined Behaviour. See also
1375    /// [CString::from_raw].
1376    ///
1377    /// [from_raw]: https://doc.rust-lang.org/std/ffi/struct.CString.html#method.from_raw
1378    unsafe fn drop_info(&mut self) {
1379        if !(*self.name).is_null() {
1380            mem::drop(CString::from_raw(*self.name as *mut _));
1381            *self.name = cstr(EMPTY_CSTRING_DATA);
1382        }
1383        if !(*self.desc).is_null() {
1384            mem::drop(CString::from_raw(*self.desc as *mut _));
1385            *self.desc = cstr(EMPTY_CSTRING_DATA);
1386        }
1387        if !(*self.vers).is_null() {
1388            mem::drop(CString::from_raw(*self.vers as *mut _));
1389            *self.vers = cstr(EMPTY_CSTRING_DATA);
1390        }
1391    }
1392}
1393
1394/// Plugin data stored in the hexchat plugin_handle.
1395struct PhUserdata {
1396    plug: Box<dyn Plugin>,
1397    pluginfo: PluginInfo,
1398}
1399
1400/// Puts the userdata in the plugin handle.
1401///
1402/// # Safety
1403///
1404/// This function is unsafe because it doesn't check if the pointer is valid.
1405///
1406/// Improper use of this function can leak memory.
1407unsafe fn put_userdata(ph: *mut internals::Ph, ud: Box<PhUserdata>) {
1408    (*ph).userdata = Box::into_raw(ud) as *mut libc::c_void;
1409}
1410
1411// unsafe fn get_userdata(ph: *mut internals::Ph) -> *const PhUserdata {
1412//     (*ph).userdata as *const _
1413// }
1414
1415/// Pops the userdata from the plugin handle.
1416///
1417/// # Safety
1418///
1419/// This function is unsafe because it doesn't check if the pointer is valid.
1420unsafe fn pop_userdata(ph: *mut internals::Ph) -> Box<PhUserdata> {
1421    Box::from_raw(mem::replace(&mut (*ph).userdata, ptr::null_mut()) as *mut PhUserdata)
1422}
1423
1424// *********************** //
1425// PUBLIC OUT OF NECESSITY //
1426// *********************** //
1427
1428#[doc(hidden)]
1429pub unsafe fn hexchat_plugin_init<T>(plugin_handle: *mut libc::c_void,
1430                                     plugin_name: *mut *const libc::c_char,
1431                                     plugin_desc: *mut *const libc::c_char,
1432                                     plugin_version: *mut *const libc::c_char,
1433                                     arg: *const libc::c_char) -> libc::c_int
1434                                     where T: Plugin + Default + 'static {
1435    if plugin_handle.is_null() || plugin_name.is_null() || plugin_desc.is_null() || plugin_version.is_null() {
1436        // we can't really do anything here.
1437        eprintln!("hexchat_plugin_init called with a null pointer that shouldn't be null - broken hexchat");
1438        // TODO maybe call abort.
1439        return 0;
1440    }
1441    let ph = plugin_handle as *mut internals::Ph;
1442    // clear the "userdata" field first thing - if the deinit function gets called (wrong hexchat
1443    // version, other issues), we don't wanna try to drop the hexchat_dummy or hexchat_read_fd
1444    // function as if it were a Box!
1445    (*ph).userdata = ptr::null_mut();
1446    // read the filename so we can pass it on later.
1447    let filename = if !(*plugin_name).is_null() {
1448        if let Ok(fname) = CStr::from_ptr(*plugin_name).to_owned().into_string() {
1449            fname
1450        } else {
1451            eprintln!("failed to convert filename to utf8 - broken hexchat");
1452            return 0;
1453        }
1454    } else {
1455        // no filename specified for some reason, but we can still load
1456        String::new() // empty string
1457    };
1458    // these may be null, unless initialization is successful.
1459    // we set them to null as markers.
1460    *plugin_name = ptr::null();
1461    *plugin_desc = ptr::null();
1462    *plugin_version = ptr::null();
1463    // do some version checks for safety
1464    // NOTE: calling hexchat functions with null plugin_name, plugin_desc, plugin_version is a bit
1465    // dangerous. this particular case is "ok".
1466    {
1467        let ver = ((*ph).hexchat_get_info)(ph, cstr(b"version\0")); // this shouldn't panic
1468        let cstr = CStr::from_ptr(ver);
1469        if let Ok(ver) = cstr.to_str() {
1470            let mut iter = ver.split('.');
1471            let a = iter.next().map(i32::from_str).and_then(Result::ok).unwrap_or(0);
1472            let b = iter.next().map(i32::from_str).and_then(Result::ok).unwrap_or(0);
1473            let c = iter.next().map(i32::from_str).and_then(Result::ok).unwrap_or(0);
1474            // 2.9.6 or greater
1475            if !(a > 2 || (a == 2 && (b > 9 || (b == 9 && (c > 6 || (c == 6)))))) {
1476                return 0;
1477            }
1478        } else {
1479            return 0;
1480        }
1481    }
1482    let mut pluginfo = if let Some(pluginfo) = PluginInfo::new(plugin_name, plugin_desc, plugin_version) {
1483        pluginfo
1484    } else {
1485        return 0;
1486    };
1487    let r: thread::Result<Option<Box<_>>> = {
1488        catch_unwind(move || {
1489            let mut pluginhandle = PluginHandle::new(ph, pluginfo);
1490            let plug = T::default();
1491            if plug.init(&mut pluginhandle, if !arg.is_null() { Some(CStr::from_ptr(arg).to_str().expect("arg not valid utf-8 - broken hexchat")) } else { None }) {
1492                if !(pluginfo.name.is_null() || pluginfo.desc.is_null() || pluginfo.vers.is_null()) {
1493                    Some(Box::new(PhUserdata { plug: Box::new(plug), pluginfo }))
1494                } else {
1495                    // TODO log: forgot to call register
1496                    None
1497                }
1498            } else {
1499                None
1500            }
1501        })
1502    };
1503    match r {
1504        Result::Ok(Option::Some(plug @ _)) => {
1505            if (*plugin_name).is_null() || (*plugin_desc).is_null() || (*plugin_version).is_null() {
1506                // TODO deallocate any which are non-null
1507                pluginfo.drop_info();
1508                0
1509            } else {
1510                put_userdata(ph, plug);
1511                1
1512            }
1513        },
1514        r @ _ => {
1515            // if the initialization fails, deinit doesn't get called, so we need to clean up
1516            // ourselves.
1517
1518            // TODO might leak pluginfo on panic?
1519            
1520            if let Err(_) = r {
1521                // TODO try to log panic?
1522            }
1523            0
1524        },
1525    }
1526}
1527
1528#[doc(hidden)]
1529pub unsafe fn hexchat_plugin_deinit<T>(plugin_handle: *mut libc::c_void) -> libc::c_int where T: Plugin {
1530    let mut safe_to_unload = 1;
1531    // plugin_handle should never be null, but just in case.
1532    if !plugin_handle.is_null() {
1533        let ph = plugin_handle as *mut internals::Ph;
1534        // userdata should also never be null.
1535        if !(*ph).userdata.is_null() {
1536            {
1537                let mut info: Option<PluginInfo> = None;
1538                {
1539                    let mut ausinfo = ::std::panic::AssertUnwindSafe(&mut info);
1540                    safe_to_unload = if catch_unwind(move || {
1541                        let userdata = *pop_userdata(ph);
1542                        **ausinfo = Some(userdata.pluginfo);
1543                        userdata.plug.deinit(&mut PluginHandle::new(ph, userdata.pluginfo));
1544                    }).is_ok() { 1 } else { 0 };
1545                }
1546                if let Some(mut info) = info {
1547                    info.drop_info();
1548                } else {
1549                    eprintln!("I have no idea tbh, I didn't know `pop_userdata` could panic!");
1550                }
1551            }
1552        } else {
1553            eprintln!("null userdata in hexchat_plugin_deinit - broken hexchat or broken hexchat-plugin.rs");
1554        }
1555    } else {
1556        eprintln!("hexchat_plugin_deinit called with a null plugin_handle - broken hexchat");
1557    }
1558    safe_to_unload
1559}
1560
1561/// Exports a hexchat plugin.
1562#[macro_export]
1563macro_rules! hexchat_plugin {
1564    ($t:ty) => {
1565        #[no_mangle]
1566        pub unsafe extern "C" fn hexchat_plugin_init(plugin_handle: *mut $crate::libc::c_void,
1567                                              plugin_name: *mut *const $crate::libc::c_char,
1568                                              plugin_desc: *mut *const $crate::libc::c_char,
1569                                              plugin_version: *mut *const $crate::libc::c_char,
1570                                              arg: *const $crate::libc::c_char) -> $crate::libc::c_int {
1571            $crate::hexchat_plugin_init::<$t>(plugin_handle, plugin_name, plugin_desc, plugin_version, arg)
1572        }
1573        #[no_mangle]
1574        pub unsafe extern "C" fn hexchat_plugin_deinit(plugin_handle: *mut $crate::libc::c_void) -> $crate::libc::c_int {
1575            $crate::hexchat_plugin_deinit::<$t>(plugin_handle)
1576        }
1577        // unlike what the documentation states, there's no need to define hexchat_plugin_get_info.
1578        // so we don't. it'd be impossible to make it work well with rust anyway.
1579    };
1580}