#[doc(hidden)]
pub extern crate libc;
mod eat;
mod infoid;
mod internals;
mod pluginfo;
mod word;
pub use eat::*;
pub use infoid::InfoId;
pub use word::*;
use pluginfo::PluginInfo;
use internals::Ph as RawPh;
use internals::HexchatEventAttrs as RawAttrs;
use std::borrow::Cow;
use std::cell::Cell;
use std::cell::RefCell;
use std::collections::HashSet;
use std::ffi::{CString, CStr};
use std::fmt;
use std::marker::PhantomData;
use std::mem;
use std::mem::ManuallyDrop;
use std::panic::{AssertUnwindSafe, RefUnwindSafe, UnwindSafe, catch_unwind};
use std::ptr;
use std::rc::Rc;
use std::rc::Weak as RcWeak;
use std::str::FromStr;
use std::thread;
use std::time::{SystemTime, UNIX_EPOCH, Duration};
#[doc(hidden)]
pub use libc::{c_char, c_int, c_void, time_t};
pub const PRI_HIGHEST: i32 = 127;
pub const PRI_HIGH: i32 = 64;
pub const PRI_NORM: i32 = 0;
pub const PRI_LOW: i32 = -64;
pub const PRI_LOWEST: i32 = -128;
pub unsafe trait Plugin<'ph> {
fn init(&self, ph: &mut PluginHandle<'ph>, filename: &str, arg: Option<&str>) -> bool;
fn deinit(&self, ph: &mut PluginHandle<'ph>) {
let _ = ph;
}
}
#[repr(transparent)]
#[doc(hidden)]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct LtPhPtr<'ph> {
ph: *mut RawPh,
_lt: PhantomData<&'ph RawPh>,
}
pub struct PluginHandle<'ph> {
ph: LtPhPtr<'ph>,
contexts: Contexts,
info: PluginInfo,
}
mod valid_context {
use crate::PluginHandle;
pub struct ValidContext<'a, 'ph: 'a> {
pub(crate) ph: &'a mut PluginHandle<'ph>,
_hidden: (),
}
impl<'a, 'ph: 'a> ValidContext<'a, 'ph> {
pub(crate) unsafe fn new(ph: &'a mut PluginHandle<'ph>) -> Self {
Self { ph, _hidden: () }
}
}
}
pub use valid_context::ValidContext;
#[derive(Clone)]
pub struct EventAttrs<'a> {
pub server_time: Option<SystemTime>,
_dummy: PhantomData<&'a ()>,
}
#[must_use = "Hooks must be stored somewhere and are automatically unhooked on Drop"]
pub struct HookHandle<'ph> {
ph: LtPhPtr<'ph>,
hh: *const internals::HexchatHook,
freed: Rc<Cell<bool>>,
_f: PhantomData<Rc<HookUd<'ph>>>,
}
#[derive(Clone)]
pub struct Context<'ph> {
contexts: Contexts,
ctx: RcWeak<*const internals::HexchatContext>,
_ph: PhantomData<&'ph RawPh>,
}
pub struct InvalidContextError<F>(F);
impl<F> InvalidContextError<F> {
pub fn get_closure(self) -> F {
self.0
}
}
impl<'ph> Drop for HookHandle<'ph> {
fn drop(&mut self) {
if self.freed.get() {
return;
}
self.freed.set(true);
unsafe {
let b = ((*self.ph.ph).hexchat_unhook)(self.ph.ph, self.hh) as *mut HookUd<'ph>;
drop(Rc::from_raw(b));
}
}
}
impl<'ph> Drop for Context<'ph> {
fn drop(&mut self) {
if self.ctx.strong_count() == 1 && self.ctx.weak_count() == 1 {
let strong = self.ctx.upgrade().unwrap();
self.contexts.borrow_mut().remove(&strong);
}
}
}
unsafe fn call_hook_protected<F: FnOnce() -> Eat + UnwindSafe>(
ph: *mut RawPh,
f: F
) -> Eat {
match catch_unwind(f) {
Result::Ok(v @ _) => v,
Result::Err(e @ _) => {
if let Some(s) = e.downcast_ref::<&str>() {
hexchat_print_str(ph, s, false);
} else if let Some(s) = e.downcast_ref::<String>() {
hexchat_print_str(ph, &s, false);
} else if let Some(s) = e.downcast_ref::<Cow<'static, str>>() {
hexchat_print_str(ph, &s, false);
}
EAT_NONE
}
}
}
impl<'ph> PluginHandle<'ph> {
unsafe fn new(ph: LtPhPtr<'ph>, info: PluginInfo, contexts: Contexts) -> PluginHandle<'ph> {
PluginHandle {
ph, info, contexts
}
}
pub fn register(&mut self, name: &str, desc: &str, ver: &str) {
unsafe {
let info = self.info;
if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
panic!("Attempt to re-register a plugin");
}
let name = CString::new(name).unwrap();
let desc = CString::new(desc).unwrap();
let ver = CString::new(ver).unwrap();
(*info.name) = name.into_raw();
(*info.desc) = desc.into_raw();
(*info.vers) = ver.into_raw();
}
}
pub fn get_name(&self) -> &str {
unsafe {
let info = self.info;
if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
std::str::from_utf8_unchecked(CStr::from_ptr(*info.name).to_bytes())
} else {
panic!("Attempt to get the name of a plugin that was not yet registered.");
}
}
}
pub fn get_description(&self) -> &str {
unsafe {
let info = self.info;
if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
std::str::from_utf8_unchecked(CStr::from_ptr(*info.desc).to_bytes())
} else {
panic!("Attempt to get the description of a plugin that was not yet registered.");
}
}
}
pub fn get_version(&self) -> &str {
unsafe {
let info = self.info;
if !(*info.name).is_null() || !(*info.desc).is_null() || !(*info.vers).is_null() {
std::str::from_utf8_unchecked(CStr::from_ptr(*info.vers).to_bytes())
} else {
panic!("Attempt to get the version of a plugin that was not yet registered.");
}
}
}
pub fn ensure_valid_context<F, R>(&mut self, f: F) -> R where F: for<'a> FnOnce(ValidContext<'a, 'ph>) -> R {
let ctx = self.get_context();
let res = self.with_context(&ctx, f);
match res {
Result::Ok(r @ _) => r,
Result::Err(e @ _) => {
let nctx = self.find_valid_context().expect("ensure_valid_context failed (find_valid_context failed), was hexchat closing?");
self.with_context(&nctx, e.get_closure()).ok().expect("ensure_valid_context failed, was hexchat closing?")
}
}
}
pub fn get_context(&mut self) -> Context<'ph> {
let ctxp = unsafe { ((*self.ph.ph).hexchat_get_context)(self.ph.ph) };
let ok = unsafe { ((*self.ph.ph).hexchat_set_context)(self.ph.ph, ctxp) };
unsafe { wrap_context(self, if ok == 0 { ptr::null() } else { ctxp }) }
}
pub fn set_context(&mut self, ctx: &Context<'ph>) -> bool {
if let Some(ctx) = ctx.ctx.upgrade() {
unsafe {
((*self.ph.ph).hexchat_set_context)(self.ph.ph, *ctx) != 0
}
} else {
false
}
}
#[inline]
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 {
if !self.set_context(ctx) {
Err(InvalidContextError(f))
} else {
Ok(f(unsafe { ValidContext::new(self) }))
}
}
pub fn hook_command<F>(&mut self, cmd: &str, pri: i32, help: Option<&str>, cb: F) -> HookHandle<'ph> where F: Fn(&mut PluginHandle<'ph>, Word, WordEol) -> Eat + 'ph + RefUnwindSafe {
unsafe extern "C" fn callback(word: *const *const c_char, word_eol: *const *const c_char, ud: *mut c_void) -> c_int {
let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd);
(f)(word, word_eol, ptr::null()).do_eat as c_int
}
let b: Rc<HookUd> = {
let ph = self.ph;
let info = self.info;
let contexts = Rc::clone(&self.contexts);
Rc::new(Box::new(move |word, word_eol, _| {
let cb = &cb;
let contexts = Rc::clone(&contexts);
unsafe {
call_hook_protected(ph.ph, move || {
let mut ph = PluginHandle::new(ph, info, contexts);
let word = Word::new(word);
let word_eol = WordEol::new(word_eol);
cb(&mut ph, word, word_eol)
})
}
}))
};
let name = CString::new(cmd).unwrap();
let help_text = help.map(CString::new).map(Result::unwrap);
let bp = Rc::into_raw(b);
unsafe {
let res = ((*self.ph.ph).hexchat_hook_command)(self.ph.ph, name.as_ptr(), pri as c_int, callback, help_text.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()), bp as *mut _);
assert!(!res.is_null());
HookHandle { ph: self.ph, hh: res, freed: Default::default(), _f: PhantomData }
}
}
pub fn hook_server<F>(&mut self, cmd: &str, pri: i32, cb: F) -> HookHandle<'ph> where F: Fn(&mut PluginHandle<'ph>, Word, WordEol) -> Eat + 'ph + RefUnwindSafe {
self.hook_server_attrs(cmd, pri, move |ph, w, we, _| cb(ph, w, we))
}
pub fn hook_server_attrs<F>(&mut self, cmd: &str, pri: i32, cb: F) -> HookHandle<'ph> where F: Fn(&mut PluginHandle<'ph>, Word, WordEol, EventAttrs) -> Eat + 'ph + RefUnwindSafe {
unsafe extern "C" fn callback(word: *const *const c_char, word_eol: *const *const c_char, attrs: *const RawAttrs, ud: *mut c_void) -> c_int {
let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd);
(f)(word, word_eol, attrs).do_eat as c_int
}
let b: Rc<HookUd> = {
let ph = self.ph;
let info = self.info;
let contexts = Rc::clone(&self.contexts);
Rc::new(Box::new(move |word, word_eol, attrs| {
let cb = &cb;
let contexts = Rc::clone(&contexts);
unsafe {
call_hook_protected(ph.ph, move || {
let mut ph = PluginHandle::new(ph, info, contexts);
let word = Word::new(word);
let word_eol = WordEol::new(word_eol);
let attrs = (&*attrs).into();
cb(&mut ph, word, word_eol, attrs)
})
}
}))
};
let name = CString::new(cmd).unwrap();
let bp = Rc::into_raw(b);
unsafe {
let res = ((*self.ph.ph).hexchat_hook_server_attrs)(self.ph.ph, name.as_ptr(), pri as c_int, callback, bp as *mut _);
assert!(!res.is_null());
HookHandle { ph: self.ph, hh: res, freed: Default::default(), _f: PhantomData }
}
}
pub fn hook_print<F>(&mut self, name: &str, pri: i32, cb: F) -> HookHandle<'ph> where F: Fn(&mut PluginHandle<'ph>, Word) -> Eat + 'ph + RefUnwindSafe {
unsafe extern "C" fn callback(word: *const *const c_char, ud: *mut c_void) -> c_int {
let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd);
(f)(word, ptr::null(), ptr::null()).do_eat as c_int
}
let b: Rc<HookUd> = {
let ph = self.ph;
let info = self.info;
let contexts = Rc::clone(&self.contexts);
Rc::new(Box::new(move |word, _, _| {
let cb = &cb;
let contexts = Rc::clone(&contexts);
unsafe {
call_hook_protected(ph.ph, move || {
let mut ph = PluginHandle::new(ph, info, contexts);
let word = Word::new(word);
cb(&mut ph, word)
})
}
}))
};
let name = CString::new(name).unwrap();
let bp = Rc::into_raw(b);
unsafe {
let res = ((*self.ph.ph).hexchat_hook_print)(self.ph.ph, name.as_ptr(), pri as c_int, callback, bp as *mut _);
assert!(!res.is_null());
HookHandle { ph: self.ph, hh: res, freed: Default::default(), _f: PhantomData }
}
}
pub fn hook_print_attrs<F>(&mut self, name: &str, pri: i32, cb: F) -> HookHandle<'ph> where F: Fn(&mut PluginHandle<'ph>, Word, EventAttrs) -> Eat + 'ph + RefUnwindSafe {
unsafe extern "C" fn callback(word: *const *const c_char, attrs: *const RawAttrs, ud: *mut c_void) -> c_int {
let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd);
(f)(word, ptr::null(), attrs).do_eat as c_int
}
let b: Rc<HookUd> = {
let ph = self.ph;
let info = self.info;
let contexts = Rc::clone(&self.contexts);
Rc::new(Box::new(move |word, _, attrs| {
let cb = &cb;
let contexts = Rc::clone(&contexts);
unsafe {
call_hook_protected(ph.ph, move || {
let mut ph = PluginHandle::new(ph, info, contexts);
let word = Word::new(word);
let attrs = (&*attrs).into();
cb(&mut ph, word, attrs)
})
}
}))
};
let name = CString::new(name).unwrap();
let bp = Rc::into_raw(b);
unsafe {
let res = ((*self.ph.ph).hexchat_hook_print_attrs)(self.ph.ph, name.as_ptr(), pri as c_int, callback, bp as *mut _);
assert!(!res.is_null());
HookHandle { ph: self.ph, hh: res, freed: Default::default(), _f: PhantomData }
}
}
pub fn hook_timer<F>(&mut self, timeout: i32, cb: F) -> HookHandle<'ph> where F: Fn(&mut PluginHandle<'ph>) -> bool + 'ph + RefUnwindSafe {
unsafe extern "C" fn callback(ud: *mut c_void) -> c_int {
let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd);
(f)(ptr::null(), ptr::null(), ptr::null()).do_eat as c_int
}
let freed = Rc::new(Cell::new(false));
let dropper = Rc::new(Cell::new(None));
let b: Rc<HookUd> = {
let ph = self.ph;
let info = self.info;
let contexts = Rc::clone(&self.contexts);
let freed = AssertUnwindSafe(Rc::clone(&freed));
let dropper = AssertUnwindSafe(Rc::clone(&dropper));
Rc::new(Box::new(move |_, _, _| {
let cb = &cb;
let contexts = Rc::clone(&contexts);
let res = unsafe {
call_hook_protected(ph.ph, move || {
let mut ph = PluginHandle::new(ph, info, contexts);
if cb(&mut ph) {
EAT_HEXCHAT
} else {
EAT_NONE
}
})
};
if res == EAT_NONE && !freed.get() {
freed.set(true);
unsafe {
call_hook_protected(ph.ph, || {
drop(Rc::from_raw(dropper.take().unwrap()));
EAT_NONE
});
}
}
res
}))
};
let bp = Rc::into_raw(b);
dropper.set(Some(bp));
unsafe {
let res = ((*self.ph.ph).hexchat_hook_timer)(self.ph.ph, timeout as c_int, callback, bp as *mut _);
assert!(!res.is_null());
HookHandle { ph: self.ph, hh: res, freed: freed, _f: PhantomData }
}
}
pub fn print<T: ToString>(&mut self, s: T) {
let s = s.to_string();
unsafe {
hexchat_print_str(self.ph.ph, &*s, true);
}
}
pub fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) {
self.print(fmt);
}
pub fn get_info<'a>(&'a mut self, id: InfoId) -> Option<&'a str> {
let ph = self.ph;
let id_cstring = CString::new(&*id.name()).unwrap();
unsafe {
let res = ((*ph.ph).hexchat_get_info)(ph.ph, id_cstring.as_ptr());
if res.is_null() {
None
} else {
let s = CStr::from_ptr(res).to_str();
Some(s.expect("non-utf8 word - broken hexchat"))
}
}
}
fn find_valid_context(&mut self) -> Option<Context<'ph>> {
unsafe {
let ph = self.ph;
#[allow(unused_mut)]
let mut list = ((*ph.ph).hexchat_list_get)(ph.ph, cstr(b"channels\0"));
if ((*ph.ph).hexchat_list_next)(ph.ph, list) != 0 {
let ctx = ((*ph.ph).hexchat_list_str)(ph.ph, list, cstr(b"context\0")) as *const internals::HexchatContext;
((*ph.ph).hexchat_list_free)(ph.ph, list);
Some(wrap_context(self, ctx))
} else {
((*ph.ph).hexchat_list_free)(ph.ph, list);
None
}
}
}
}
impl<'a> EventAttrs<'a> {
pub fn new() -> EventAttrs<'a> {
EventAttrs {
server_time: None,
_dummy: PhantomData,
}
}
}
impl<'a> From<&'a RawAttrs> for EventAttrs<'a> {
fn from(other: &'a RawAttrs) -> EventAttrs<'a> {
EventAttrs {
server_time: if other.server_time_utc > 0 { Some(UNIX_EPOCH + Duration::from_secs(other.server_time_utc as u64)) } else { None },
_dummy: PhantomData,
}
}
}
impl<'a, 'ph: 'a> ValidContext<'a, 'ph> {
pub fn find_context(&mut self, servname: Option<&str>, channel: Option<&str>) -> Option<Context<'ph>> {
let ph = self.ph.ph;
let servname = servname.map(|x| CString::new(x).unwrap());
let channel = channel.map(|x| CString::new(x).unwrap());
let ctx = unsafe {
let sptr = servname.map(|x| x.as_ptr()).unwrap_or(ptr::null());
let cptr = channel.map(|x| x.as_ptr()).unwrap_or(ptr::null());
((*ph.ph).hexchat_find_context)(ph.ph, sptr, cptr)
};
if ctx.is_null() {
None
} else {
Some(unsafe { wrap_context(self.ph, ctx) })
}
}
pub fn nickcmp(&mut self, nick1: &str, nick2: &str) -> ::std::cmp::Ordering {
use std::cmp::Ordering;
let ph = self.ph.ph;
let nick1 = CString::new(nick1).unwrap();
let nick2 = CString::new(nick2).unwrap();
let res = unsafe {
((*ph.ph).hexchat_nickcmp)(ph.ph, nick1.as_ptr(), nick2.as_ptr())
};
if res < 0 {
Ordering::Less
} else if res > 0 {
Ordering::Greater
} else {
Ordering::Equal
}
}
pub fn send_modes<'b, I: IntoIterator<Item=&'b str>>(&mut self, iter: I, mpl: i32, sign: char, mode: char) {
let ph = self.ph.ph;
assert!(sign == '+' || sign == '-', "sign must be + or -");
assert!(mode.is_ascii(), "mode must be ascii");
assert!(mpl >= 0, "mpl must be non-negative");
let v: Vec<CString> = iter.into_iter().map(|s| CString::new(s).unwrap()).collect();
let mut v2: Vec<*const c_char> = (&v).iter().map(|x| x.as_ptr()).collect();
let arr: &mut [*const c_char] = &mut *v2;
unsafe {
((*ph.ph).hexchat_send_modes)(ph.ph, arr.as_mut_ptr(), arr.len() as c_int,
mpl as c_int, sign as c_char, mode as c_char)
}
}
pub fn command(self, cmd: &str) {
let ph = self.ph.ph;
let cmd = CString::new(cmd).unwrap();
unsafe {
((*ph.ph).hexchat_command)(ph.ph, cmd.as_ptr())
}
}
pub fn emit_print<'b, I: IntoIterator<Item=&'b str>>(self, event: &str, args: I) -> bool {
let ph = self.ph.ph;
let event = CString::new(event).unwrap();
let mut args_cs: [Option<CString>; 4] = [None, None, None, None];
{
let mut iter = args.into_iter();
for i in 0..4 {
args_cs[i] = iter.next().map(|x| CString::new(x).unwrap());
if args_cs[i].is_none() {
break;
}
}
if iter.next().is_some() {
panic!("too many arguments to emit_print (max 4), or iterator not fused");
}
}
let mut argv: [*const c_char; 5] = [ptr::null(); 5];
for i in 0..4 {
argv[i] = args_cs[i].as_ref().map_or(ptr::null(), |s| s.as_ptr());
}
unsafe {
((*ph.ph).hexchat_emit_print)(ph.ph, event.as_ptr(), argv[0], argv[1], argv[2], argv[3], argv[4]) != 0
}
}
pub fn emit_print_attrs<'b, I: IntoIterator<Item=&'b str>>(self, attrs: EventAttrs, event: &str, args: I) -> bool {
let ph = self.ph.ph;
let event = CString::new(event).unwrap();
let mut args_cs: [Option<CString>; 4] = [None, None, None, None];
{
let mut iter = args.into_iter();
for i in 0..4 {
args_cs[i] = iter.next().map(|x| CString::new(x).unwrap());
if args_cs[i].is_none() {
break;
}
}
if let Some(_) = iter.next() {
panic!("too many arguments to emit_print_attrs (max 4), or iterator not fused");
}
}
let mut argv: [*const c_char; 5] = [ptr::null(); 5];
for i in 0..4 {
argv[i] = args_cs[i].as_ref().map_or(ptr::null(), |s| s.as_ptr());
}
let helper = unsafe { HexchatEventAttrsHelper::new_with(ph.ph, attrs) };
unsafe {
((*ph.ph).hexchat_emit_print_attrs)(ph.ph, helper.0, event.as_ptr(), argv[0], argv[1], argv[2], argv[3], argv[4]) != 0
}
}
pub fn get_context(&mut self) -> Context<'ph> {
self.ph.get_context()
}
pub fn set_context(&mut self, ctx: &Context<'ph>) -> bool {
self.ph.set_context(ctx)
}
pub fn print<T: ToString>(&mut self, s: T) {
self.ph.print(s)
}
pub fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) {
self.ph.write_fmt(fmt)
}
pub fn hook_command<F>(&mut self, cmd: &str, pri: i32, help: Option<&str>, cb: F) -> HookHandle<'ph> where F: Fn(&mut PluginHandle<'ph>, Word, WordEol) -> Eat + 'ph + RefUnwindSafe {
self.ph.hook_command(cmd, pri, help, cb)
}
pub fn hook_server<F>(&mut self, cmd: &str, pri: i32, cb: F) -> HookHandle<'ph> where F: Fn(&mut PluginHandle<'ph>, Word, WordEol) -> Eat + 'ph + RefUnwindSafe {
self.ph.hook_server(cmd, pri, cb)
}
pub fn hook_server_attrs<F>(&mut self, cmd: &str, pri: i32, cb: F) -> HookHandle<'ph> where F: Fn(&mut PluginHandle<'ph>, Word, WordEol, EventAttrs) -> Eat + 'ph + RefUnwindSafe {
self.ph.hook_server_attrs(cmd, pri, cb)
}
pub fn hook_print<F>(&mut self, name: &str, pri: i32, cb: F) -> HookHandle<'ph> where F: Fn(&mut PluginHandle<'ph>, Word) -> Eat + 'ph + RefUnwindSafe {
self.ph.hook_print(name, pri, cb)
}
pub fn hook_print_attrs<F>(&mut self, name: &str, pri: i32, cb: F) -> HookHandle<'ph> where F: Fn(&mut PluginHandle<'ph>, Word, EventAttrs) -> Eat + 'ph + RefUnwindSafe {
self.ph.hook_print_attrs(name, pri, cb)
}
pub fn hook_timer<F>(&mut self, timeout: i32, cb: F) -> HookHandle<'ph> where F: Fn(&mut PluginHandle<'ph>) -> bool + 'ph + RefUnwindSafe {
self.ph.hook_timer(timeout, cb)
}
pub fn get_info<'b>(&'b mut self, id: InfoId) -> Option<&'b str> {
self.ph.get_info(id)
}
}
type HookUd<'ph> = Box<dyn Fn(*const *const c_char, *const *const c_char, *const RawAttrs) -> Eat + RefUnwindSafe + 'ph>;
type Contexts = Rc<AssertUnwindSafe<RefCell<HashSet<Rc<*const internals::HexchatContext>>>>>;
const EMPTY_CSTRING_DATA: &[u8] = b"\0";
fn cstr(b: &'static [u8]) -> *const c_char {
CStr::from_bytes_with_nul(b).unwrap().as_ptr()
}
unsafe fn rc_clone_from_raw<T>(ptr: *const T) -> Rc<T> {
let rc = ManuallyDrop::new(Rc::from_raw(ptr));
Rc::clone(&rc)
}
unsafe fn wrap_context<'ph>(ph: &mut PluginHandle<'ph>, ctx: *const internals::HexchatContext) -> Context<'ph> {
let contexts = ph.contexts.clone();
if ctx.is_null() {
Context { contexts, ctx: RcWeak::new(), _ph: PhantomData }
} else {
let weak_ctxp = (|| {
contexts.borrow().get(&ctx).map(|x| {
Rc::downgrade(x)
})
})().unwrap_or_else(|| {
let ctxp = Rc::new(ctx);
let weak_ctxp = Rc::downgrade(&ctxp);
contexts.borrow_mut().insert(ctxp);
weak_ctxp
});
Context { contexts, ctx: weak_ctxp, _ph: PhantomData }
}
}
unsafe fn hexchat_print_str(ph: *mut RawPh, s: &str, panic_on_nul: bool) {
match CString::new(s) {
Result::Ok(cs @ _) => {
let csr: &CStr = &cs;
((*ph).hexchat_print)(ph, csr.as_ptr())
},
e @ _ => if panic_on_nul {e.unwrap();}, }
}
struct HexchatEventAttrsHelper(*mut RawAttrs, *mut RawPh);
impl HexchatEventAttrsHelper {
unsafe fn new(ph: *mut RawPh) -> Self {
HexchatEventAttrsHelper(((*ph).hexchat_event_attrs_create)(ph), ph)
}
unsafe fn new_with(ph: *mut RawPh, attrs: EventAttrs<'_>) -> Self {
let helper = Self::new(ph);
let v = attrs.server_time.or(Some(UNIX_EPOCH)).map(|st| match st.duration_since(UNIX_EPOCH) {
Ok(n) => n.as_secs(),
Err(_) => 0
}).filter(|&st| st < (time_t::max_value() as u64)).unwrap() as time_t;
(*helper.0).server_time_utc = v;
helper
}
}
impl Drop for HexchatEventAttrsHelper {
fn drop(&mut self) {
unsafe {
((*self.1).hexchat_event_attrs_free)(self.1, self.0)
}
}
}
struct PhUserdata<'ph> {
plug: Box<dyn Plugin<'ph> + 'ph>,
contexts: Contexts,
_context_hook: HookHandle<'ph>,
pluginfo: PluginInfo,
}
unsafe fn put_userdata<'ph>(ph: LtPhPtr<'ph>, ud: Rc<PhUserdata<'ph>>) {
(*ph.ph).userdata = Rc::into_raw(ud) as *mut c_void;
}
unsafe fn pop_userdata<'ph>(ph: LtPhPtr<'ph>) -> Rc<PhUserdata<'ph>> {
Rc::from_raw(mem::replace(&mut (*ph.ph).userdata, ptr::null_mut()) as *mut PhUserdata<'ph>)
}
#[doc(hidden)]
pub unsafe fn hexchat_plugin_init<'ph, T>(plugin_handle: LtPhPtr<'ph>,
plugin_name: *mut *const c_char,
plugin_desc: *mut *const c_char,
plugin_version: *mut *const c_char,
arg: *const c_char) -> c_int
where T: Plugin<'ph> + Default + 'ph {
if plugin_handle.ph.is_null() || plugin_name.is_null() || plugin_desc.is_null() || plugin_version.is_null() {
eprintln!("hexchat_plugin_init called with a null pointer that shouldn't be null - broken hexchat");
return 0;
}
let ph = plugin_handle.ph as *mut RawPh;
(*ph).userdata = ptr::null_mut();
let filename = if !(*plugin_name).is_null() {
if let Ok(fname) = CStr::from_ptr(*plugin_name).to_owned().into_string() {
fname
} else {
eprintln!("failed to convert filename to utf8 - broken hexchat");
return 0;
}
} else {
String::new() };
*plugin_name = ptr::null();
*plugin_desc = ptr::null();
*plugin_version = ptr::null();
{
let ver = ((*ph).hexchat_get_info)(ph, cstr(b"version\0")); let cstr = CStr::from_ptr(ver);
if let Ok(ver) = cstr.to_str() {
let mut iter = ver.split('.');
let a = iter.next().map(i32::from_str).and_then(Result::ok).unwrap_or(0);
let b = iter.next().map(i32::from_str).and_then(Result::ok).unwrap_or(0);
let c = iter.next().map(i32::from_str).and_then(Result::ok).unwrap_or(0);
if !(a > 2 || (a == 2 && (b > 9 || (b == 9 && (c > 6 || (c == 6)))))) {
return 0;
}
} else {
return 0;
}
}
let mut pluginfo = if let Some(pluginfo) = PluginInfo::new(plugin_name, plugin_desc, plugin_version) {
pluginfo
} else {
return 0;
};
let r: thread::Result<Option<Rc<_>>> = {
catch_unwind(move || {
let contexts = Rc::new(AssertUnwindSafe(Default::default()));
let mut pluginhandle = PluginHandle::new(plugin_handle, pluginfo, contexts);
let contexts = Rc::clone(&pluginhandle.contexts);
let context_hook = pluginhandle.hook_print("Close Context", c_int::min_value(), move |ph, _| {
let ctx = unsafe { ((*ph.ph.ph).hexchat_get_context)(ph.ph.ph) };
contexts.borrow_mut().remove(&ctx);
EAT_NONE
});
let contexts = Rc::clone(&pluginhandle.contexts);
let plug = T::default();
if plug.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 }) {
if !(pluginfo.name.is_null() || pluginfo.desc.is_null() || pluginfo.vers.is_null()) {
Some(Rc::new(PhUserdata { plug: Box::new(plug), pluginfo, contexts, _context_hook: context_hook }))
} else {
None
}
} else {
if !(pluginfo.name.is_null() || pluginfo.desc.is_null() || pluginfo.vers.is_null()) {
pluginfo.drop_info()
}
None
}
})
};
match r {
Result::Ok(Option::Some(plug @ _)) => {
put_userdata(plugin_handle, plug);
1
},
r @ _ => {
if let Err(_) = r {
}
0
},
}
}
#[doc(hidden)]
pub unsafe fn hexchat_plugin_deinit<'ph, T>(plugin_handle: LtPhPtr<'ph>) -> c_int where T: Plugin<'ph> {
let mut safe_to_unload = 1;
if !plugin_handle.ph.is_null() {
let ph = plugin_handle.ph as *mut RawPh;
if !(*ph).userdata.is_null() {
{
let mut info: Option<PluginInfo> = None;
{
let mut ausinfo = AssertUnwindSafe(&mut info);
safe_to_unload = if catch_unwind(move || {
let userdata = pop_userdata(plugin_handle);
let pluginfo = userdata.pluginfo;
userdata.plug.deinit(&mut PluginHandle::new(plugin_handle, pluginfo, Rc::clone(&userdata.contexts)));
drop(userdata);
**ausinfo = Some(pluginfo);
}).is_ok() { 1 } else { 0 };
}
if let Some(mut info) = info {
info.drop_info();
} else {
eprintln!("I have no idea tbh, I didn't know `pop_userdata` could panic!");
}
}
} else {
}
} else {
eprintln!("hexchat_plugin_deinit called with a null plugin_handle - broken hexchat");
}
safe_to_unload
}
#[macro_export]
macro_rules! hexchat_plugin {
($l:lifetime, $t:ty) => {
#[no_mangle]
pub unsafe extern "C" fn hexchat_plugin_init<$l>(plugin_handle: $crate::LtPhPtr<$l>,
plugin_name: *mut *const $crate::c_char,
plugin_desc: *mut *const $crate::c_char,
plugin_version: *mut *const $crate::c_char,
arg: *const $crate::c_char) -> $crate::c_int {
$crate::hexchat_plugin_init::<$l, $t>(plugin_handle, plugin_name, plugin_desc, plugin_version, arg)
}
#[no_mangle]
pub unsafe extern "C" fn hexchat_plugin_deinit<$l>(plugin_handle: $crate::LtPhPtr<$l>) -> $crate::c_int {
$crate::hexchat_plugin_deinit::<$l, $t>(plugin_handle)
}
};
}