#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(feature = "doc", doc = document_features::document_features!())]
use core::str;
use std::{
cell::{Cell, OnceCell, UnsafeCell},
ffi::{CString, c_void},
mem,
ops::Deref,
slice,
};
use bon::Builder;
use everything_ipc::IpcWindow;
use tracing::{debug, trace};
use crate::data::Config;
pub use everything_ipc as ipc;
pub use serde;
pub mod data;
#[cfg(feature = "tracing")]
pub mod log;
pub mod macros;
pub mod sys;
pub mod ui;
pub trait PluginApp: 'static {
type Config: Config;
fn new(config: Option<Self::Config>) -> Self;
fn start(&self) {}
fn config(&self) -> &Self::Config;
fn into_config(self) -> Self::Config;
}
#[derive(Builder)]
pub struct PluginHandler<A: PluginApp> {
#[builder(skip)]
host: OnceCell<PluginHost>,
#[builder(with = |x: impl Into<String>| CString::new(x.into()).unwrap())]
name: Option<CString>,
#[builder(with = |x: impl Into<String>| CString::new(x.into()).unwrap())]
description: Option<CString>,
#[builder(with = |x: impl Into<String>| CString::new(x.into()).unwrap())]
author: Option<CString>,
#[builder(with = |x: impl Into<String>| CString::new(x.into()).unwrap())]
version: Option<CString>,
#[builder(with = |x: impl Into<String>| CString::new(x.into()).unwrap())]
link: Option<CString>,
#[builder(skip)]
app: UnsafeCell<Option<A>>,
#[builder(default)]
options_pages: Vec<ui::OptionsPage<A>>,
#[builder(skip)]
options_message: Cell<ui::OptionsMessage>,
#[builder(skip)]
instance_name: UnsafeCell<Option<String>>,
}
unsafe impl<A: PluginApp> Send for PluginHandler<A> {}
unsafe impl<A: PluginApp> Sync for PluginHandler<A> {}
impl<A: PluginApp> PluginHandler<A> {
pub fn init_start(&self) {
self.handle(sys::EVERYTHING_PLUGIN_PM_INIT, 0 as _);
self.handle(sys::EVERYTHING_PLUGIN_PM_START, 0 as _);
}
pub fn init_start_with_config(&self, config: A::Config) {
self.handle(sys::EVERYTHING_PLUGIN_PM_INIT, 0 as _);
self.handle(
sys::EVERYTHING_PLUGIN_PM_START,
Box::into_raw(Box::new(config)) as _,
);
}
pub fn stop_kill(&self) {
self.handle(sys::EVERYTHING_PLUGIN_PM_STOP, 0 as _);
self.handle(sys::EVERYTHING_PLUGIN_PM_KILL, 0 as _);
}
pub fn get_host(&self) -> Option<&PluginHost> {
self.host.get()
}
pub fn host(&self) -> &PluginHost {
debug_assert!(self.get_host().is_some(), "Plugin host not inited");
unsafe { self.get_host().unwrap_unchecked() }
}
#[cfg(feature = "rust-i18n")]
fn init_i18n(data: *mut c_void) {
let language = if !data.is_null() {
let host = unsafe { PluginHost::from_data(data) };
host.config_get_language_name()
} else {
PluginHost::get_thread_language_name()
};
rust_i18n::set_locale(&language);
}
pub fn handle_init_i18n(_msg: u32, _data: *mut c_void) {
#[cfg(feature = "rust-i18n")]
if _msg == sys::EVERYTHING_PLUGIN_PM_INIT {
use std::sync::Once;
static INIT: Once = Once::new();
if INIT.is_completed() && !_data.is_null() {
debug!("i18n reinit");
Self::init_i18n(_data);
}
INIT.call_once(|| {
Self::init_i18n(_data);
});
}
}
pub fn handle(&self, msg: u32, data: *mut c_void) -> *mut c_void {
match msg {
sys::EVERYTHING_PLUGIN_PM_INIT => {
if !self.app_is_some() {
#[cfg(feature = "tracing")]
let _ = log::tracing_try_init();
debug!("Plugin init");
} else {
debug!("Plugin reinit");
self.app_into_config();
}
if !data.is_null() {
_ = self.host.set(unsafe { PluginHost::from_data(data) });
}
*unsafe { &mut *self.instance_name.get() } =
PluginHost::instance_name_from_main_thread();
debug!(instance_name = ?self.instance_name());
1 as _
}
sys::EVERYTHING_PLUGIN_PM_GET_PLUGIN_VERSION => sys::EVERYTHING_PLUGIN_VERSION as _,
sys::EVERYTHING_PLUGIN_PM_GET_NAME => {
debug!("Plugin get name");
match &self.name {
Some(name) => name.as_ptr() as _,
None => 0 as _,
}
}
sys::EVERYTHING_PLUGIN_PM_GET_DESCRIPTION => {
debug!("Plugin get description");
match &self.description {
Some(description) => description.as_ptr() as _,
None => 0 as _,
}
}
sys::EVERYTHING_PLUGIN_PM_GET_AUTHOR => {
debug!("Plugin get author");
match &self.author {
Some(author) => author.as_ptr() as _,
None => 0 as _,
}
}
sys::EVERYTHING_PLUGIN_PM_GET_VERSION => {
debug!("Plugin get version");
match &self.version {
Some(version) => version.as_ptr() as _,
None => 0 as _,
}
}
sys::EVERYTHING_PLUGIN_PM_GET_LINK => {
debug!("Plugin get link");
match &self.link {
Some(link) => link.as_ptr() as _,
None => 0 as _,
}
}
sys::EVERYTHING_PLUGIN_PM_START => {
debug!("Plugin start");
self.app_new(self.load_settings(data));
1 as _
}
sys::EVERYTHING_PLUGIN_PM_STOP => {
debug!("Plugin stop");
1 as _
}
sys::EVERYTHING_PLUGIN_PM_UNINSTALL => {
debug!("Plugin uninstall");
1 as _
}
sys::EVERYTHING_PLUGIN_PM_KILL => {
debug!("Plugin kill");
self.app_into_config();
1 as _
}
sys::EVERYTHING_PLUGIN_PM_ADD_OPTIONS_PAGES => self.add_options_pages(data),
sys::EVERYTHING_PLUGIN_PM_LOAD_OPTIONS_PAGE => self.load_options_page(data),
sys::EVERYTHING_PLUGIN_PM_SAVE_OPTIONS_PAGE => self.save_options_page(data),
sys::EVERYTHING_PLUGIN_PM_GET_OPTIONS_PAGE_MINMAX => self.get_options_page_minmax(data),
sys::EVERYTHING_PLUGIN_PM_SIZE_OPTIONS_PAGE => self.size_options_page(data),
sys::EVERYTHING_PLUGIN_PM_OPTIONS_PAGE_PROC => self.options_page_proc(data),
sys::EVERYTHING_PLUGIN_PM_KILL_OPTIONS_PAGE => self.kill_options_page(data),
sys::EVERYTHING_PLUGIN_PM_SAVE_SETTINGS => self.save_settings(data),
_ => {
debug!(msg, ?data, "Plugin message");
0 as _
}
}
}
pub fn instance_name(&self) -> Option<&str> {
unsafe { &*self.instance_name.get() }.as_deref()
}
fn app_is_some(&self) -> bool {
let app = unsafe { &*self.app.get() };
app.is_some()
}
fn app_new(&self, config: Option<A::Config>) {
let app = unsafe { &mut *self.app.get() };
debug_assert!(app.is_none(), "App already inited");
*app = Some(A::new(config));
unsafe { app.as_ref().unwrap_unchecked() }.start();
}
fn app_into_config(&self) -> A::Config {
let app = unsafe { &mut *self.app.get() };
match app.take() {
Some(app) => app.into_config(),
None => unreachable!("App not inited"),
}
}
pub unsafe fn app(&self) -> &A {
unsafe { &*self.app.get() }
.as_ref()
.expect("App not inited")
}
pub fn with_app<T>(&self, f: impl FnOnce(&A) -> T) -> T {
f(unsafe { self.app() })
}
}
pub struct PluginHost {
get_proc_address: sys::everything_plugin_get_proc_address_t,
}
impl PluginHost {
pub fn new(get_proc_address: sys::everything_plugin_get_proc_address_t) -> Self {
Self { get_proc_address }
}
pub unsafe fn from_data(data: *mut c_void) -> Self {
Self::new(unsafe { mem::transmute(data) })
}
fn get_proc_address(
&self,
) -> unsafe extern "system" fn(
name: *const sys::everything_plugin_utf8_t,
) -> *mut ::std::os::raw::c_void {
unsafe { self.get_proc_address.unwrap_unchecked() }
}
pub unsafe fn get<T: Copy>(&self, name: &str) -> Option<T> {
assert_eq!(mem::size_of::<T>(), mem::size_of::<fn()>());
trace!(name, "Plugin host get proc address");
let name = CString::new(name).unwrap();
let ptr = unsafe { (self.get_proc_address())(name.as_ptr() as _) };
if ptr.is_null() {
None
} else {
Some(unsafe { mem::transmute_copy(&ptr) })
}
}
pub fn utf8_buf_init(&self, cbuf: *mut sys::everything_plugin_utf8_buf_t) {
let utf8_buf_init: unsafe extern "system" fn(cbuf: *mut sys::everything_plugin_utf8_buf_t) =
unsafe { self.get("utf8_buf_init").unwrap_unchecked() };
unsafe { utf8_buf_init(cbuf) };
}
pub fn utf8_buf_kill(&self, cbuf: *mut sys::everything_plugin_utf8_buf_t) {
let utf8_buf_kill: unsafe extern "system" fn(cbuf: *mut sys::everything_plugin_utf8_buf_t) =
unsafe { self.get("utf8_buf_kill").unwrap_unchecked() };
unsafe { utf8_buf_kill(cbuf) };
}
pub fn utf8_buf_into_string(&self, cbuf: *mut sys::everything_plugin_utf8_buf_t) -> String {
let s = unsafe { (*cbuf).to_string() };
self.utf8_buf_kill(cbuf);
s
}
pub fn ipc_window_from_main_thread() -> Option<IpcWindow> {
IpcWindow::from_current_thread()
}
pub fn instance_name_from_main_thread() -> Option<String> {
let ipc_window = Self::ipc_window_from_main_thread();
ipc_window.and_then(|w| w.instance_name().map(|s| s.to_string()))
}
}
impl Deref for sys::everything_plugin_utf8_buf_t {
type Target = str;
fn deref(&self) -> &Self::Target {
unsafe {
str::from_utf8_unchecked(slice::from_raw_parts(self.buf, self.len))
}
}
}