use crate::AviUtl2Info;
use pastey::paste;
use std::num::NonZeroIsize;
pub struct HostAppHandle<'a> {
internal: *mut aviutl2_sys::plugin2::HOST_APP_TABLE,
global_leak_manager: &'a mut crate::common::LeakManager,
is_register_plugin_done: std::sync::Arc<std::sync::atomic::AtomicBool>,
plugin_registry: &'a mut crate::generic::PluginRegistry,
is_edit_handle_available: std::sync::Arc<std::sync::atomic::AtomicBool>,
}
pub struct SubPlugin<T> {
plugin: std::marker::PhantomData<T>,
internal: std::sync::Arc<InternalReferenceHandle>,
}
struct InternalReferenceHandle {
uninitialize_fn: fn(),
}
impl Drop for InternalReferenceHandle {
fn drop(&mut self) {
(self.uninitialize_fn)();
}
}
impl<'plugin> HostAppHandle<'plugin> {
pub(crate) unsafe fn new(
internal: *mut aviutl2_sys::plugin2::HOST_APP_TABLE,
global_leak_manager: &'plugin mut crate::common::LeakManager,
is_register_plugin_done: std::sync::Arc<std::sync::atomic::AtomicBool>,
plugin_registry: &'plugin mut crate::generic::PluginRegistry,
is_edit_handle_available: std::sync::Arc<std::sync::atomic::AtomicBool>,
) -> Self {
Self {
internal,
global_leak_manager,
is_register_plugin_done,
plugin_registry,
is_edit_handle_available,
}
}
fn assert_not_killed(&self) {
if self
.is_register_plugin_done
.load(std::sync::atomic::Ordering::SeqCst)
{
panic!("This HostAppHandle is no longer valid.");
}
}
pub fn create_edit_handle(&mut self) -> crate::generic::EditHandle {
self.assert_not_killed();
let raw_handle = unsafe { ((*self.internal).create_edit_handle)() };
unsafe {
crate::generic::EditHandle::new(raw_handle, self.is_edit_handle_available.clone())
}
}
pub fn register_import_menu<F>(&mut self, name: &str, callback: F)
where
F: Fn() + 'static + Send + Sync,
{
self.register_menu_internal(name, callback, unsafe {
(*self.internal).register_import_menu_param
});
}
pub fn register_export_menu<F>(&mut self, name: &str, callback: F)
where
F: Fn() + 'static + Send + Sync,
{
self.register_menu_internal(name, callback, unsafe {
(*self.internal).register_export_menu_param
});
}
pub fn register_layer_menu<F>(&mut self, name: &str, callback: F)
where
F: Fn() + 'static + Send + Sync,
{
self.register_menu_internal(name, callback, unsafe {
(*self.internal).register_layer_menu_param
});
}
pub fn register_object_menu<F>(&mut self, name: &str, callback: F)
where
F: Fn() + 'static + Send + Sync,
{
self.register_menu_internal(name, callback, unsafe {
(*self.internal).register_object_menu_param
});
}
pub fn register_edit_menu<F>(&mut self, name: &str, callback: F)
where
F: Fn() + 'static + Send + Sync,
{
self.register_menu_internal(name, callback, unsafe {
(*self.internal).register_edit_menu_param
});
}
pub fn register_config_menu(
&mut self,
name: &str,
callback: extern "C" fn(aviutl2_sys::plugin2::HWND, aviutl2_sys::plugin2::HINSTANCE),
) {
self.assert_not_killed();
unsafe {
((*self.internal).register_config_menu)(
self.global_leak_manager.leak_as_wide_string(name),
callback,
)
};
}
pub fn register_object_item_menu<F>(&mut self, name: &str, callback: F)
where
F: Fn(crate::generic::ObjectHandle, &str, usize, &str) + 'static + Send + Sync,
{
self.assert_not_killed();
let trampoline_param: Box<F> = Box::new(callback);
let trampoline_param_ptr = Box::into_raw(trampoline_param);
let name_wide = self.global_leak_manager.leak_as_wide_string(name);
unsafe {
((*self.internal).register_object_item_menu_param)(
name_wide,
false,
trampoline_param_ptr as *mut std::ffi::c_void,
trampoline::<F>,
);
}
unsafe extern "C" fn trampoline<F>(
param: *mut std::ffi::c_void,
object_handle: aviutl2_sys::plugin2::OBJECT_HANDLE,
name_and_index: aviutl2_sys::common::LPCWSTR,
item_name: aviutl2_sys::common::LPCWSTR,
) where
F: Fn(crate::generic::ObjectHandle, &str, usize, &str) + 'static + Send + Sync,
{
let callback = unsafe { &mut *(param as *mut F) };
let object_handle = crate::generic::ObjectHandle::from(object_handle);
let name_and_index_str = unsafe { crate::common::load_wide_string(name_and_index) };
let (name_str, index) = parse_name_and_index(&name_and_index_str);
let item_name_str = unsafe { crate::common::load_wide_string(item_name) };
if let Err(panic_info) =
crate::utils::catch_unwind_with_panic_info(std::panic::AssertUnwindSafe(|| {
callback(object_handle, name_str, index, &item_name_str);
}))
{
tracing::error!(
"Panic occurred in object item menu callback: {}",
panic_info
);
let _ = crate::logger::write_error_log(&panic_info);
}
}
}
pub fn register_object_item_and_effect_menu<F>(&mut self, name: &str, callback: F)
where
F: Fn(crate::generic::ObjectHandle, &str, usize, Option<&str>) + 'static + Send + Sync,
{
self.assert_not_killed();
let trampoline_param: Box<F> = Box::new(callback);
let trampoline_param_ptr = Box::into_raw(trampoline_param);
let name_wide = self.global_leak_manager.leak_as_wide_string(name);
unsafe {
((*self.internal).register_object_item_menu_param)(
name_wide,
true,
trampoline_param_ptr as *mut std::ffi::c_void,
trampoline::<F>,
);
}
unsafe extern "C" fn trampoline<F>(
param: *mut std::ffi::c_void,
object_handle: aviutl2_sys::plugin2::OBJECT_HANDLE,
effect_name_and_index: aviutl2_sys::common::LPCWSTR,
item_name: aviutl2_sys::common::LPCWSTR,
) where
F: Fn(crate::generic::ObjectHandle, &str, usize, Option<&str>) + 'static + Send + Sync,
{
let callback = unsafe { &mut *(param as *mut F) };
let object_handle = crate::generic::ObjectHandle::from(object_handle);
let effect_name_and_index =
unsafe { crate::common::load_wide_string(effect_name_and_index) };
let item_name = (!item_name.is_null())
.then(|| unsafe { crate::common::load_wide_string(item_name) });
let (effect_name_str, index) = parse_name_and_index(&effect_name_and_index);
let item_name_option = item_name.as_deref();
if let Err(panic_info) =
crate::utils::catch_unwind_with_panic_info(std::panic::AssertUnwindSafe(|| {
callback(object_handle, effect_name_str, index, item_name_option);
}))
{
tracing::error!(
"Panic occurred in object item menu with effect callback: {}",
panic_info
);
let _ = crate::logger::write_error_log(&panic_info);
}
}
}
pub fn register_file_drop_handler<F>(
&mut self,
name: &str,
file_filters: &[crate::common::FileFilter],
callback: F,
) where
F: Fn(std::path::PathBuf) + 'static + Send + Sync,
{
self.assert_not_killed();
let callback_box = Box::new(callback);
let callback_ptr = Box::into_raw(callback_box);
let name_wide = self.global_leak_manager.leak_as_wide_string(name);
let file_filter_wide = self
.global_leak_manager
.leak_as_wide_string(&crate::common::format_file_filters(file_filters));
unsafe {
((*self.internal).register_file_drop_param_handler)(
name_wide,
file_filter_wide,
callback_ptr as *mut std::ffi::c_void,
file_drop_trampoline::<F>,
);
}
}
pub fn register_window_client<T: raw_window_handle::HasWindowHandle>(
&mut self,
name: &str,
instance: &T,
) -> Result<(), raw_window_handle::HandleError> {
self.assert_not_killed();
let raw_handle = instance.window_handle()?;
let hwnd = match raw_handle.as_raw() {
raw_window_handle::RawWindowHandle::Win32(handle) => handle.hwnd,
_ => panic!("Only Win32WindowHandle is supported"),
};
unsafe {
((*self.internal).register_window_client)(
self.global_leak_manager.leak_as_wide_string(name),
hwnd.get() as *mut std::ffi::c_void,
);
}
Ok(())
}
pub fn register_menus<T: GenericPluginMenus>(&mut self) {
self.assert_not_killed();
T::register_menus(self);
}
pub fn register_project_load_handler(
&mut self,
callback: extern "C" fn(*mut aviutl2_sys::plugin2::PROJECT_FILE),
) {
self.assert_not_killed();
unsafe {
((*self.internal).register_project_load_handler)(callback);
}
}
pub fn register_project_save_handler(
&mut self,
callback: extern "C" fn(*mut aviutl2_sys::plugin2::PROJECT_FILE),
) {
self.assert_not_killed();
unsafe {
((*self.internal).register_project_save_handler)(callback);
}
}
pub fn register_clear_cache_handler(
&mut self,
callback: extern "C" fn(*mut aviutl2_sys::plugin2::EDIT_SECTION),
) {
self.assert_not_killed();
unsafe {
((*self.internal).register_clear_cache_handler)(callback);
}
}
pub fn register_change_scene_handler(
&mut self,
callback: extern "C" fn(*mut aviutl2_sys::plugin2::EDIT_SECTION),
) {
self.assert_not_killed();
unsafe {
((*self.internal).register_change_scene_handler)(callback);
}
}
fn register_menu_internal<F>(
&mut self,
name: &str,
callback: F,
register_fn: unsafe extern "C" fn(
aviutl2_sys::common::LPCWSTR,
*mut std::ffi::c_void,
unsafe extern "C" fn(*mut std::ffi::c_void),
),
) where
F: Fn() + 'static + Send + Sync,
{
self.assert_not_killed();
let trampoline_param: Box<MenuTrampolineParam<F>> = Box::new(callback);
let trampoline_param_ptr = Box::into_raw(trampoline_param);
unsafe {
register_fn(
self.global_leak_manager.leak_as_wide_string(name),
trampoline_param_ptr as *mut std::ffi::c_void,
menu_trampoline::<F>,
);
}
}
}
fn parse_name_and_index(name_and_index: &str) -> (&str, usize) {
let Some(pos) = name_and_index.rfind(':') else {
return (name_and_index, 0);
};
let index_str = &name_and_index[pos + 1..];
if let Ok(index) = index_str.parse::<usize>() {
let name = &name_and_index[..pos];
(name, index)
} else {
(name_and_index, 0)
}
}
type MenuTrampolineParam<F> = F;
unsafe extern "C" fn menu_trampoline<F>(param: *mut std::ffi::c_void)
where
F: Fn() + 'static + Send + Sync,
{
let callback = unsafe { &mut *(param as *mut MenuTrampolineParam<F>) };
if let Err(panic_info) =
crate::utils::catch_unwind_with_panic_info(std::panic::AssertUnwindSafe(callback))
{
tracing::error!("Panic occurred in menu callback: {}", panic_info);
let _ = crate::logger::write_error_log(&panic_info);
}
}
unsafe extern "C" fn file_drop_trampoline<F>(
param: *mut std::ffi::c_void,
file_path: aviutl2_sys::common::LPCWSTR,
) where
F: Fn(std::path::PathBuf) + 'static + Send + Sync,
{
let callback = unsafe { &mut *(param as *mut F) };
let path_str = unsafe { crate::common::load_wide_string(file_path) };
let path = std::path::PathBuf::from(path_str);
if let Err(panic_info) =
crate::utils::catch_unwind_with_panic_info(std::panic::AssertUnwindSafe(|| {
callback(path);
}))
{
tracing::error!("Panic occurred in file drop callback: {}", panic_info);
let _ = crate::logger::write_error_log(&panic_info);
}
}
pub trait GenericPluginMenus {
fn register_menus(host: &mut crate::generic::HostAppHandle);
}
#[doc(inline)]
pub use aviutl2_macros::generic_menus as menus;
#[derive(Default)]
pub(crate) struct PluginRegistry {
#[cfg(feature = "input")]
input_plugins: Vec<std::sync::Arc<InternalReferenceHandle>>,
#[cfg(feature = "output")]
output_plugins: Vec<std::sync::Arc<InternalReferenceHandle>>,
#[cfg(feature = "filter")]
filter_plugins: Vec<std::sync::Arc<InternalReferenceHandle>>,
#[cfg(feature = "module")]
script_modules: Vec<std::sync::Arc<InternalReferenceHandle>>,
}
impl PluginRegistry {
pub(crate) fn new() -> Self {
Self::default()
}
}
macro_rules! impl_plugin_registry {
(
$description:literal,
$feature:literal,
$module:ident,
$name:ident,
$register_method:ident,
$PluginTrait:path,
$SingletonTrait:path,
$TableType:ty
) => {
paste! {
impl<T> SubPlugin<T> {
#[cfg(feature = $feature)]
#[doc = concat!($description, "の新しいインスタンスを作成します。")]
pub fn [<new_ $name>](info: &AviUtl2Info) -> crate::AnyResult<Self>
where
T: $PluginTrait + $SingletonTrait + 'static
{
crate::$module::__bridge::initialize_plugin::<T>(info.version.into())?;
let internal = std::sync::Arc::new(InternalReferenceHandle {
uninitialize_fn: || {
unsafe {
crate::$module::__bridge::uninitialize_plugin::<T>();
}
},
});
Ok(Self {
plugin: std::marker::PhantomData,
internal,
})
}
}
#[cfg(feature = $feature)]
impl<'a> HostAppHandle<'a> {
#[doc = concat!($description, "を登録します。")]
pub fn [<register_ $name>]<T: $PluginTrait + $SingletonTrait + 'static>(
&mut self,
handle: &SubPlugin<T>,
) {
self.assert_not_killed();
unsafe { ((*self.internal).$register_method)(crate::$module::__bridge::create_table_unwind::<T>()) };
self.plugin_registry
.[<$name s>]
.push(std::sync::Arc::clone(&handle.internal));
}
#[doc = concat!("unwindなしで", $description, "を登録します。")]
pub fn [<register_ $name _nounwind>]<T: $PluginTrait + $SingletonTrait + 'static>(
&mut self,
handle: &SubPlugin<T>,
) {
self.assert_not_killed();
unsafe { ((*self.internal).$register_method)(crate::$module::__bridge::create_table::<T>()) };
self.plugin_registry
.[<$name s>]
.push(std::sync::Arc::clone(&handle.internal));
}
}
}
};
}
impl_plugin_registry!(
"入力プラグイン",
"input",
input,
input_plugin,
register_input_plugin,
crate::input::InputPlugin,
crate::input::__bridge::InputSingleton,
aviutl2_sys::input2::INPUT_PLUGIN_TABLE
);
impl_plugin_registry!(
"出力プラグイン",
"output",
output,
output_plugin,
register_output_plugin,
crate::output::OutputPlugin,
crate::output::__bridge::OutputSingleton,
aviutl2_sys::output2::OUTPUT_PLUGIN_TABLE
);
impl_plugin_registry!(
"フィルタープラグイン",
"filter",
filter,
filter_plugin,
register_filter_plugin,
crate::filter::FilterPlugin,
crate::filter::__bridge::FilterSingleton,
aviutl2_sys::filter2::FILTER_PLUGIN_TABLE
);
#[cfg(feature = "module")]
impl<T> SubPlugin<T> {
pub fn new_script_module(info: &AviUtl2Info) -> crate::AnyResult<Self>
where
T: crate::module::ScriptModule + crate::module::__bridge::ScriptModuleSingleton + 'static,
{
crate::module::__bridge::initialize_plugin::<T>(info.version.into())?;
let internal = std::sync::Arc::new(InternalReferenceHandle {
uninitialize_fn: || unsafe {
crate::module::__bridge::uninitialize_plugin::<T>();
},
});
Ok(Self {
plugin: std::marker::PhantomData,
internal,
})
}
}
#[cfg(feature = "module")]
impl<'a> HostAppHandle<'a> {
pub fn register_script_module<
T: crate::module::ScriptModule + crate::module::__bridge::ScriptModuleSingleton + 'static,
>(
&mut self,
name: Option<&str>,
handle: &SubPlugin<T>,
) {
self.assert_not_killed();
unsafe {
if let Some(name_str) = name {
((*self.internal).register_script_module_name)(
crate::module::__bridge::create_table_unwind::<T>(),
self.global_leak_manager.leak_as_wide_string(name_str),
)
} else {
((*self.internal).register_script_module)(
crate::module::__bridge::create_table_unwind::<T>(),
)
}
};
self.plugin_registry
.script_modules
.push(std::sync::Arc::clone(&handle.internal));
}
pub fn register_script_module_nounwind<
T: crate::module::ScriptModule + crate::module::__bridge::ScriptModuleSingleton + 'static,
>(
&mut self,
name: Option<&str>,
handle: &SubPlugin<T>,
) {
self.assert_not_killed();
unsafe {
if let Some(name_str) = name {
((*self.internal).register_script_module_name)(
crate::module::__bridge::create_table::<T>(),
self.global_leak_manager.leak_as_wide_string(name_str),
)
} else {
((*self.internal).register_script_module)(
crate::module::__bridge::create_table::<T>(),
);
}
};
self.plugin_registry
.script_modules
.push(std::sync::Arc::clone(&handle.internal));
}
}
#[doc(hidden)]
pub unsafe fn __internal_rwh_from_raw(
hwnd: aviutl2_sys::plugin2::HWND,
hinstance: aviutl2_sys::plugin2::HINSTANCE,
) -> raw_window_handle::Win32WindowHandle {
let mut handle =
raw_window_handle::Win32WindowHandle::new(NonZeroIsize::new(hwnd as isize).unwrap());
handle.hinstance = Some(NonZeroIsize::new(hinstance as isize).unwrap());
handle
}