pub mod config; pub mod context; pub mod state; pub mod handler; use gtk::glib; use gstreamer as gst; use std::ffi::c_void; use std::os::raw::{c_char, c_int, c_uint}; use std::ptr; // Re-export types for external use (e.g. integration tests) pub use config::{ModuleConfig, DEADZONE_ARG, DEADZONE_LONG, DEADZONE_DESC, DEADZONE_ARG_DESC}; pub use context::{Window, GtkLock, WindowContext, __IncompleteArrayField}; pub use state::{MODULE_STATE, WindowData, ModuleState}; pub use handler::WindowHandler; #[no_mangle] pub static module_name: [c_char; 7] = [ b'a' as c_char, b'h' as c_char, b'f' as c_char, b'a' as c_char, b'i' as c_char, b'l' as c_char, 0, ]; #[no_mangle] pub static module_major_version: c_uint = 4; #[no_mangle] pub static module_minor_version: c_uint = 0; #[no_mangle] pub static mut module_entries: [glib::ffi::GOptionEntry; 3] = [ glib::ffi::GOptionEntry { long_name: DEADZONE_LONG.as_ptr() as *const c_char, short_name: 0, flags: 0, arg: glib::ffi::G_OPTION_ARG_STRING, arg_data: &raw mut DEADZONE_ARG as *mut _, description: DEADZONE_DESC.as_ptr() as *const c_char, arg_description: DEADZONE_ARG_DESC.as_ptr() as *const c_char }, glib::ffi::GOptionEntry { long_name: ptr::null(), short_name: 0, flags: 0, arg: 0, arg_data: ptr::null_mut(), description: ptr::null(), arg_description: ptr::null() }, glib::ffi::GOptionEntry { long_name: ptr::null(), short_name: 0, flags: 0, arg: 0, arg_data: ptr::null_mut(), description: ptr::null(), arg_description: ptr::null() }, ]; // Helper for tests to inspect window data /// # Safety /// `ctx` must be a valid Window pointer. pub unsafe fn get_window_data_ref(ctx: *mut Window) -> Option<&'static state::WindowData> { let ptr = *(*ctx).module_data.as_ptr(); if ptr.is_null() { None } else { Some(&*(ptr as *mut state::WindowData)) } } /// # Safety /// This function is called by the C host. #[no_mangle] pub unsafe extern "C" fn on_activation(_gtklock: *mut GtkLock, _id: c_int) { println!("[ahfail] on_activation called"); if let Err(e) = gtk::init() { eprintln!("Failed to initialize GTK bindings: {}", e); return; } if let Err(e) = gst::init() { eprintln!("Failed to initialize GStreamer: {}", e); return; } let anim_opt = unsafe { ahfail_ui::animation::load_animation() }; let config = ModuleConfig::from_args(); MODULE_STATE.with(|state| { let mut state = state.borrow_mut(); state.animation = anim_opt; state.audio_uri = Some("resource:///ahfail/audio/magic-word.mp3".to_string()); state.config = config; }); } /// # Safety /// This function is called by the C host. #[no_mangle] pub unsafe extern "C" fn on_window_create(_gtklock: *mut GtkLock, ctx: *mut Window) { if let Some(win_ctx) = WindowContext::new(ctx) { WindowHandler::create(&win_ctx); } } /// # Safety /// This function is called by the C host. #[no_mangle] pub unsafe extern "C" fn on_window_destroy(_gtklock: *mut GtkLock, ctx: *mut Window) { if let Some(win_ctx) = WindowContext::new(ctx) { WindowHandler::destroy(&win_ctx); } } /// # Safety /// This function is called by the C host. #[no_mangle] pub unsafe extern "C" fn on_idle_hide(gtklock: *mut GtkLock) { if gtklock.is_null() { return; } let garray = (*gtklock).windows; if !garray.is_null() { let len = (*garray).len; let data_ptr = (*garray).data as *mut *mut Window; let windows = std::slice::from_raw_parts(data_ptr, len as usize); for &window_ptr in windows { on_window_destroy(ptr::null_mut(), window_ptr); } } } /// # Safety /// This function is called by the C host. #[no_mangle] pub unsafe extern "C" fn on_focus_change(_gtklock: *mut GtkLock, _win: *mut Window, _old: *mut Window) {} /// # Safety /// This function is called by the C host. #[no_mangle] pub unsafe extern "C" fn on_idle_show(_gtklock: *mut GtkLock) {} /// # Safety /// This function is called by the C host. #[no_mangle] pub unsafe extern "C" fn g_module_unload(_module: *mut c_void) { MODULE_STATE.with(|state| { let mut state = state.borrow_mut(); if let Some(vs) = state.volume_state.take() { ahfail_ui::volume::restore(vs); } state.animation = None; state.audio_uri = None; state.config.deadzone = None; }); }