// This file is part of Hexchat Plugin API Bindings for Rust
// Copyright (C) 2022 Soni L.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
// Based on hexchat's plugin.c
//
/* X-Chat
* Copyright (C) 2002 Peter Zelezny.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
//! Internal mock test framework.
//!
//! This API is not stable. Do not rely on this API to be stable.
use crate::internals::Ph;
use std::cell::Cell;
use std::cell::RefCell;
use std::cell::UnsafeCell;
use std::ffi::CStr;
use std::ffi::CString;
use std::marker::PhantomData;
use std::marker::PhantomPinned;
use std::pin::Pin;
use std::ptr;
use std::rc::Rc;
use ::libc::{c_char, c_int};
use ::ltptr::*;
use ::selfref::{Holder, NewWith, OperateIn, opaque};
struct Context {
}
// MUST be repr(C), despite having repr(Rust) types inside it.
/// A loaded plugin/complete plugin handle, including private fields.
#[repr(C)]
struct MockPlugin<'ph, 'env: 'ph> {
// NOTE: MUST be first thing in the struct.
// we actually pass the whole MockPlugin to the plugin, but we pretend it's
// just the Ph.
methods: Ph<'ph>,
filename: CString,
plugin_name: *const c_char,
plugin_desc: *const c_char,
plugin_vers: *const c_char,
free_strings: bool,
env: Pin<&'env PluginEnvironment<'env>>,
deinit: Option>) -> c_int>,
context: Option>,
}
struct MockPluginK<'env> {
_p: PhantomData<&'env PluginEnvironment<'env>>,
}
opaque! {
impl['env] Opaque for MockPluginK<'env> {
type Kind<'ph> = UnsafeCell>;
}
}
enum MockCallback {
HookCommand {
help: Option>,
f: 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,
},
HookServer {
f: 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,
},
HookServerAttrs {
f: unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, attrs: *const crate::internals::HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int,
},
HookPrint {
f: unsafe extern "C" fn(word: *const *const libc::c_char, user_data: *mut libc::c_void) -> libc::c_int,
},
HookPrintAttrs {
f: unsafe extern "C" fn(word: *const *const libc::c_char, attrs: *const crate::internals::HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int
},
HookTimer {
tag: libc::c_int,
f: unsafe extern "C" fn(user_data: *mut libc::c_void) -> libc::c_int,
},
HookFd {
tag: libc::c_int,
f: unsafe extern "C" fn(fd: libc::c_int, flags: libc::c_int, user_data: *mut libc::c_void) -> libc::c_int
},
HookDeleted,
}
struct MockHook<'env> {
pl: Rc>>,
//pl: Rc>,
cb: RefCell,
name: Option>,
pri_or_fd: libc::c_int,
userdata: *mut libc::c_void,
_pin: PhantomPinned,
}
struct MockHookK<'env> {
_p: PhantomData<&'env PluginEnvironment<'env>>,
}
opaque! {
impl['env] Opaque for MockHookK<'env> {
type Kind<'ph> = MockHook<'env>;
}
}
/// A plugin environment. An emulated hexchat instance.
// TODO: maybe make this a full plugin host, with <'app>?
pub struct PluginEnvironment<'env> {
/// The loaded plugins.
plugins: RefCell>>>>>,
/// Priority-based hooks.
hooks: RefCell>>>>>,
/// Timer and fd hooks.
extra_hooks: RefCell>>>>>,
/// Hook recursion depth.
depth: Cell,
/// Contexts.
contexts: RefCell>>,
}
pub struct PluginEnvironmentK;
opaque! {
impl Opaque for PluginEnvironmentK {
type Kind<'env> = PluginEnvironment<'env>;
}
}
unsafe extern "C" fn hexchat_hook_command<'ph>(
ph: MutLtPtr<'ph, Ph<'ph>>,
name: *const libc::c_char,
pri: libc::c_int,
/* CALLBACK */
callback: 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 crate::internals::HexchatHook {
let ph = MutLtPtr::from_raw(ph.as_raw() as *mut MockPlugin<'ph, '_>);
todo!();
}
unsafe extern "C" fn hexchat_hook_server<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
name: *const libc::c_char,
pri: libc::c_int,
/* CALLBACK */
callback: 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 crate::internals::HexchatHook {
todo!();
}
unsafe extern "C" fn hexchat_hook_print<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
name: *const libc::c_char,
pri: libc::c_int,
/* CALLBACK */
callback: 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 crate::internals::HexchatHook {
todo!();
}
unsafe extern "C" fn hexchat_hook_timer<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
timeout: libc::c_int,
/* CALLBACK */
callback: unsafe extern "C" fn(user_data: *mut libc::c_void) -> libc::c_int,
userdata: *mut libc::c_void) -> *const crate::internals::HexchatHook {
todo!();
}
unsafe extern "C" fn hexchat_hook_fd<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
fd: libc::c_int,
flags: libc::c_int,
/* CALLBACK */
callback: 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 crate::internals::HexchatHook {
todo!();
}
unsafe extern "C" fn hexchat_unhook<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
hook: *const crate::internals::HexchatHook) -> *const libc::c_void {
todo!();
}
unsafe extern "C" fn hexchat_print<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
text: *const libc::c_char) {
todo!();
}
pub unsafe extern "C" fn hexchat_printf<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
format: *const libc::c_char, ...) {
unimplemented!();
}
unsafe extern "C" fn hexchat_command<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
command: *const libc::c_char) {
todo!();
}
unsafe extern "C" fn hexchat_commandf<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
format: *const libc::c_char, ...) {
unimplemented!();
}
unsafe extern "C" fn hexchat_nickcmp<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
s1: *const libc::c_char,
s2: *const libc::c_char) -> libc::c_int {
todo!();
}
unsafe extern "C" fn hexchat_set_context<'ph>(
ph: MutLtPtr<'ph, Ph<'ph>>,
ctx: *const crate::internals::HexchatContext,
) -> libc::c_int {
let ctx = ctx as *const Context;
let ph: *mut MockPlugin<'_, '_> = ph.as_raw() as *mut _;
if ctx.is_null() { return 0; }
// in C, ctx must NOT be dangling, so we let miri catch it
let _ = &*ctx;
// in addition to that we also panic if ctx isn't on the list,
// because you're supposed to use "Close Context" for cleanup.
// if your plugin makes it this far, you're literally passing in garbage.
let env: Pin<&PluginEnvironment<'_>> = (*ph).env;
if env.contexts.borrow().iter().any(|e| ptr::eq(&**e, ctx)) {
(*ph).context = Some(crate::rc_clone_from_raw(ctx));
return 1;
} else {
panic!("garbage in hexchat_set_context - broken plugin")
}
}
unsafe extern "C" fn hexchat_find_context<'ph>(
ph: MutLtPtr<'ph, Ph<'ph>>,
servname: *const libc::c_char,
channel: *const libc::c_char,
) -> *const crate::internals::HexchatContext {
todo!();
}
unsafe extern "C" fn hexchat_get_context<'ph>(
ph: MutLtPtr<'ph, Ph<'ph>>,
) -> *const crate::internals::HexchatContext {
let ph: *mut MockPlugin<'_, '_> = ph.as_raw() as *mut _;
let ctx = (*ph).context.as_ref().map(|rc| Rc::as_ptr(rc));
ctx.unwrap_or(ptr::null()) as *const _
}
unsafe extern "C" fn hexchat_get_info<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
id: *const libc::c_char) -> *const libc::c_char {
todo!();
}
unsafe extern "C" fn hexchat_get_prefs<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
name: *const libc::c_char,
string: *mut *const libc::c_char,
integer: *mut libc::c_int) -> libc::c_int {
todo!();
}
unsafe extern "C" fn hexchat_list_get<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
name: *const libc::c_char) -> *mut crate::internals::HexchatList {
todo!();
}
unsafe extern "C" fn hexchat_list_free<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
xlist: *const crate::internals::HexchatList) {
todo!();
}
unsafe extern "C" fn hexchat_list_fields<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
name: *const libc::c_char) -> *const *const libc::c_char {
todo!();
}
unsafe extern "C" fn hexchat_list_next<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
xlist: *const crate::internals::HexchatList) -> libc::c_int {
todo!();
}
unsafe extern "C" fn hexchat_list_str<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
xlist: *const crate::internals::HexchatList,
name: *const libc::c_char) -> *const libc::c_char {
todo!();
}
unsafe extern "C" fn hexchat_list_int<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
xlist: *const crate::internals::HexchatList,
name: *const libc::c_char) -> libc::c_int {
todo!();
}
unsafe extern "C" fn hexchat_plugingui_add<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
filename: *const libc::c_char,
name: *const libc::c_char,
desc: *const libc::c_char,
version: *const libc::c_char,
reserved: *mut char) -> *const crate::internals::PluginGuiHandle {
todo!();
}
unsafe extern "C" fn hexchat_plugingui_remove<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
handle: *const crate::internals::PluginGuiHandle) {
todo!();
}
unsafe extern "C" fn hexchat_emit_print<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
event_name: *const libc::c_char, ...) -> libc::c_int {
todo!();
}
unsafe extern "C" fn hexchat_read_fd<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
src: *const libc::c_void,
buf: *mut char,
len: *mut libc::c_int) -> libc::c_int {
todo!();
}
unsafe extern "C" fn hexchat_list_time<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
xlist: *const crate::internals::HexchatList,
name: *const libc::c_char) -> libc::time_t {
todo!();
}
unsafe extern "C" fn hexchat_gettext<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
msgid: *const libc::c_char) -> *const libc::c_char {
todo!();
}
unsafe extern "C" fn hexchat_send_modes<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
targets: *mut *const libc::c_char,
ntargets: libc::c_int,
modes_per_line: libc::c_int,
sign: libc::c_char,
mode: libc::c_char) {
todo!();
}
unsafe extern "C" fn hexchat_strip<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
string: *const libc::c_char,
len: libc::c_int,
flags: libc::c_int) -> *const libc::c_char {
todo!();
}
unsafe extern "C" fn hexchat_free<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
ptr: *const libc::c_void) {
todo!();
}
unsafe extern "C" fn hexchat_pluginpref_set_str<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
var: *const libc::c_char,
value: *const libc::c_char) -> libc::c_int {
todo!();
}
unsafe extern "C" fn hexchat_pluginpref_get_str<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
var: *const libc::c_char,
dest: *mut char) -> libc::c_int {
todo!();
}
unsafe extern "C" fn hexchat_pluginpref_set_int<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
var: *const libc::c_char,
value: libc::c_int) -> libc::c_int {
todo!();
}
unsafe extern "C" fn hexchat_pluginpref_get_int<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
var: *const libc::c_char) -> libc::c_int {
todo!();
}
unsafe extern "C" fn hexchat_pluginpref_delete<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
var: *const libc::c_char) -> libc::c_int {
todo!();
}
unsafe extern "C" fn hexchat_pluginpref_list<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
dest: *mut char) -> libc::c_int {
todo!();
}
unsafe extern "C" fn hexchat_hook_server_attrs<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
name: *const libc::c_char,
pri: libc::c_int,
/* CALLBACK */
callback: unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, attrs: *const crate::internals::HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int,
userdata: *mut libc::c_void) -> *const crate::internals::HexchatHook {
todo!();
}
unsafe extern "C" fn hexchat_hook_print_attrs<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
name: *const libc::c_char,
pri: libc::c_int,
/* CALLBACK */
callback: unsafe extern "C" fn(word: *const *const libc::c_char, attrs: *const crate::internals::HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int,
userdata: *mut libc::c_void) -> *const crate::internals::HexchatHook {
todo!();
}
unsafe extern "C" fn hexchat_emit_print_attrs<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, attrs: *const crate::internals::HexchatEventAttrs,
event_name: *const libc::c_char, ...) -> libc::c_int {
todo!();
}
unsafe extern "C" fn hexchat_event_attrs_create<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>) -> *mut crate::internals::HexchatEventAttrs {
todo!();
}
unsafe extern "C" fn hexchat_event_attrs_free<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>,
attrs: *mut crate::internals::HexchatEventAttrs) {
todo!();
}
impl<'env> PluginEnvironment<'env> {
/// Creates a new plugin environment.
pub fn new() -> Holder<'static, PluginEnvironmentK> {
struct NewEnv;
impl<'k> NewWith<'k, PluginEnvironmentK> for NewEnv {
fn new_with<'env>(self) -> PluginEnvironment<'env> {
PluginEnvironment {
plugins: Default::default(),
hooks: Default::default(),
extra_hooks: Default::default(),
depth: Default::default(),
contexts: Default::default(),
}
}
}
Holder::new_with(NewEnv)
}
/// Loads a plugin.
pub fn load_plugin(
self: Pin<&'env Self>,
filename: CString,
arg: Option<&CStr>,
) -> Result<(), ()> {
let plug = MockPlugin::new(self, filename);
// add the plugin
self.plugins.borrow_mut().push(plug.clone());
struct MockPluginInit<'arg, T> {
arg: Option<&'arg CStr>,
_p: PhantomData
}
impl<'k, 'env: 'k, 'arg, T> OperateIn<'k, MockPluginK<'env>>
for MockPluginInit<'arg, T>
where T: PluginFactory {
type Out = bool;
fn operate_in<'ph>(
self,
ph: Pin<&'ph UnsafeCell>>,
) -> bool where 'k: 'ph {
let ptr = ph.get();
let arg = self.arg;
unsafe {
// setup deinit function
(*ptr).deinit = Some(
crate::hexchat_plugin_deinit::<'ph, T::Plugin<'ph>>
);
crate::hexchat_plugin_init::<'ph, T::Plugin<'ph>>(
MutLtPtr::from_raw(ptr as *mut _),
ptr::addr_of_mut!((*ptr).plugin_name),
ptr::addr_of_mut!((*ptr).plugin_desc),
ptr::addr_of_mut!((*ptr).plugin_vers),
arg.map(|arg| arg.as_ptr()).unwrap_or(ptr::null()),
) != 0
}
}
}
if plug.as_ref().operate_in(MockPluginInit:: {
arg,
_p: PhantomData,
}) {
Ok(())
} else {
// remove the plugin - aka retain every plugin that isn't this one
self.plugins.borrow_mut().retain(|e| {
!ptr::eq::>(&**e, &*plug)
});
Err(())
}
}
}
/// Helper for making [`MockPlugin`]s from [`crate::Plugin`]s.
pub trait PluginFactory: 'static {
/// The plugin type.
type Plugin<'a>: crate::Plugin<'a> + Default;
}
impl<'ph, 'env> MockPlugin<'ph, 'env> {
/// Creates a MockPlugin, but does not load it (yet).
fn new(
env: Pin<&'env PluginEnvironment<'env>>,
filename: CString,
) -> Pin>>> {
struct MockPluginNewWith<'env> {
filename: CString,
env: Pin<&'env PluginEnvironment<'env>>
}
impl<'k, 'env> NewWith<'k, MockPluginK<'env>>
for MockPluginNewWith<'env>
where 'env: 'k {
fn new_with<'ph>(self) -> UnsafeCell>
where 'k: 'ph {
let filename = self.filename;
let env = self.env;
UnsafeCell::new(MockPlugin {
methods: Ph {
hexchat_hook_command,
hexchat_hook_server,
hexchat_hook_print,
hexchat_hook_timer,
hexchat_hook_fd,
hexchat_unhook,
hexchat_print,
userdata: hexchat_printf as *mut _,
hexchat_command,
hexchat_commandf_do_not_use: hexchat_commandf,
hexchat_nickcmp,
hexchat_set_context,
hexchat_find_context,
hexchat_get_context,
hexchat_get_info,
hexchat_get_prefs,
hexchat_list_get,
hexchat_list_free,
hexchat_list_fields,
hexchat_list_next,
hexchat_list_str,
hexchat_list_int,
hexchat_plugingui_add,
hexchat_plugingui_remove,
hexchat_emit_print,
hexchat_read_fd,
hexchat_list_time,
hexchat_gettext,
hexchat_send_modes,
hexchat_strip,
hexchat_free,
hexchat_pluginpref_set_str,
hexchat_pluginpref_get_str,
hexchat_pluginpref_set_int,
hexchat_pluginpref_get_int,
hexchat_pluginpref_delete,
hexchat_pluginpref_list,
hexchat_hook_server_attrs,
hexchat_hook_print_attrs,
hexchat_emit_print_attrs,
hexchat_event_attrs_create,
hexchat_event_attrs_free,
},
filename: filename,
plugin_name: ptr::null(), // not initialized yet!
plugin_desc: ptr::null(),
plugin_vers: ptr::null(),
free_strings: false,
env: env,
deinit: None,
context: None,
})
}
}
let plug = Rc::pin(Holder::new_with(MockPluginNewWith {
filename,
env,
}));
struct MockPluginPreInit;
impl<'k, 'env: 'k> OperateIn<'k, MockPluginK<'env>>
for MockPluginPreInit {
type Out = ();
fn operate_in<'ph>(
self,
ph: Pin<&'ph UnsafeCell>>,
) where 'k: 'ph {
let ptr = ph.get();
unsafe {
// initialize plugin_name from filename(!)
(*ptr).plugin_name = (*ptr).filename.as_ptr();
}
}
}
plug.as_ref().operate_in(MockPluginPreInit);
plug
}
}