/*
* 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 .
*/
//!
#[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,
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(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