use std::cmp::Ordering;
use std::convert::TryInto;
use std::ffi::CStr;
use std::iter;
use std::marker::PhantomData;
use std::mem;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr::{self, NonNull};
use std::time::Duration;
use time::OffsetDateTime;
use crate::context::{Context, ContextHandle};
use crate::event::print::PrintEvent;
use crate::event::server::ServerEvent;
use crate::event::EventAttrs;
use crate::ffi::{
hexchat_event_attrs, hexchat_list, int_to_result, word_to_iter, ListElem, RawPluginHandle,
};
use crate::gui::FakePluginHandle;
use crate::hook::{Eat, HookHandle, Priority, Timer};
use crate::info::private::FromInfoValue;
use crate::info::Info;
use crate::iter::{CurriedItem, LendingIterator};
use crate::list::private::FromListElem;
use crate::list::List;
use crate::mode::Sign;
use crate::pref::private::{FromPrefValue, PrefValue};
use crate::pref::Pref;
use crate::state::{catch_and_log_unwind, with_plugin_state};
use crate::str::private::AsCStrArray;
use crate::str::{HexStr, HexString, IntoCStr, IntoCStrArray};
use crate::strip::{MircColors, StrippedStr, TextAttrs};
pub trait Plugin: Default + 'static {
fn init(&self, ph: PluginHandle<'_, Self>);
fn deinit(&self, ph: PluginHandle<'_, Self>) {
let _ = ph;
}
}
#[derive(Debug)]
pub struct PluginHandle<'ph, P: 'static> {
pub(crate) raw: RawPluginHandle<'ph>,
_plugin: PhantomData<P>,
}
impl<'ph, P> Copy for PluginHandle<'ph, P> {}
impl<'ph, P> Clone for PluginHandle<'ph, P> {
fn clone(&self) -> Self {
*self
}
}
impl<'ph, P> PluginHandle<'ph, P> {
pub(crate) fn new(raw: RawPluginHandle<'ph>) -> Self {
Self {
raw,
_plugin: PhantomData,
}
}
}
impl<'ph, P> PluginHandle<'ph, P> {
pub fn print(self, text: impl IntoCStr) {
let text = text.into_cstr();
unsafe {
self.raw.hexchat_print(text.as_ptr());
}
}
pub fn command(self, cmd: impl IntoCStr) {
let cmd = cmd.into_cstr();
unsafe {
self.raw.hexchat_command(cmd.as_ptr());
}
}
pub fn emit_print<E: PrintEvent<N>, const N: usize>(
self,
event: E,
args: impl IntoCStrArray<N>,
) -> Result<(), ()> {
let _ = event;
let args = args.into_cstrs();
let args = args.as_cstr_array();
assert!(
args.len() <= 4,
"bug in hexavalent - more than 4 args from PrintEvent"
);
let args: [*const c_char; 4] = [
args.get(0).map_or_else(ptr::null, |a| a.as_ptr()),
args.get(1).map_or_else(ptr::null, |a| a.as_ptr()),
args.get(2).map_or_else(ptr::null, |a| a.as_ptr()),
args.get(3).map_or_else(ptr::null, |a| a.as_ptr()),
];
int_to_result(unsafe {
self.raw.hexchat_emit_print(
E::NAME.as_ptr(),
args[0],
args[1],
args[2],
args[3],
ptr::null::<c_char>(),
)
})
}
pub fn emit_print_attrs<E: PrintEvent<N>, const N: usize>(
self,
event: E,
attrs: EventAttrs<'_>,
args: impl IntoCStrArray<N>,
) -> Result<(), ()> {
let _ = event;
let args = args.into_cstrs();
let args = args.as_cstr_array();
assert!(
args.len() <= 4,
"bug in hexavalent - more than 4 args from PrintEvent"
);
let args: [*const c_char; 4] = [
args.get(0).map_or_else(ptr::null, |a| a.as_ptr()),
args.get(1).map_or_else(ptr::null, |a| a.as_ptr()),
args.get(2).map_or_else(ptr::null, |a| a.as_ptr()),
args.get(3).map_or_else(ptr::null, |a| a.as_ptr()),
];
int_to_result(unsafe {
let event_attrs = self.raw.hexchat_event_attrs_create();
defer! { self.raw.hexchat_event_attrs_free(event_attrs) };
ptr::write(
&mut (*event_attrs).server_time_utc as *mut _,
attrs.time().unix_timestamp(),
);
#[cfg(feature = "__unstable_ircv3_line_in_event_attrs")]
let ircv3_line = crate::str::private::IntoCStrImpl::into_cstr(attrs.ircv3_line());
#[cfg(feature = "__unstable_ircv3_line_in_event_attrs")]
ptr::write(
&mut (*event_attrs).ircv3_line as *mut _,
ircv3_line.as_ptr(),
);
self.raw.hexchat_emit_print_attrs(
event_attrs,
E::NAME.as_ptr(),
args[0],
args[1],
args[2],
args[3],
ptr::null::<c_char>(),
)
})
}
pub fn send_modes(
self,
targets: impl IntoIterator<Item = impl IntoCStr>,
sign: Sign,
mode_char: u8,
) {
let targets: Vec<_> = targets.into_iter().map(|t| t.into_cstr()).collect();
let mut targets: Vec<*const c_char> = targets.iter().map(|t| t.as_ptr()).collect();
let ntargets = targets
.len()
.try_into()
.unwrap_or_else(|e| panic!("Too many send_modes targets: {}", e));
let sign = match sign {
Sign::Add => b'+',
Sign::Remove => b'-',
} as c_char;
let mode = mode_char as c_char;
unsafe {
self.raw
.hexchat_send_modes(targets.as_mut_ptr(), ntargets, 0, sign, mode)
}
}
pub fn send_mode(self, target: impl IntoCStr, sign: Sign, mode_char: u8) {
let target = target.into_cstr();
let mut targets: [*const c_char; 1] = [target.as_ptr()];
let ntargets = 1;
let sign = match sign {
Sign::Add => b'+',
Sign::Remove => b'-',
} as c_char;
let mode = mode_char as c_char;
unsafe {
self.raw
.hexchat_send_modes(targets.as_mut_ptr(), ntargets, 0, sign, mode)
}
}
pub fn nickcmp(self, s1: impl IntoCStr, s2: impl IntoCStr) -> Ordering {
let s1 = s1.into_cstr();
let s2 = s2.into_cstr();
let ordering = unsafe { self.raw.hexchat_nickcmp(s1.as_ptr(), s2.as_ptr()) };
ordering.cmp(&0)
}
pub fn strip(
self,
str: impl IntoCStr,
mirc: MircColors,
attrs: TextAttrs,
) -> Result<StrippedStr<'ph>, ()> {
let str = str.into_cstr();
let mirc_flag = match mirc {
MircColors::Keep => 0,
MircColors::Remove => 1,
};
let attrs_flag = match attrs {
TextAttrs::Keep => 0,
TextAttrs::Remove => 1,
} << 1;
let flags = mirc_flag | attrs_flag;
let stripped_ptr = unsafe { self.raw.hexchat_strip(str.as_ptr(), -1, flags) };
let stripped_ptr = match NonNull::new(stripped_ptr) {
Some(stripped_ptr) => stripped_ptr,
None => return Err(()),
};
let raw_str = unsafe { CStr::from_ptr(stripped_ptr.as_ptr()) };
let stripped = unsafe { StrippedStr::new(self.raw, raw_str) }
.unwrap_or_else(|e| panic!("Invalid UTF8 from `hexchat_strip`: {}", e));
Ok(stripped)
}
}
impl<'ph, P> PluginHandle<'ph, P> {
pub fn get_info<I: Info>(self, info: I) -> <I as Info>::Type {
self.get_info_with(info, FromInfoValue::from_info_value)
}
fn get_info_with<I: Info, R>(
self,
info: I,
f: fn(Option<&HexStr>) -> R,
) -> R {
let _ = info;
let ptr = unsafe { self.raw.hexchat_get_info(I::NAME.as_ptr()) };
if ptr.is_null() {
return f(None);
}
let str = unsafe { CStr::from_ptr(ptr) };
let str = HexStr::from_cstr(str)
.unwrap_or_else(|e| panic!("Invalid UTF8 from `hexchat_get_info`: {}", e));
f(Some(str))
}
pub fn get_pref<Pr: Pref>(self, pref: Pr) -> Result<<Pr as Pref>::Type, ()> {
self.get_pref_value_with(pref, |value| value.and_then(FromPrefValue::from_pref_value))
}
fn get_pref_value_with<Pr: Pref, R>(
self,
pref: Pr,
f: fn(Result<PrefValue<'_>, ()>) -> R,
) -> R {
let _ = pref;
let mut string = ptr::null();
let mut int = 0;
let result = unsafe {
self.raw
.hexchat_get_prefs(Pr::NAME.as_ptr(), &mut string, &mut int)
};
let value = match result {
1 => {
assert!(!string.is_null());
let str = unsafe { CStr::from_ptr(string) };
let str = HexStr::from_cstr(str)
.unwrap_or_else(|e| panic!("Invalid UTF8 from `hexchat_get_prefs`: {}", e));
PrefValue::Str(str)
}
2 => PrefValue::Int(int),
3 => PrefValue::Bool(int != 0),
_ => return f(Err(())),
};
f(Ok(value))
}
pub fn get_list<L: List>(
self,
list: L,
) -> Result<impl Iterator<Item = <L as List>::Elem> + 'ph, ()> {
let mut iter = unsafe { self.get_list_iter(list) }?;
Ok(iter::from_fn(move || {
iter.next().map(FromListElem::from_list_elem)
}))
}
#[allow(dead_code)] fn get_list_with<L: List, R>(
self,
list: L,
f: fn(
Result<
&mut dyn LendingIterator<Item = dyn for<'a> CurriedItem<'a, Item = ListElem<'a>>>,
(),
>,
) -> R,
) -> R {
let iter = unsafe { self.get_list_iter(list) };
match iter {
Ok(mut iter) => f(Ok(&mut iter)),
Err(e) => f(Err(e)),
}
}
unsafe fn get_list_iter<L: List>(
self,
list: L,
) -> Result<
impl LendingIterator<Item = dyn for<'a> CurriedItem<'a, Item = ListElem<'a>>> + 'ph,
(),
> {
let _ = list;
let list_ptr = unsafe { self.raw.hexchat_list_get(L::NAME.as_ptr()) };
let list_ptr = match NonNull::new(list_ptr) {
Some(list_ptr) => list_ptr,
None => return Err(()),
};
struct ListElemIter<'ph> {
raw: RawPluginHandle<'ph>,
list_ptr: NonNull<hexchat_list>,
}
impl<'ph> Drop for ListElemIter<'ph> {
fn drop(&mut self) {
unsafe { self.raw.hexchat_list_free(self.list_ptr.as_ptr()) };
}
}
impl<'ph> LendingIterator for ListElemIter<'ph> {
type Item = dyn for<'a> CurriedItem<'a, Item = ListElem<'a>>;
fn next<'a>(&'a mut self) -> Option<ListElem<'a>> {
if unsafe { self.raw.hexchat_list_next(self.list_ptr.as_ptr()) } == 0 {
return None;
}
let elem = unsafe { ListElem::<'a>::new(self.raw, self.list_ptr) };
Some(elem)
}
}
Ok(ListElemIter {
raw: self.raw,
list_ptr,
})
}
}
impl<'ph, P> PluginHandle<'ph, P> {
pub fn hook_command(
self,
name: impl IntoCStr,
help_text: impl IntoCStr,
priority: Priority,
callback: fn(plugin: &P, ph: PluginHandle<'_, P>, words: &[&HexStr]) -> Eat,
) -> HookHandle {
extern "C" fn hook_command_callback<P: 'static>(
word: *mut *mut c_char,
_word_eol: *mut *mut c_char,
user_data: *mut c_void,
) -> c_int {
catch_and_log_unwind("hook_command_callback", || {
let callback: fn(plugin: &P, ph: PluginHandle<'_, P>, words: &[&HexStr]) -> Eat =
unsafe { mem::transmute(user_data) };
let word = unsafe { word_to_iter(&word) };
let mut words = [HexStr::EMPTY; 32];
for (ws, w) in words.iter_mut().zip(word) {
*ws = w;
}
with_plugin_state(|plugin, ph| callback(plugin, ph, &words))
})
.unwrap_or(Eat::None) as c_int
}
let name = name.into_cstr();
let help_text = help_text.into_cstr();
let hook = unsafe {
self.raw.hexchat_hook_command(
name.as_ptr(),
priority as c_int,
hook_command_callback::<P>,
help_text.as_ptr(),
callback as *mut c_void,
)
};
let hook = NonNull::new(hook)
.unwrap_or_else(|| panic!("Hook handle was null, should be infallible"));
unsafe { HookHandle::new(hook) }
}
pub fn hook_print<E: PrintEvent<N>, const N: usize>(
self,
event: E,
priority: Priority,
callback: fn(plugin: &P, ph: PluginHandle<'_, P>, args: [&HexStr; N]) -> Eat,
) -> HookHandle {
extern "C" fn hook_print_callback<P: 'static, E: PrintEvent<N>, const N: usize>(
word: *mut *mut c_char,
user_data: *mut c_void,
) -> c_int {
catch_and_log_unwind("hook_print_callback", || {
let callback: fn(plugin: &P, ph: PluginHandle<'_, P>, args: [&HexStr; N]) -> Eat =
unsafe { mem::transmute(user_data) };
let word = unsafe { word_to_iter(&word) };
let args = E::args_from_words(word, iter::empty());
with_plugin_state(|plugin, ph| callback(plugin, ph, args))
})
.unwrap_or(Eat::None) as c_int
}
let _ = event;
let hook = unsafe {
self.raw.hexchat_hook_print(
E::NAME.as_ptr(),
priority as c_int,
hook_print_callback::<P, E, N>,
callback as *mut c_void,
)
};
let hook = NonNull::new(hook)
.unwrap_or_else(|| panic!("Hook handle was null, should be infallible"));
unsafe { HookHandle::new(hook) }
}
pub fn hook_print_attrs<E: PrintEvent<N>, const N: usize>(
self,
event: E,
priority: Priority,
callback: fn(
plugin: &P,
ph: PluginHandle<'_, P>,
attrs: EventAttrs<'_>,
args: [&HexStr; N],
) -> Eat,
) -> HookHandle {
extern "C" fn hook_print_attrs_callback<P: 'static, E: PrintEvent<N>, const N: usize>(
word: *mut *mut c_char,
attrs: *mut hexchat_event_attrs,
user_data: *mut c_void,
) -> c_int {
catch_and_log_unwind("hook_print_attrs_callback", || {
let callback: fn(
plugin: &P,
ph: PluginHandle<'_, P>,
attrs: EventAttrs<'_>,
args: [&HexStr; N],
) -> Eat = unsafe { mem::transmute(user_data) };
let timestamp = unsafe { (*attrs).server_time_utc };
let timestamp =
OffsetDateTime::from_unix_timestamp(timestamp).unwrap_or_else(|e| {
panic!("Invalid timestamp from `hexchat_event_attrs`: {}", e)
});
#[cfg(feature = "__unstable_ircv3_line_in_event_attrs")]
let ircv3_line = unsafe { CStr::from_ptr((*attrs).ircv3_line) }
.to_str()
.unwrap_or_else(|e| panic!("Invalid UTF8 from `hexchat_event_attrs`: {}", e));
let attrs = EventAttrs::new(
timestamp,
#[cfg(feature = "__unstable_ircv3_line_in_event_attrs")]
ircv3_line,
);
let word = unsafe { word_to_iter(&word) };
let args = E::args_from_words(word, iter::empty());
with_plugin_state(|plugin, ph| callback(plugin, ph, attrs, args))
})
.unwrap_or(Eat::None) as c_int
}
let _ = event;
let hook = unsafe {
self.raw.hexchat_hook_print_attrs(
E::NAME.as_ptr(),
priority as c_int,
hook_print_attrs_callback::<P, E, N>,
callback as *mut c_void,
)
};
let hook = NonNull::new(hook)
.unwrap_or_else(|| panic!("Hook handle was null, should be infallible"));
unsafe { HookHandle::new(hook) }
}
pub fn hook_server<E: ServerEvent<N>, const N: usize>(
self,
event: E,
priority: Priority,
callback: fn(plugin: &P, ph: PluginHandle<'_, P>, args: [&HexStr; N]) -> Eat,
) -> HookHandle {
extern "C" fn hook_server_callback<P: 'static, E: ServerEvent<N>, const N: usize>(
word: *mut *mut c_char,
word_eol: *mut *mut c_char,
user_data: *mut c_void,
) -> c_int {
catch_and_log_unwind("hook_server_callback", || {
let callback: fn(plugin: &P, ph: PluginHandle<'_, P>, args: [&HexStr; N]) -> Eat =
unsafe { mem::transmute(user_data) };
let word = unsafe { word_to_iter(&word) };
let word_eol = unsafe { word_to_iter(&word_eol) };
let args = E::args_from_words(word, word_eol);
with_plugin_state(|plugin, ph| callback(plugin, ph, args))
})
.unwrap_or(Eat::None) as c_int
}
let _ = event;
let hook = unsafe {
self.raw.hexchat_hook_server(
E::NAME.as_ptr(),
priority as c_int,
hook_server_callback::<P, E, N>,
callback as *mut c_void,
)
};
let hook = NonNull::new(hook)
.unwrap_or_else(|| panic!("Hook handle was null, should be infallible"));
unsafe { HookHandle::new(hook) }
}
pub fn hook_server_attrs<E: ServerEvent<N>, const N: usize>(
self,
event: E,
priority: Priority,
callback: fn(
plugin: &P,
ph: PluginHandle<'_, P>,
attrs: EventAttrs<'_>,
args: [&HexStr; N],
) -> Eat,
) -> HookHandle {
extern "C" fn hook_server_attrs_callback<P: 'static, E: ServerEvent<N>, const N: usize>(
word: *mut *mut c_char,
word_eol: *mut *mut c_char,
attrs: *mut hexchat_event_attrs,
user_data: *mut c_void,
) -> c_int {
catch_and_log_unwind("hook_server_attrs_callback", || {
let callback: fn(
plugin: &P,
ph: PluginHandle<'_, P>,
attrs: EventAttrs<'_>,
args: [&HexStr; N],
) -> Eat = unsafe { mem::transmute(user_data) };
let timestamp = unsafe { (*attrs).server_time_utc };
let timestamp =
OffsetDateTime::from_unix_timestamp(timestamp).unwrap_or_else(|e| {
panic!("Invalid timestamp from `hexchat_event_attrs`: {}", e)
});
#[cfg(feature = "__unstable_ircv3_line_in_event_attrs")]
let ircv3_line = unsafe { CStr::from_ptr((*attrs).ircv3_line) }
.to_str()
.unwrap_or_else(|e| panic!("Invalid UTF8 from `hexchat_event_attrs`: {}", e));
let attrs = EventAttrs::new(
timestamp,
#[cfg(feature = "__unstable_ircv3_line_in_event_attrs")]
ircv3_line,
);
let word = unsafe { word_to_iter(&word) };
let word_eol = unsafe { word_to_iter(&word_eol) };
let args = E::args_from_words(word, word_eol);
with_plugin_state(|plugin, ph| callback(plugin, ph, attrs, args))
})
.unwrap_or(Eat::None) as c_int
}
let _ = event;
let hook = unsafe {
self.raw.hexchat_hook_server_attrs(
E::NAME.as_ptr(),
priority as c_int,
hook_server_attrs_callback::<P, E, N>,
callback as *mut c_void,
)
};
let hook = NonNull::new(hook)
.unwrap_or_else(|| panic!("Hook handle was null, should be infallible"));
unsafe { HookHandle::new(hook) }
}
pub fn hook_timer(
self,
timeout: Duration,
callback: fn(plugin: &P, ph: PluginHandle<'_, P>) -> Timer,
) -> HookHandle {
extern "C" fn hook_timer_callback<P: 'static>(user_data: *mut c_void) -> c_int {
catch_and_log_unwind("hook_timer_callback", || {
let callback: fn(plugin: &P, ph: PluginHandle<'_, P>) -> Timer =
unsafe { mem::transmute(user_data) };
with_plugin_state(callback)
})
.unwrap_or(Timer::Stop) as c_int
}
let milliseconds = timeout
.as_millis()
.try_into()
.unwrap_or_else(|e| panic!("Timeout duration too long: {}", e));
let hook = unsafe {
self.raw.hexchat_hook_timer(
milliseconds,
hook_timer_callback::<P>,
callback as *mut c_void,
)
};
let hook = NonNull::new(hook)
.unwrap_or_else(|| panic!("Hook handle was null, should be infallible"));
unsafe { HookHandle::new(hook) }
}
pub fn unhook(self, hook: HookHandle) {
let hook = hook.into_raw();
let _ = unsafe { self.raw.hexchat_unhook(hook.as_ptr()) };
}
}
impl<'ph, P> PluginHandle<'ph, P> {
pub fn find_context<S>(self, find: Context<S>) -> Option<ContextHandle<'ph>>
where
S: IntoCStr,
{
let servname = find.servname.map(|s| s.into_cstr());
let channel = find.channel.map(|c| c.into_cstr());
let servname = servname.as_ref().map_or_else(ptr::null, |s| s.as_ptr());
let channel = channel.as_ref().map_or_else(ptr::null, |c| c.as_ptr());
let context = unsafe { self.raw.hexchat_find_context(servname, channel) };
NonNull::new(context).map(|c| unsafe { ContextHandle::new(c) })
}
pub fn with_context<R>(self, context: ContextHandle<'_>, f: impl FnOnce() -> R) -> R {
let old_context = unsafe { self.raw.hexchat_get_context() };
int_to_result(unsafe { self.raw.hexchat_set_context(context.into_raw().as_ptr()) })
.unwrap_or_else(|_| panic!("Channel invalidated while plugin running"));
defer! {
int_to_result(unsafe { self.raw.hexchat_set_context( old_context) })
.unwrap_or_else(|_| panic!("Failed to switch back to original context"))
};
f()
}
}
impl<'ph, P> PluginHandle<'ph, P> {
pub fn pluginpref_set_str(self, name: impl IntoCStr, value: impl IntoCStr) -> Result<(), ()> {
let name = name.into_cstr();
let value = value.into_cstr();
if value.to_bytes_with_nul().len() > 512 {
return Err(());
}
int_to_result(unsafe {
self.raw
.hexchat_pluginpref_set_str(name.as_ptr(), value.as_ptr())
})
}
pub fn pluginpref_get_str(self, name: impl IntoCStr) -> Result<HexString, ()> {
self.pluginpref_get_str_with(name, |pref| pref.map(ToOwned::to_owned))
}
pub fn pluginpref_get_str_with<R>(
self,
name: impl IntoCStr,
f: impl FnOnce(Result<&HexStr, ()>) -> R,
) -> R {
let name = name.into_cstr();
let mut buf = [0; 512];
let res = int_to_result(unsafe {
self.raw
.hexchat_pluginpref_get_str(name.as_ptr(), buf.as_mut_ptr())
});
if let Err(()) = res {
return f(Err(()));
}
let buf = buf.map(|x| x as u8);
let str = CStr::from_bytes_until_nul(&buf)
.unwrap_or_else(|e| panic!("Buffer overrun in `hexchat_pluginpref_get_str`: {}", e));
let str = HexStr::from_cstr(str)
.unwrap_or_else(|e| panic!("Invalid UTF8 from `hexchat_pluginpref_get_str`: {}", e));
f(Ok(str))
}
pub fn pluginpref_set_int(self, name: impl IntoCStr, value: i32) -> Result<(), ()> {
let name = name.into_cstr();
int_to_result(unsafe { self.raw.hexchat_pluginpref_set_int(name.as_ptr(), value) })
}
pub fn pluginpref_get_int(self, name: impl IntoCStr) -> Result<i32, ()> {
let name = name.into_cstr();
let value = unsafe { self.raw.hexchat_pluginpref_get_int(name.as_ptr()) };
match value {
-1 => Err(()),
_ => Ok(value),
}
}
pub fn pluginpref_delete(self, name: impl IntoCStr) -> Result<(), ()> {
let name = name.into_cstr();
int_to_result(unsafe { self.raw.hexchat_pluginpref_delete(name.as_ptr()) })
}
pub fn pluginpref_list(self) -> Result<Vec<String>, ()> {
self.pluginpref_list_with(
#[inline(always)]
|prefs| prefs.map(|p| p.map(ToOwned::to_owned).collect()),
)
}
pub fn pluginpref_list_with<R>(
self,
f: impl FnOnce(Result<&mut dyn Iterator<Item = &str>, ()>) -> R,
) -> R {
let mut buf = [0; 4096];
let res = int_to_result(unsafe { self.raw.hexchat_pluginpref_list(buf.as_mut_ptr()) });
if let Err(()) = res {
return f(Err(()));
}
let buf = buf.map(|x| x as u8);
let str = CStr::from_bytes_until_nul(&buf)
.unwrap_or_else(|e| panic!("Buffer overrun in `hexchat_pluginpref_list`: {}", e))
.to_str()
.unwrap_or_else(|e| panic!("Invalid UTF8 from `hexchat_pluginpref_list`: {}", e));
let str = str.trim_end_matches(',');
match str {
"" => f(Ok(&mut iter::empty())),
_ => f(Ok(&mut str.split(','))),
}
}
}
impl<'ph, P> PluginHandle<'ph, P> {
pub fn plugingui_add(
self,
filename: impl IntoCStr,
name: impl IntoCStr,
desc: impl IntoCStr,
version: impl IntoCStr,
) -> FakePluginHandle {
let filename = filename.into_cstr();
let name = name.into_cstr();
let desc = desc.into_cstr();
let version = version.into_cstr();
let gui = unsafe {
self.raw.hexchat_plugingui_add(
filename.as_ptr(),
name.as_ptr(),
desc.as_ptr(),
version.as_ptr(),
ptr::null_mut(),
)
};
let gui = NonNull::new(gui)
.unwrap_or_else(|| panic!("GUI handle was null, should be infallible"));
unsafe { FakePluginHandle::new(gui) }
}
pub fn plugingui_remove(self, gui: FakePluginHandle) {
let gui = gui.into_raw();
unsafe { self.raw.hexchat_plugingui_remove(gui.as_ptr()) };
}
}