diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/internals.rs | 196 | ||||
-rw-r--r-- | src/lib.rs | 197 |
2 files changed, 393 insertions, 0 deletions
diff --git a/src/internals.rs b/src/internals.rs new file mode 100644 index 0000000..8a881e0 --- /dev/null +++ b/src/internals.rs @@ -0,0 +1,196 @@ +/* + * Hexchat Plugin API Bindings for Rust - Internals + * Copyright (C) 2018 Soni L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +//! Implementation details, mostly. +//! +//! This also includes the hexchat_plugin struct, from hexchat-plugin.h. Note that we use the +//! struct even on non-Windows platforms because it's a lot easier that way. Should be safe, tho. +use libc; + +// apparently this is the right way to do these +#[repr(i8)] +pub enum HexchatList { + __One, + __Two, +} +#[repr(i8)] +pub enum HexchatHook { + __One, + __Two, +} +#[repr(i8)] +pub enum HexchatContext { + __One, + __Two, +} + +// not in hexchat-plugin.h +#[repr(i8)] +pub enum PluginGuiHandle { + __One, + __Two, +} + +#[repr(C)] +pub struct HexchatEventAttrs { + server_time_utc: libc::time_t, +} + +pub type HexchatPlugin = Ph; + +#[repr(C)] +pub struct Ph { + pub hexchat_hook_command: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + name: *const libc::c_char, + pri: libc::c_int, + /* CALLBACK */ + callback: Option<unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, user_data: *mut libc::c_void) -> libc::c_int>, + help_text: *const libc::c_char, + userdata: *mut libc::c_void) -> *const HexchatHook>, + pub hexchat_hook_server: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + name: *const libc::c_char, + pri: libc::c_int, + /* CALLBACK */ + callback: Option<unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, user_data: *mut libc::c_void) -> libc::c_int>, + userdata: *mut libc::c_void) -> *const HexchatHook>, + pub hexchat_hook_print: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + name: *const libc::c_char, + pri: libc::c_int, + /* CALLBACK */ + callback: Option<unsafe extern "C" fn(word: *const *const libc::c_char, user_data: *mut libc::c_void) -> libc::c_int>, + userdata: *mut libc::c_void) -> *const HexchatHook>, + pub hexchat_hook_timer: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + timeout: libc::c_int, + /* CALLBACK */ + callback: Option<unsafe extern "C" fn(user_data: *mut libc::c_void) -> libc::c_int>, + userdata: *mut libc::c_void) -> *const HexchatHook>, + pub hexchat_hook_fd: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + fd: libc::c_int, + flags: libc::c_int, + /* CALLBACK */ + callback: Option<unsafe extern "C" fn(fd: libc::c_int, flags: libc::c_int, user_data: *mut libc::c_void) -> libc::c_int>, + userdata: *mut libc::c_void) -> *const HexchatHook>, + pub hexchat_unhook: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + hook: *const HexchatHook) -> *const libc::c_void>, + pub hexchat_print: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + text: *const libc::c_char)>, + pub hexchat_printf: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + format: *const libc::c_char, ...)>, + pub hexchat_command: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + command: *const libc::c_char)>, + pub hexchat_commandf: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + format: *const libc::c_char, ...)>, + pub hexchat_nickcmp: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + s1: *const libc::c_char, + s2: *const libc::c_char) -> libc::c_int>, + pub hexchat_set_context: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + ctx: *const HexchatContext) -> libc::c_int>, + pub hexchat_find_context: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + servname: *const libc::c_char, + channel: *const libc::c_char) -> *const HexchatContext>, + pub hexchat_get_context: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin) -> *const HexchatContext>, + pub hexchat_get_info: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + id: *const libc::c_char) -> *const libc::c_char>, + pub hexchat_get_prefs: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + name: *const libc::c_char, + string: *mut *const libc::c_char, + integer: *mut libc::c_int) -> libc::c_int>, + pub hexchat_list_get: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + name: *const libc::c_char) -> *const HexchatList>, + pub hexchat_list_free: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + xlist: *const HexchatList)>, + pub hexchat_list_fields: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + name: *const libc::c_char) -> *const *const libc::c_char>, + pub hexchat_list_next: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + xlist: *const HexchatList) -> libc::c_int>, + pub hexchat_list_str: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + xlist: *const HexchatList, + name: *const libc::c_char) -> *const libc::c_char>, + pub hexchat_list_int: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + xlist: *const HexchatList, + name: *const libc::c_char) -> libc::c_int>, + pub hexchat_plugingui_add: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + filename: *const libc::c_char, + name: *const libc::c_char, + desc: *const libc::c_char, + version: *const libc::c_char, + reserved: *mut char) -> *const PluginGuiHandle>, + pub hexchat_plugingui_remove: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + handle: *const PluginGuiHandle)>, + pub hexchat_emit_print: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + event_name: *const libc::c_char, ...) -> libc::c_int>, + // this is VERY NAUGHTY. + // TODO see if hexchat's gonna provide a proper userdata field at some point. + // it appears this function isn't used anywhere by hexchat so we reuse its pointer. + // on linux, it's a dummy anyway. + // another option would've been to use one of the printf functions. + // TODO test this on platforms hexchat doesn't build on, like AVR. + pub userdata: *mut libc::c_void, + /*pub hexchat_read_fd: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + src: *const libc::c_void, + buf: *mut char, + len: *mut libc::c_int) -> libc::c_int>,*/ + pub hexchat_list_time: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + xlist: *const HexchatList, + name: *const libc::c_char) -> libc::time_t>, + pub hexchat_gettext: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + msgid: *const libc::c_char) -> *const libc::c_char>, + pub hexchat_send_modes: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + targets: *mut *const libc::c_char, + ntargets: libc::c_int, + modes_per_line: libc::c_int, + sign: libc::c_char, + mode: libc::c_char)>, + pub hexchat_strip: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + string: *const libc::c_char, + len: libc::c_int, + flags: libc::c_int) -> *const libc::c_char>, + pub hexchat_free: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + ptr: *const libc::c_void)>, + pub hexchat_pluginpref_set_str: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + var: *const libc::c_char, + value: *const libc::c_char) -> libc::c_int>, + pub hexchat_pluginpref_get_str: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + var: *const libc::c_char, + dest: *mut char) -> libc::c_int>, + pub hexchat_pluginpref_set_int: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + var: *const libc::c_char, + value: libc::c_int) -> libc::c_int>, + pub hexchat_pluginpref_get_int: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + var: *const libc::c_char) -> libc::c_int>, + pub hexchat_pluginpref_delete: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + var: *const libc::c_char) -> libc::c_int>, + pub hexchat_pluginpref_list: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + dest: *mut char) -> libc::c_int>, + pub hexchat_hook_server_attrs: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + name: *const libc::c_char, + pri: libc::c_int, + /* CALLBACK */ + callback: Option<unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, attrs: *const HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int>, + userdata: *mut libc::c_void) -> *const HexchatHook>, + pub hexchat_hook_print_attrs: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + name: *const libc::c_char, + pri: libc::c_int, + /* CALLBACK */ + callback: Option<unsafe extern "C" fn(word: *const *const libc::c_char, attrs: *const HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int>, + userdata: *mut libc::c_void) -> *const HexchatHook>, + pub hexchat_emit_print_attrs: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, attrs: *const HexchatEventAttrs, + event_name: *const libc::c_char, ...) -> libc::c_int>, + pub hexchat_event_attrs_create: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin) -> *mut HexchatEventAttrs>, + pub hexchat_event_attrs_free: Option<unsafe extern "C" fn(ph: *mut HexchatPlugin, + attrs: *const HexchatEventAttrs)>, +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..85fe77f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,197 @@ +/* + * Hexchat Plugin API Bindings for Rust + * Copyright (C) 2018 Soni L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +//! <!-- TODO (placeholder) --> + +#[doc(hidden)] +pub extern crate libc; + +mod internals; + +use std::panic::catch_unwind; +use std::thread; +use std::ffi::{CString, CStr}; +use std::str::FromStr; + +const EMPTY_CSTRING_DATA: &[u8] = b"\0"; + +/// A hexchat plugin +pub trait Plugin { + fn init(&mut self, &mut PluginHandle) -> bool; +} + +/// A handle for a hexchat plugin +pub struct PluginHandle { + ph: *mut internals::Ph, + filename: Option<CString>, + registered: bool, + fakehandle: *const internals::PluginGuiHandle, +} + +impl PluginHandle { + pub fn register(&mut self, name: &str, desc: &str, ver: &str) { + unsafe { + let ph = &mut *self.ph; + if let Some(hexchat_plugingui_add) = ph.hexchat_plugingui_add { + let filename = self.filename.take().expect("Attempt to re-register a plugin"); + let name = CString::new(name).unwrap(); + let desc = CString::new(desc).unwrap(); + let ver = CString::new(ver).unwrap(); + self.fakehandle = hexchat_plugingui_add(self.ph, filename.as_ptr(), name.as_ptr(), desc.as_ptr(), ver.as_ptr(), ::std::ptr::null_mut()); + } + self.registered = true; + } + } +} + +#[doc(hidden)] +pub unsafe fn hexchat_plugin_init<T>(plugin_handle: *mut libc::c_void, + plugin_name: *mut *const libc::c_char, + plugin_desc: *mut *const libc::c_char, + plugin_version: *mut *const libc::c_char, + arg: *const libc::c_char) -> libc::c_int + where T: Plugin + Default { + if plugin_handle.is_null() { + // we can't really do anything here. + eprintln!("hexchat_plugin_init called with a null plugin_handle. This is an error!"); + // TODO maybe call abort. + return 0; + } + let ph = &mut *(plugin_handle as *mut internals::Ph); + // clear the "userdata" field first thing - if the deinit function gets called (wrong hexchat + // version, other issues), we don't wanna try to drop the hexchat_dummy or hexchat_read_fd + // function as if it were a Box! + ph.userdata = ::std::ptr::null_mut(); + // we set these to empty strings because rust strings aren't nul-terminated. which means we + // need to *allocate* nul-terminated strings for the real values, and hexchat has no way of + // freeing these. + // BUT before we set them, read the filename off plugin_name - we'll need it! + // (TODO figure out how to make this NOT break the plugins list) + let filename = CStr::from_ptr(*plugin_name).to_owned(); + let empty_cstr = CStr::from_bytes_with_nul_unchecked(EMPTY_CSTRING_DATA).as_ptr(); + *plugin_name = empty_cstr; + *plugin_desc = empty_cstr; + *plugin_version = empty_cstr; + // do some version checks for safety + if let Some(hexchat_get_info) = ph.hexchat_get_info { + let ver: *const libc::c_char = hexchat_get_info(ph, CStr::from_bytes_with_nul_unchecked(b"version\0").as_ptr()); + 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); + let b = iter.next().map(i32::from_str).and_then(Result::ok); + let c = iter.next().map(i32::from_str).and_then(Result::ok); + if !match a.unwrap_or(0) { + 0 | 1 => false, + 2 => match b.unwrap_or(0) { + // range patterns are a bit broken + 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 => false, + 9 => match c.unwrap_or(0) { + 0 | 1 | 2 | 3 | 4 | 5 => false, + 6 => + // min acceptable version = 2.9.6 + true, + _ => true, + }, + _ => true, + }, + _ => true, + } { + // TODO print: "error loading plugin: hexchat too old"? + return 0; + } + } else { + return 0; + } + } + let r: thread::Result<Option<Box<_>>> = catch_unwind(|| { + let mut plug = T::default(); + let mut pluginhandle = PluginHandle { + ph: plugin_handle as *mut _, + registered: false, + filename: Some(filename), + fakehandle: ::std::ptr::null(), + }; + if plug.init(&mut pluginhandle) { + if pluginhandle.registered { + Some(Box::new((plug, pluginhandle.fakehandle))) + } else { + // TODO log: forgot to call register + None + } + } else { + None + } + }); + if r.is_ok() { + if let Ok(Some(plug)) = r { + ph.userdata = Box::into_raw(plug) as *mut libc::c_void; + 1 + } else { + 0 + } + } else { + // TODO try to log panic? + 0 + } +} + +#[doc(hidden)] +pub unsafe fn hexchat_plugin_deinit<T>(plugin_handle: *mut libc::c_void) where T: Plugin { + // plugin_handle should never be null, but just in case... + if !plugin_handle.is_null() { + let ph = &mut *(plugin_handle as *mut internals::Ph); + if !ph.userdata.is_null() { + // + let userdata = ph.userdata; + ph.userdata = ::std::ptr::null_mut(); + // we use an explicit drop (instead of an implicit one) so this is less confusing/weird + // to read. + catch_unwind(|| { + let ph = &mut *(plugin_handle as *mut internals::Ph); + let (plug, fakehandle) = *Box::from_raw(userdata as *mut (T, *const internals::PluginGuiHandle)); + if let Some(hexchat_plugingui_remove) = ph.hexchat_plugingui_remove { + hexchat_plugingui_remove(ph, fakehandle); + } + ::std::mem::drop(plug); // suppress compiler warnings + }).ok(); + } + } else { + eprintln!("hexchat_plugin_deinit called with a null plugin_handle. This is an error!"); + } +} + +/// Exports a hexchat plugin. +#[macro_export] +macro_rules! hexchat_plugin { + ($t:ty) => { + #[no_mangle] + pub unsafe extern "C" fn hexchat_plugin_init(plugin_handle: *mut $crate::libc::c_void, + plugin_name: *mut *const $crate::libc::c_char, + plugin_desc: *mut *const $crate::libc::c_char, + plugin_version: *mut *const $crate::libc::c_char, + arg: *const $crate::libc::c_char) -> $crate::libc::c_int { + $crate::hexchat_plugin_init::<$t>(plugin_handle, plugin_name, plugin_desc, plugin_version, arg) + } + #[no_mangle] + pub unsafe extern "C" fn hexchat_plugin_deinit(plugin_handle: *mut $crate::libc::c_void) { + $crate::hexchat_plugin_deinit::<$t>(plugin_handle); + } + // unlike what the documentation states, there's no need to define hexchat_plugin_get_info. + // so we don't. it'd be impossible to make it work well with rust anyway. + }; +} |