use crate::{AviUtl2Info, generic::EditSection};
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,
kill_switch: std::sync::Arc<std::sync::atomic::AtomicBool>,
plugin_registry: &'a mut crate::generic::PluginRegistry,
}
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<'a> HostAppHandle<'a> {
pub(crate) unsafe fn new(
internal: *mut aviutl2_sys::plugin2::HOST_APP_TABLE,
global_leak_manager: &'a mut crate::common::LeakManager,
kill_switch: std::sync::Arc<std::sync::atomic::AtomicBool>,
plugin_registry: &'a mut crate::generic::PluginRegistry,
) -> Self {
Self {
internal,
global_leak_manager,
kill_switch,
plugin_registry,
}
}
fn assert_not_killed(&self) {
if self.kill_switch.load(std::sync::atomic::Ordering::SeqCst) {
panic!("This HostAppHandle is no longer valid.");
}
}
pub fn set_plugin_information(&mut self, information: &str) {
self.assert_not_killed();
let information = if cfg!(debug_assertions) {
format!("{information} (Debug Build)")
} else {
information.to_string()
};
unsafe {
((*self.internal).set_plugin_information)(
self.global_leak_manager.leak_as_wide_string(&information),
)
}
}
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) }
}
pub fn register_import_menu(
&mut self,
name: &str,
callback: extern "C" fn(*mut aviutl2_sys::plugin2::EDIT_SECTION),
) {
self.assert_not_killed();
unsafe {
((*self.internal).register_import_menu)(
self.global_leak_manager.leak_as_wide_string(name),
callback,
)
}
}
pub fn register_export_menu(
&mut self,
name: &str,
callback: extern "C" fn(*mut aviutl2_sys::plugin2::EDIT_SECTION),
) {
self.assert_not_killed();
unsafe {
((*self.internal).register_export_menu)(
self.global_leak_manager.leak_as_wide_string(name),
callback,
)
}
}
pub fn register_layer_menu(
&mut self,
name: &str,
callback: extern "C" fn(*mut aviutl2_sys::plugin2::EDIT_SECTION),
) {
self.assert_not_killed();
unsafe {
((*self.internal).register_layer_menu)(
self.global_leak_manager.leak_as_wide_string(name),
callback,
)
}
}
pub fn register_object_menu(
&mut self,
name: &str,
callback: extern "C" fn(*mut aviutl2_sys::plugin2::EDIT_SECTION),
) {
self.assert_not_killed();
unsafe {
((*self.internal).register_object_menu)(
self.global_leak_manager.leak_as_wide_string(name),
callback,
)
}
}
pub fn register_edit_menu(
&mut self,
name: &str,
callback: extern "C" fn(*mut aviutl2_sys::plugin2::EDIT_SECTION),
) {
self.assert_not_killed();
unsafe {
((*self.internal).register_edit_menu)(
self.global_leak_manager.leak_as_wide_string(name),
callback,
)
}
}
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_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);
}
}
}
pub trait GenericPluginMenus {
fn register_menus(host: &mut 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
);
impl_plugin_registry!(
"スクリプトモジュール",
"module",
module,
script_module,
register_script_module,
crate::module::ScriptModule,
crate::module::__bridge::ScriptModuleSingleton,
aviutl2_sys::module2::SCRIPT_MODULE_TABLE
);
#[derive(Debug)]
pub struct EditHandle {
pub(crate) internal: *mut aviutl2_sys::plugin2::EDIT_HANDLE,
edit_info_worker: std::sync::OnceLock<EditInfoWorker>,
}
unsafe impl Send for EditHandle {}
unsafe impl Sync for EditHandle {}
#[derive(thiserror::Error, Debug)]
pub enum EditHandleError {
#[error("api call failed")]
ApiCallFailed,
}
impl EditHandle {
pub(crate) unsafe fn new(internal: *mut aviutl2_sys::plugin2::EDIT_HANDLE) -> Self {
Self {
internal,
edit_info_worker: std::sync::OnceLock::new(),
}
}
fn edit_info_worker(&self) -> &EditInfoWorker {
self.edit_info_worker
.get_or_init(|| EditInfoWorker::new(self.internal))
}
pub fn call_edit_section<'a, T, F>(&self, callback: F) -> Result<T, EditHandleError>
where
T: Send + 'static,
F: FnOnce(&mut EditSection) -> T + Send + 'a,
{
type CallbackParam<'a, F, T> = (ChildKillablePointer<Option<F>>, &'a mut Option<T>);
let closure = Some(callback);
let param = KillablePointer::new(closure);
let child_param = param.create_child();
extern "C" fn trampoline<F, T>(
param: *mut std::ffi::c_void,
edit_section: *mut aviutl2_sys::plugin2::EDIT_SECTION,
) where
T: Send + 'static,
F: FnOnce(&mut EditSection) -> T,
{
unsafe {
let (child_param, result_ptr) = &mut *(param as *mut CallbackParam<F, T>);
let callback = child_param.as_mut().take().expect("Callback already taken");
let mut edit_section = EditSection::from_raw(edit_section);
let res = callback(&mut edit_section);
result_ptr.replace(res);
}
}
let trampoline_static = trampoline::<F, T>
as extern "C" fn(*mut std::ffi::c_void, *mut aviutl2_sys::plugin2::EDIT_SECTION);
let mut result = None;
let param = Box::<CallbackParam<F, T>>::new((child_param, &mut result));
let param_ptr = Box::into_raw(param);
let success = unsafe {
((*self.internal).call_edit_section_param)(
param_ptr as *mut std::ffi::c_void,
trampoline_static,
)
};
drop(unsafe { Box::from_raw(param_ptr) });
if success {
Ok(result.expect("Callback did not set result"))
} else {
Err(EditHandleError::ApiCallFailed)
}
}
pub fn get_edit_info(&self) -> crate::generic::EditInfo {
let mut raw_info = std::mem::MaybeUninit::<aviutl2_sys::plugin2::EDIT_INFO>::uninit();
unsafe {
((*self.internal).get_edit_info)(
raw_info.as_mut_ptr(),
std::mem::size_of::<aviutl2_sys::plugin2::EDIT_INFO>() as _,
);
let edit_info = raw_info.assume_init();
crate::generic::EditInfo::from_raw(&edit_info)
}
}
pub fn try_get_edit_info(
&self,
timeout: std::time::Duration,
) -> Result<crate::generic::EditInfo, EditHandleError> {
let (tx, rx) = std::sync::mpsc::channel();
if self
.edit_info_worker()
.sender
.send(std::ops::ControlFlow::Continue(EditInfoRequest {
responder: tx,
}))
.is_err()
{
return Err(EditHandleError::ApiCallFailed);
}
match rx.recv_timeout(timeout) {
Ok(info) => Ok(info),
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
log::warn!("try_get_edit_info timed out");
Err(EditHandleError::ApiCallFailed)
}
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {
Err(EditHandleError::ApiCallFailed)
}
}
}
pub fn restart_host_app(&self) {
unsafe {
((*self.internal).restart_host_app)();
}
}
pub fn enumerate_effects<F>(&self, mut callback: F)
where
F: FnMut(Effect),
{
extern "C" fn trampoline<F>(
param: *mut std::ffi::c_void,
name: aviutl2_sys::common::LPCWSTR,
r#type: i32,
flag: i32,
) where
F: FnMut(Effect),
{
let callback = unsafe { &mut *(param as *mut F) };
let name_str = unsafe { crate::common::load_wide_string(name) };
let effect = Effect {
name: name_str,
effect_type: EffectType::from(r#type),
flag: EffectFlag::from_bits(flag),
};
callback(effect);
}
let trampoline_static = trampoline::<F>
as extern "C" fn(*mut std::ffi::c_void, aviutl2_sys::common::LPCWSTR, i32, i32);
let user_data = &mut callback as *mut F as *mut std::ffi::c_void;
unsafe {
((*self.internal).enum_effect_name)(user_data, trampoline_static);
}
}
pub fn get_effects(&self) -> Vec<Effect> {
let mut effects = Vec::new();
self.enumerate_effects(|effect| {
effects.push(effect);
});
effects
}
pub fn enumerate_modules<F>(&self, mut callback: F)
where
F: FnMut(ModuleInfo),
{
extern "C" fn trampoline<F>(
param: *mut std::ffi::c_void,
module: *mut aviutl2_sys::plugin2::MODULE_INFO,
) where
F: FnMut(ModuleInfo),
{
let callback = unsafe { &mut *(param as *mut F) };
let module_info = ModuleInfo {
module_type: ModuleType::from(unsafe { (*module).r#type }),
name: unsafe { crate::common::load_wide_string((*module).name) },
information: unsafe { crate::common::load_wide_string((*module).information) },
};
callback(module_info);
}
let trampoline_static = trampoline::<F>
as unsafe extern "C" fn(*mut std::ffi::c_void, *mut aviutl2_sys::plugin2::MODULE_INFO);
let user_data = &mut callback as *mut F as *mut std::ffi::c_void;
unsafe {
((*self.internal).enum_module_info)(user_data, trampoline_static);
}
}
pub fn get_modules(&self) -> Vec<ModuleInfo> {
let mut modules = Vec::new();
self.enumerate_modules(|module| {
modules.push(module);
});
modules
}
}
impl Drop for EditHandle {
fn drop(&mut self) {
if let Some(worker) = self.edit_info_worker.take() {
let _ = worker.sender.send(std::ops::ControlFlow::Break(()));
let _ = worker.join_handle.join();
}
}
}
struct InternalSendableEditHandle(*mut aviutl2_sys::plugin2::EDIT_HANDLE);
unsafe impl Send for InternalSendableEditHandle {}
impl InternalSendableEditHandle {
fn get(&self) -> *mut aviutl2_sys::plugin2::EDIT_HANDLE {
self.0
}
}
struct EditInfoRequest {
responder: std::sync::mpsc::Sender<crate::generic::EditInfo>,
}
#[derive(Debug)]
struct EditInfoWorker {
sender: std::sync::mpsc::Sender<std::ops::ControlFlow<(), EditInfoRequest>>,
join_handle: std::thread::JoinHandle<()>,
}
impl EditInfoWorker {
fn new(internal: *mut aviutl2_sys::plugin2::EDIT_HANDLE) -> Self {
let (tx, rx) = std::sync::mpsc::channel::<std::ops::ControlFlow<(), EditInfoRequest>>();
let internal = InternalSendableEditHandle(internal);
let join_handle = std::thread::Builder::new()
.name("aviutl2-edit-info-worker".to_string())
.spawn(move || {
while let Ok(std::ops::ControlFlow::Continue(request)) = rx.recv() {
let mut raw_info =
std::mem::MaybeUninit::<aviutl2_sys::plugin2::EDIT_INFO>::uninit();
unsafe {
((*internal.get()).get_edit_info)(
raw_info.as_mut_ptr(),
std::mem::size_of::<aviutl2_sys::plugin2::EDIT_INFO>() as _,
);
let edit_info = raw_info.assume_init();
let info = crate::generic::EditInfo::from_raw(&edit_info);
let _ = request.responder.send(info);
}
}
})
.expect("Failed to spawn edit info worker thread");
Self {
sender: tx,
join_handle,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Effect {
pub name: String,
pub effect_type: EffectType,
pub flag: EffectFlag,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EffectType {
Filter,
Input,
SceneChange,
Other(i32),
}
define_bitflag! {
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct EffectFlag: i32 {
video: aviutl2_sys::plugin2::EDIT_HANDLE::EFFECT_FLAG_VIDEO,
audio: aviutl2_sys::plugin2::EDIT_HANDLE::EFFECT_FLAG_AUDIO,
as_filter: aviutl2_sys::plugin2::EDIT_HANDLE::EFFECT_FLAG_FILTER,
}
}
impl From<i32> for EffectType {
fn from(value: i32) -> Self {
match value {
1 => EffectType::Filter,
2 => EffectType::Input,
3 => EffectType::SceneChange,
other => EffectType::Other(other),
}
}
}
impl From<EffectType> for i32 {
fn from(value: EffectType) -> Self {
match value {
EffectType::Filter => 1,
EffectType::Input => 2,
EffectType::SceneChange => 3,
EffectType::Other(other) => other,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ModuleInfo {
pub module_type: ModuleType,
pub name: String,
pub information: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModuleType {
ScriptFilter,
ScriptObject,
ScriptCamera,
ScriptTrack,
ScriptModule,
PluginInput,
PluginOutput,
PluginFilter,
PluginGeneric,
Other(i32),
}
impl From<i32> for ModuleType {
fn from(value: i32) -> Self {
match value {
1 => ModuleType::ScriptFilter,
2 => ModuleType::ScriptObject,
3 => ModuleType::ScriptCamera,
4 => ModuleType::ScriptTrack,
5 => ModuleType::ScriptModule,
6 => ModuleType::PluginInput,
7 => ModuleType::PluginOutput,
8 => ModuleType::PluginFilter,
9 => ModuleType::PluginGeneric,
other => ModuleType::Other(other),
}
}
}
impl From<ModuleType> for i32 {
fn from(value: ModuleType) -> Self {
match value {
ModuleType::ScriptFilter => 1,
ModuleType::ScriptObject => 2,
ModuleType::ScriptCamera => 3,
ModuleType::ScriptTrack => 4,
ModuleType::ScriptModule => 5,
ModuleType::PluginInput => 6,
ModuleType::PluginOutput => 7,
ModuleType::PluginFilter => 8,
ModuleType::PluginGeneric => 9,
ModuleType::Other(other) => other,
}
}
}
#[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
}
struct KillablePointer<T> {
kill_switch: std::sync::Arc<std::sync::atomic::AtomicBool>,
inner: *mut T,
}
unsafe impl<T> Send for KillablePointer<T> {}
unsafe impl<T> Sync for KillablePointer<T> {}
impl<T> Drop for KillablePointer<T> {
fn drop(&mut self) {
self.kill_switch
.store(true, std::sync::atomic::Ordering::SeqCst);
}
}
impl<T> KillablePointer<T> {
pub fn new(inner: T) -> Self {
Self {
kill_switch: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
inner: Box::into_raw(Box::new(inner)),
}
}
pub fn create_child(&self) -> ChildKillablePointer<T> {
ChildKillablePointer {
kill_switch: std::sync::Arc::clone(&self.kill_switch),
inner: self.inner,
}
}
}
struct ChildKillablePointer<T> {
kill_switch: std::sync::Arc<std::sync::atomic::AtomicBool>,
inner: *mut T,
}
unsafe impl<T> Send for ChildKillablePointer<T> {}
unsafe impl<T> Sync for ChildKillablePointer<T> {}
impl<T> ChildKillablePointer<T> {
pub fn is_killed(&self) -> bool {
self.kill_switch.load(std::sync::atomic::Ordering::SeqCst)
}
pub unsafe fn as_mut(&mut self) -> &mut T {
if self.is_killed() {
panic!("parent KillablePointer has been dropped");
}
unsafe { &mut *self.inner }
}
}