//! ShowenV2 Plugin SDK //! //! 插件开发者使用此 SDK 编写动态插件。 //! 实现 `ShowenPlugin` trait,然后用 `export_plugin!` 宏导出。 use serde::{Deserialize, Serialize}; use std::ffi::{c_char, c_int, c_void, CString}; use std::ptr; // ── 重新导出消息类型(与主程序共享 JSON 契约) ── /// 描述插件元数据。 /// /// 主程序会在加载动态库后读取此结构体,用于展示插件名称、版本、平台信息, /// 并帮助第三方开发者确认插件是否按预期被正确识别。 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PluginInfo { /// 插件的人类可读名称。 pub name: String, /// 插件版本号,通常使用语义化版本格式。 pub version: String, /// 插件用途简介,会显示给使用者或调试工具。 pub description: String, /// 插件面向的平台标识,例如 `linux` 或 `cross-platform`。 pub platform: String, } /// 表示一次能力自检中的单项结果。 /// /// `self_test` 返回的列表会由主程序或测试工具消费,用于判断插件声明的能力 /// 是否已经完成最基本的运行时验证。 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CapabilityTestResult { /// 被测试的能力名称,通常与 `ShowenPlugin::capabilities` 中的条目对应。 pub capability: String, /// 该能力是否通过自检。 pub passed: bool, /// 面向开发者的结果说明,可用于记录失败原因或补充上下文。 pub message: String, } /// 插件系统中的标准消息信封。 /// /// 所有跨插件、插件到主程序、或插件到管理层的通信都通过此结构体完成。 /// 它定义了统一的发送者、目的地和消息载荷格式,并通过 JSON 在 ABI 边界上传输。 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Envelope { /// 消息发送者名称,通常填写当前插件名。 pub from: String, /// 消息目标,可指定单个插件、广播或管理层。 pub to: Destination, /// 实际发送的业务消息。 pub message: Message, } /// 定义消息的投递目标。 #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Destination { /// 将消息发送给指定名称的插件。 Plugin(String), /// 将消息广播给所有感兴趣的接收方。 Broadcast, /// 将消息发送给主程序的管理层或调度层。 Manager, } /// 消息类型 — 与主程序 Message 枚举保持 JSON 兼容 /// 动态插件只需处理自己关心的消息变体 #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Message { /// 发送给播放器模块的命令。 /// /// 载荷内容由播放器插件或主程序定义,通常是 JSON 对象,包含播放、暂停、 /// 跳转等控制信息。 PlayerCommand(serde_json::Value), /// 来自播放器模块的状态快照或状态变更。 /// /// 适用于同步当前曲目、播放状态、进度等信息。 PlayerStatus(serde_json::Value), /// 触发器事件。 /// /// `name` 表示触发器名称,`value` 表示本次触发附带的值,适合按钮、传感器 /// 或自动化规则触发。 Trigger { /// 触发器名称。 name: String, /// 触发器携带的值。 value: String, }, /// 状态机切换事件。 /// /// 当系统状态从一个命名状态迁移到另一个状态时使用,便于插件同步行为。 StateChanged { /// 切换前的状态名。 old_state: String, /// 切换后的状态名。 new_state: String, }, /// 请求屏幕锁定或解锁。 /// /// `true` 表示请求锁定,`false` 表示请求解除锁定。 ScreenLockRequest(bool), /// 请求显示或隐藏光标。 /// /// `true` 表示显示光标,`false` 表示隐藏光标。 CursorVisibility(bool), /// Wi-Fi 控制命令。 /// /// 具体 JSON 字段由网络相关插件或主程序约定。 WifiCommand(serde_json::Value), /// Wi-Fi 操作结果字符串。 /// /// 一般用于返回简短状态、错误描述或执行结果摘要。 WifiResult(String), /// Wi-Fi 配网成功事件。 WifiProvisioned { /// 当前接入的 SSID。 ssid: String, /// 当前设备获取到的 IP 地址。 ip: String, }, /// 配置重载完成通知。 /// /// JSON 载荷通常是更新后的配置片段或完整配置。 ConfigReloaded(serde_json::Value), /// 请求主程序重新加载配置。 ConfigReloadRequest, /// 请求插件或主程序进入关闭流程。 Shutdown, /// 插件就绪通知。 /// /// 字符串内容通常为就绪插件的名称。 PluginReady(String), /// 设备命令(业务插件 → DevicePlugin)。 DeviceCommand(DeviceCommand), /// 设备响应(DevicePlugin → 请求者)。 DeviceResponse(DeviceResponse), /// 设备事件(DevicePlugin → 广播)。 DeviceEvent(DeviceEvent), /// 自定义业务消息。 /// /// 当标准消息不足以表达插件间协议时,可通过 `kind` 区分消息类型, /// 并在 `payload` 中承载自定义内容。 Custom { /// 自定义消息类型标识。 kind: String, /// 自定义消息的文本载荷。 payload: String, }, } // ── 设备管理类型 ── /// 像素格式 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum PixelFormat { /// RGBA 8888 格式(每像素 4 字节) RGBA8888, /// RGB 888 格式(每像素 3 字节) RGB888, /// RGB 565 格式(每像素 2 字节) RGB565, } /// 传感器类型 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum SensorType { /// 温度传感器 Temperature, /// 湿度传感器 Humidity, /// 光线传感器 Light, /// 接近传感器 Proximity, } /// 触摸动作 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum TouchAction { /// 按下 Down, /// 移动 Move, /// 抬起 Up, } /// 设备能力 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum DeviceCapability { /// 显示屏 Display, /// 触摸屏 Touch, /// 按钮 Buttons, /// 音频 Audio, /// 电池 Battery, /// 背光 Backlight, /// 传感器 Sensors, /// 帧缓冲 Framebuffer, /// GPIO GPIO, /// 光标控制能力 Cursor, } /// 设备命令(业务插件 → DevicePlugin) #[derive(Debug, Clone, Serialize, Deserialize)] pub enum DeviceCommand { /// 获取显示信息 GetDisplayInfo, /// 设置亮度(0-100) SetBrightness(u8), /// 设置背光开关 SetBacklight(bool), /// 写入帧缓冲 WriteFramebuffer { data: Vec, format: PixelFormat }, /// 设置防息屏 SetSleepInhibit(bool), /// 设置光标可见性 /// - true: 显示光标 /// - false: 隐藏光标(通过 unclutter 或平台特定方式) SetCursorVisible(bool), /// 获取电池电量 GetBatteryLevel, /// 设置音量(0-100) SetVolume(u8), /// 播放音频 PlayAudio { path: String }, /// 获取触摸事件 GetTouchEvents, /// 获取按钮状态 GetButtonState, /// 获取传感器数据 GetSensorData(SensorType), /// 自定义命令 CustomCommand { subsystem: String, payload: serde_json::Value, }, } /// 设备响应(DevicePlugin → 请求者) #[derive(Debug, Clone, Serialize, Deserialize)] pub enum DeviceResponse { /// 显示信息 DisplayInfo { width: u32, height: u32, format: PixelFormat, }, /// 传感器数据 SensorData { sensor: SensorType, value: f64 }, /// 电池电量(0-100) BatteryLevel(u8), /// 操作成功 Ok, /// 操作失败 Error(String), /// 自定义响应 Custom(serde_json::Value), } /// 设备事件(DevicePlugin → 广播) #[derive(Debug, Clone, Serialize, Deserialize)] pub enum DeviceEvent { /// 触摸事件 TouchEvent { x: i32, y: i32, action: TouchAction }, /// 按钮事件 ButtonEvent { button: u8, pressed: bool }, /// 电池电量低 BatteryLow(u8), /// 显示器已连接 DisplayConnected, /// 显示器已断开 DisplayDisconnected, /// 传感器警报 SensorAlert { sensor: SensorType, value: f64 }, } // ── FFI 类型(与主程序 plugin_abi.rs 完全对应) ── /// 插件实例在 FFI 边界上的不透明句柄。 pub type PluginHandle = *mut c_void; /// 指向以 null 结尾的 C 字符串。 pub type FfiStr = *const c_char; /// ABI 安全的字符串返回类型。 /// /// 主程序从插件取回 JSON 或错误信息时使用该结构体。内存由插件分配, /// 再通过 `PluginVTable::free_string` 释放。 #[repr(C)] pub struct FfiString { /// 字符串起始指针;为空时表示没有内容。 pub ptr: *mut c_char, /// 字节长度,不包含结尾的 `\0`。 pub len: usize, } impl FfiString { /// 从 Rust `String` 构造 FFI 字符串。 /// /// 如果字符串中包含内部 `NUL` 字节,会返回空字符串表示失败。 pub fn from_string(s: String) -> Self { match CString::new(s) { Ok(cstr) => { let len = cstr.as_bytes().len(); Self { ptr: cstr.into_raw(), len, } } Err(_) => Self::null(), } } /// 构造空的 FFI 字符串。 pub fn null() -> Self { Self { ptr: ptr::null_mut(), len: 0, } } /// 复制为 Rust String(不释放底层内存) /// /// # Safety /// ptr 必须指向有效的 null-terminated C 字符串 pub unsafe fn to_string(&self) -> Option { if self.ptr.is_null() { return None; } unsafe { std::ffi::CStr::from_ptr(self.ptr) } .to_str() .ok() .map(str::to_owned) } } /// ABI 安全的返回值结构。 /// /// `code == 0` 表示成功,非零表示失败;`error` 中包含面向开发者的错误文本。 #[repr(C)] pub struct FfiResult { /// 状态码,约定 `0` 为成功,`-1` 为失败。 pub code: c_int, /// 错误消息;成功时通常为空字符串。 pub error: FfiString, } impl FfiResult { /// 创建表示成功的返回值。 pub fn ok() -> Self { Self { code: 0, error: FfiString::null(), } } /// 创建表示失败的返回值,并附带错误消息。 pub fn err(msg: String) -> Self { Self { code: -1, error: FfiString::from_string(msg), } } } /// 主程序提供给插件的消息发送回调。 /// /// 插件通常无需直接调用该类型,而是通过 [`MessageSender`] 使用安全封装。 pub type SendCallback = unsafe extern "C" fn(ctx: *mut c_void, envelope_json: FfiStr); /// 插件导出给主程序的函数表。 /// /// 该结构与主程序中的 ABI 定义一一对应。普通插件作者一般不需要手动构造, /// 使用 [`export_plugin!`] 宏即可自动生成。 #[repr(C)] pub struct PluginVTable { /// 创建插件实例。 pub create: unsafe extern "C" fn() -> PluginHandle, /// 获取 [`PluginInfo`] 的 JSON 序列化结果。 pub get_info: unsafe extern "C" fn(handle: PluginHandle) -> FfiString, /// 初始化插件实例。 pub init: unsafe extern "C" fn( handle: PluginHandle, config_json: FfiStr, send_ctx: *mut c_void, send_cb: SendCallback, ) -> FfiResult, /// 启动插件。 pub start: unsafe extern "C" fn(handle: PluginHandle) -> FfiResult, /// 处理一条 JSON 序列化消息。 pub handle_message: unsafe extern "C" fn(handle: PluginHandle, message_json: FfiStr) -> FfiResult, /// 停止插件。 pub stop: unsafe extern "C" fn(handle: PluginHandle) -> FfiResult, /// 释放由插件返回的字符串。 pub free_string: unsafe extern "C" fn(s: FfiString), /// 销毁插件实例。 pub destroy: unsafe extern "C" fn(handle: PluginHandle), /// 获取插件能力列表的 JSON 序列化结果。 pub get_capabilities: unsafe extern "C" fn(handle: PluginHandle) -> FfiString, /// 获取插件自检结果列表的 JSON 序列化结果。 pub self_test: unsafe extern "C" fn(handle: PluginHandle) -> FfiString, } // ── 高级接口:插件作者实现此 trait ── /// 消息发送器,封装底层 FFI 回调并提供安全的 Rust API。 /// /// 插件在 [`ShowenPlugin::init`] 中会收到一个 `MessageSender`,之后可将其保存到 /// 插件状态中,供运行期间向其他插件或主程序发送消息。 pub struct MessageSender { ctx: *mut c_void, cb: SendCallback, } impl MessageSender { /// 基于底层发送上下文和回调创建发送器。 /// /// 普通插件作者通常不需要手动调用该方法;主程序在初始化插件时会自动构造。 /// /// # Examples /// ```ignore /// use showen_plugin_sdk::{MessageSender, SendCallback}; /// use std::ffi::c_void; /// /// unsafe extern "C" fn callback(_: *mut c_void, _: *const std::ffi::c_char) {} /// /// let sender = MessageSender::new(std::ptr::null_mut(), callback as SendCallback); /// # let _ = sender; /// ``` pub fn new(ctx: *mut c_void, cb: SendCallback) -> Self { Self { ctx, cb } } /// 发送完整的消息信封。 /// /// 当你已经手动构造好 [`Envelope`],或者需要完全控制发送者、目标和消息载荷时, /// 直接使用此方法最合适。 /// /// # Examples /// ```ignore /// use showen_plugin_sdk::{Destination, Envelope, Message, MessageSender}; /// /// # let sender: MessageSender = unimplemented!(); /// sender.send(&Envelope { /// from: "clock".to_string(), /// to: Destination::Manager, /// message: Message::PluginReady("clock".to_string()), /// }); /// ``` pub fn send(&self, envelope: &Envelope) { if let Ok(json) = serde_json::to_string(envelope) { if let Ok(cstr) = CString::new(json) { unsafe { (self.cb)(self.ctx, cstr.as_ptr()) }; } } } /// 发送消息给指定插件。 /// /// 这是构造 [`Destination::Plugin`] 的便捷方法,适合点对点通信。 /// /// # Examples /// ```ignore /// use showen_plugin_sdk::{Message, MessageSender}; /// /// # let sender: MessageSender = unimplemented!(); /// sender.send_to( /// "clock", /// "player", /// Message::Custom { /// kind: "sync-request".to_string(), /// payload: "{}".to_string(), /// }, /// ); /// ``` pub fn send_to(&self, from: &str, to_plugin: &str, message: Message) { self.send(&Envelope { from: from.to_string(), to: Destination::Plugin(to_plugin.to_string()), message, }); } /// 广播消息给所有接收方。 /// /// 适合发送系统事件、状态变更或多个插件都可能关心的通知。 /// /// # Examples /// ```ignore /// use showen_plugin_sdk::{Message, MessageSender}; /// /// # let sender: MessageSender = unimplemented!(); /// sender.broadcast( /// "network", /// Message::WifiProvisioned { /// ssid: "Office-WiFi".to_string(), /// ip: "192.168.1.8".to_string(), /// }, /// ); /// ``` pub fn broadcast(&self, from: &str, message: Message) { self.send(&Envelope { from: from.to_string(), to: Destination::Broadcast, message, }); } /// 发送消息给主程序管理层。 /// /// 适用于请求重载配置、上报就绪状态、或提交不属于单个插件的系统级事件。 /// /// # Examples /// ```ignore /// use showen_plugin_sdk::{Message, MessageSender}; /// /// # let sender: MessageSender = unimplemented!(); /// sender.send_to_manager( /// "config-watcher", /// Message::ConfigReloadRequest, /// ); /// ``` pub fn send_to_manager(&self, from: &str, message: Message) { self.send(&Envelope { from: from.to_string(), to: Destination::Manager, message, }); } } // SendCallback 是 extern "C" fn 指针,可跨线程安全传递 unsafe impl Send for MessageSender {} unsafe impl Sync for MessageSender {} /// 动态插件的高层 Rust 接口。 /// /// 第三方插件作者只需要实现此 trait,再调用 [`export_plugin!`] 宏,即可导出一个 /// 可被主程序加载的动态库。主程序会按照 `info -> init -> start -> handle_message -> stop` /// 的生命周期驱动插件。 pub trait ShowenPlugin: Send { /// 返回插件的静态元信息。 /// /// 主程序会在插件加载后尽早调用此方法,用于识别插件、展示描述并进行兼容性判断。 /// /// # Examples /// ```ignore /// use showen_plugin_sdk::{PluginInfo, ShowenPlugin}; /// /// struct DemoPlugin; /// /// impl ShowenPlugin for DemoPlugin { /// fn info(&self) -> PluginInfo { /// PluginInfo { /// name: "demo".to_string(), /// version: "0.1.0".to_string(), /// description: "Example plugin".to_string(), /// platform: "linux".to_string(), /// } /// } /// /// fn init(&mut self, _: &str, _: showen_plugin_sdk::MessageSender) -> Result<(), String> { Ok(()) } /// fn start(&mut self) -> Result<(), String> { Ok(()) } /// fn handle_message(&mut self, _: showen_plugin_sdk::Message) -> Result<(), String> { Ok(()) } /// fn stop(&mut self) -> Result<(), String> { Ok(()) } /// } /// ``` fn info(&self) -> PluginInfo; /// 声明插件支持的能力列表。 /// /// 能力字符串通常用于主程序展示、诊断以及自测分组。默认实现返回空列表。 /// /// # Examples /// ```ignore /// # use showen_plugin_sdk::{PluginInfo, ShowenPlugin}; /// # struct DemoPlugin; /// # impl ShowenPlugin for DemoPlugin { /// # fn info(&self) -> PluginInfo { /// # PluginInfo { name: "demo".into(), version: "0.1.0".into(), description: "Example".into(), platform: "linux".into() } /// # } /// fn capabilities(&self) -> Vec { /// vec!["wifi.scan".to_string(), "wifi.connect".to_string()] /// } /// # fn init(&mut self, _: &str, _: showen_plugin_sdk::MessageSender) -> Result<(), String> { Ok(()) } /// # fn start(&mut self) -> Result<(), String> { Ok(()) } /// # fn handle_message(&mut self, _: showen_plugin_sdk::Message) -> Result<(), String> { Ok(()) } /// # fn stop(&mut self) -> Result<(), String> { Ok(()) } /// # } /// ``` fn capabilities(&self) -> Vec { vec![] } /// 运行插件自测,返回每项能力的测试结果。 /// /// 默认实现会把 `capabilities` 返回的每个能力都标记为通过,并给出 /// `"no test defined"` 提示。若插件依赖外部设备、系统命令或网络状态,建议覆写此方法。 /// /// # Examples /// ```ignore /// # use showen_plugin_sdk::{CapabilityTestResult, PluginInfo, ShowenPlugin}; /// # struct DemoPlugin; /// # impl ShowenPlugin for DemoPlugin { /// # fn info(&self) -> PluginInfo { /// # PluginInfo { name: "demo".into(), version: "0.1.0".into(), description: "Example".into(), platform: "linux".into() } /// # } /// fn self_test(&mut self) -> Vec { /// vec![CapabilityTestResult { /// capability: "wifi.scan".to_string(), /// passed: true, /// message: "scan backend reachable".to_string(), /// }] /// } /// # fn init(&mut self, _: &str, _: showen_plugin_sdk::MessageSender) -> Result<(), String> { Ok(()) } /// # fn start(&mut self) -> Result<(), String> { Ok(()) } /// # fn handle_message(&mut self, _: showen_plugin_sdk::Message) -> Result<(), String> { Ok(()) } /// # fn stop(&mut self) -> Result<(), String> { Ok(()) } /// # } /// ``` fn self_test(&mut self) -> Vec { self.capabilities() .into_iter() .map(|c| CapabilityTestResult { capability: c, passed: true, message: "no test defined".into(), }) .collect() } /// 初始化插件。 /// /// 主程序会把插件配置的 JSON 文本和一个 [`MessageSender`] 传入。插件通常在此阶段 /// 解析配置、保存发送器、准备运行所需资源,但不应启动长期运行任务。 /// /// # Examples /// ```ignore /// # use showen_plugin_sdk::{MessageSender, PluginInfo, ShowenPlugin}; /// # struct DemoPlugin { /// # sender: Option, /// # } /// # impl ShowenPlugin for DemoPlugin { /// # fn info(&self) -> PluginInfo { /// # PluginInfo { name: "demo".into(), version: "0.1.0".into(), description: "Example".into(), platform: "linux".into() } /// # } /// fn init(&mut self, config_json: &str, sender: MessageSender) -> Result<(), String> { /// let _config: serde_json::Value = /// serde_json::from_str(config_json).map_err(|e| e.to_string())?; /// self.sender = Some(sender); /// Ok(()) /// } /// # fn start(&mut self) -> Result<(), String> { Ok(()) } /// # fn handle_message(&mut self, _: showen_plugin_sdk::Message) -> Result<(), String> { Ok(()) } /// # fn stop(&mut self) -> Result<(), String> { Ok(()) } /// # } /// ``` fn init(&mut self, config_json: &str, sender: MessageSender) -> Result<(), String>; /// 启动插件。 /// /// 在 `init` 成功之后调用。适合在这里启动后台线程、注册监听器或发送 /// [`Message::PluginReady`] 等就绪通知。 /// /// # Examples /// ```ignore /// # use showen_plugin_sdk::{Message, MessageSender, PluginInfo, ShowenPlugin}; /// # struct DemoPlugin { /// # sender: Option, /// # } /// # impl ShowenPlugin for DemoPlugin { /// # fn info(&self) -> PluginInfo { /// # PluginInfo { name: "demo".into(), version: "0.1.0".into(), description: "Example".into(), platform: "linux".into() } /// # } /// # fn init(&mut self, _: &str, sender: MessageSender) -> Result<(), String> { self.sender = Some(sender); Ok(()) } /// fn start(&mut self) -> Result<(), String> { /// if let Some(sender) = &self.sender { /// sender.send_to_manager("demo", Message::PluginReady("demo".to_string())); /// } /// Ok(()) /// } /// # fn handle_message(&mut self, _: showen_plugin_sdk::Message) -> Result<(), String> { Ok(()) } /// # fn stop(&mut self) -> Result<(), String> { Ok(()) } /// # } /// ``` fn start(&mut self) -> Result<(), String>; /// 处理主程序转发给插件的消息。 /// /// 进入此方法前,JSON 已经被 SDK 反序列化为 [`Message`]。插件应只匹配自己关心的 /// 消息变体,并在必要时返回错误字符串帮助定位问题。 /// /// # Examples /// ```ignore /// # use showen_plugin_sdk::{Message, PluginInfo, ShowenPlugin}; /// # struct DemoPlugin; /// # impl ShowenPlugin for DemoPlugin { /// # fn info(&self) -> PluginInfo { /// # PluginInfo { name: "demo".into(), version: "0.1.0".into(), description: "Example".into(), platform: "linux".into() } /// # } /// # fn init(&mut self, _: &str, _: showen_plugin_sdk::MessageSender) -> Result<(), String> { Ok(()) } /// # fn start(&mut self) -> Result<(), String> { Ok(()) } /// fn handle_message(&mut self, message: Message) -> Result<(), String> { /// match message { /// Message::Shutdown => Ok(()), /// Message::Custom { kind, payload } if kind == "sync" => { /// let _ = payload; /// Ok(()) /// } /// _ => Ok(()), /// } /// } /// # fn stop(&mut self) -> Result<(), String> { Ok(()) } /// # } /// ``` fn handle_message(&mut self, message: Message) -> Result<(), String>; /// 停止插件并释放运行期资源。 /// /// 该方法通常用于停止后台线程、撤销监听、关闭文件句柄或网络连接。执行完成后, /// 主程序可能很快销毁插件实例。 /// /// # Examples /// ```ignore /// # use showen_plugin_sdk::{PluginInfo, ShowenPlugin}; /// # struct DemoPlugin { /// # running: bool, /// # } /// # impl ShowenPlugin for DemoPlugin { /// # fn info(&self) -> PluginInfo { /// # PluginInfo { name: "demo".into(), version: "0.1.0".into(), description: "Example".into(), platform: "linux".into() } /// # } /// # fn init(&mut self, _: &str, _: showen_plugin_sdk::MessageSender) -> Result<(), String> { Ok(()) } /// # fn start(&mut self) -> Result<(), String> { self.running = true; Ok(()) } /// # fn handle_message(&mut self, _: showen_plugin_sdk::Message) -> Result<(), String> { Ok(()) } /// fn stop(&mut self) -> Result<(), String> { /// self.running = false; /// Ok(()) /// } /// # } /// ``` fn stop(&mut self) -> Result<(), String>; } // ── 导出宏:自动生成 extern "C" 胶水代码 ── /// 将 [`ShowenPlugin`] 实现导出为主程序可加载的 C ABI 入口。 /// /// 该宏会自动生成完整的 `extern "C"` 胶水代码,包括: /// /// - 创建与销毁插件实例 /// - 把 `PluginInfo`、capabilities、自测结果序列化为 JSON /// - 在 ABI 边界上捕获 panic,避免传播到主程序 /// - 把传入的 JSON 消息反序列化为 [`Message`] 后再调用 trait 方法 /// - 导出主程序约定名称的 `showen_plugin_vtable` /// /// 传入参数: /// /// - 第一个参数是插件具体类型 /// - 第二个参数是无参构造表达式,例如 `MyPlugin::new()` /// /// 使用此宏时,插件类型必须实现 [`ShowenPlugin`],且构造表达式应返回该类型实例。 /// /// # Examples /// ```ignore /// use showen_plugin_sdk::{ /// export_plugin, Message, MessageSender, PluginInfo, ShowenPlugin, /// }; /// /// struct MyPlugin { /// sender: Option, /// } /// /// impl MyPlugin { /// fn new() -> Self { /// Self { sender: None } /// } /// } /// /// impl ShowenPlugin for MyPlugin { /// fn info(&self) -> PluginInfo { /// PluginInfo { /// name: "my-plugin".to_string(), /// version: "0.1.0".to_string(), /// description: "Example ShowenV2 plugin".to_string(), /// platform: "linux".to_string(), /// } /// } /// /// fn init(&mut self, _config_json: &str, sender: MessageSender) -> Result<(), String> { /// self.sender = Some(sender); /// Ok(()) /// } /// /// fn start(&mut self) -> Result<(), String> { /// if let Some(sender) = &self.sender { /// sender.send_to_manager( /// "my-plugin", /// Message::PluginReady("my-plugin".to_string()), /// ); /// } /// Ok(()) /// } /// /// fn handle_message(&mut self, _message: Message) -> Result<(), String> { /// Ok(()) /// } /// /// fn stop(&mut self) -> Result<(), String> { /// Ok(()) /// } /// } /// /// export_plugin!(MyPlugin, MyPlugin::new()); /// ``` /// /// 生成的 `showen_plugin_vtable` 会被主程序在加载动态库后自动发现,因此插件 crate /// 通常只需在 `lib.rs` 末尾调用一次该宏。 #[macro_export] macro_rules! export_plugin { ($plugin_type:ty, $constructor:expr) => { fn __showen_panic_error(payload: Box) -> String { let msg = if let Some(s) = payload.downcast_ref::<&'static str>() { (*s).to_string() } else if let Some(s) = payload.downcast_ref::() { s.clone() } else { "unknown panic".to_string() }; format!("plugin panicked: {}", msg) } unsafe extern "C" fn __showen_create() -> $crate::PluginHandle { match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let plugin: Box<$plugin_type> = Box::new($constructor); Box::into_raw(plugin) as $crate::PluginHandle })) { Ok(handle) => handle, Err(_) => std::ptr::null_mut(), } } unsafe extern "C" fn __showen_get_info(handle: $crate::PluginHandle) -> $crate::FfiString { match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let plugin = unsafe { &*(handle as *const $plugin_type) }; let info = <$plugin_type as $crate::ShowenPlugin>::info(plugin); match serde_json::to_string(&info) { Ok(json) => $crate::FfiString::from_string(json), Err(_) => $crate::FfiString::null(), } })) { Ok(info) => info, Err(_) => $crate::FfiString::null(), } } unsafe extern "C" fn __showen_init( handle: $crate::PluginHandle, config_json: $crate::FfiStr, send_ctx: *mut std::ffi::c_void, send_cb: $crate::SendCallback, ) -> $crate::FfiResult { match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let plugin = unsafe { &mut *(handle as *mut $plugin_type) }; let config = match unsafe { std::ffi::CStr::from_ptr(config_json) }.to_str() { Ok(s) => s, Err(e) => return $crate::FfiResult::err(format!("invalid config UTF-8: {e}")), }; let sender = $crate::MessageSender::new(send_ctx, send_cb); match <$plugin_type as $crate::ShowenPlugin>::init(plugin, config, sender) { Ok(()) => $crate::FfiResult::ok(), Err(e) => $crate::FfiResult::err(e), } })) { Ok(result) => result, Err(payload) => $crate::FfiResult::err(__showen_panic_error(payload)), } } unsafe extern "C" fn __showen_start(handle: $crate::PluginHandle) -> $crate::FfiResult { match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let plugin = unsafe { &mut *(handle as *mut $plugin_type) }; match <$plugin_type as $crate::ShowenPlugin>::start(plugin) { Ok(()) => $crate::FfiResult::ok(), Err(e) => $crate::FfiResult::err(e), } })) { Ok(result) => result, Err(payload) => $crate::FfiResult::err(__showen_panic_error(payload)), } } unsafe extern "C" fn __showen_handle_message( handle: $crate::PluginHandle, message_json: $crate::FfiStr, ) -> $crate::FfiResult { match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let plugin = unsafe { &mut *(handle as *mut $plugin_type) }; let json_str = match unsafe { std::ffi::CStr::from_ptr(message_json) }.to_str() { Ok(s) => s, Err(e) => return $crate::FfiResult::err(format!("invalid message UTF-8: {e}")), }; let message: $crate::Message = match serde_json::from_str(json_str) { Ok(m) => m, Err(e) => return $crate::FfiResult::err(format!("invalid message JSON: {e}")), }; match <$plugin_type as $crate::ShowenPlugin>::handle_message(plugin, message) { Ok(()) => $crate::FfiResult::ok(), Err(e) => $crate::FfiResult::err(e), } })) { Ok(result) => result, Err(payload) => $crate::FfiResult::err(__showen_panic_error(payload)), } } unsafe extern "C" fn __showen_stop(handle: $crate::PluginHandle) -> $crate::FfiResult { match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let plugin = unsafe { &mut *(handle as *mut $plugin_type) }; match <$plugin_type as $crate::ShowenPlugin>::stop(plugin) { Ok(()) => $crate::FfiResult::ok(), Err(e) => $crate::FfiResult::err(e), } })) { Ok(result) => result, Err(payload) => $crate::FfiResult::err(__showen_panic_error(payload)), } } unsafe extern "C" fn __showen_destroy(handle: $crate::PluginHandle) { if let Ok(()) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { if !handle.is_null() { drop(unsafe { Box::from_raw(handle as *mut $plugin_type) }); } })) { let _ = (); } } unsafe extern "C" fn __showen_free_string(s: $crate::FfiString) { if !s.ptr.is_null() { drop(unsafe { std::ffi::CString::from_raw(s.ptr) }); } } unsafe extern "C" fn __showen_get_capabilities( handle: $crate::PluginHandle, ) -> $crate::FfiString { match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let plugin = unsafe { &*(handle as *const $plugin_type) }; let caps = <$plugin_type as $crate::ShowenPlugin>::capabilities(plugin); match serde_json::to_string(&caps) { Ok(json) => $crate::FfiString::from_string(json), Err(_) => $crate::FfiString::from_string("[]".to_string()), } })) { Ok(caps) => caps, Err(_) => $crate::FfiString::null(), } } unsafe extern "C" fn __showen_self_test(handle: $crate::PluginHandle) -> $crate::FfiString { match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let plugin = unsafe { &mut *(handle as *mut $plugin_type) }; let results = <$plugin_type as $crate::ShowenPlugin>::self_test(plugin); match serde_json::to_string(&results) { Ok(json) => $crate::FfiString::from_string(json), Err(_) => $crate::FfiString::from_string("[]".to_string()), } })) { Ok(results) => results, Err(_) => $crate::FfiString::null(), } } #[no_mangle] pub static showen_plugin_vtable: $crate::PluginVTable = $crate::PluginVTable { create: __showen_create, get_info: __showen_get_info, init: __showen_init, start: __showen_start, handle_message: __showen_handle_message, stop: __showen_stop, free_string: __showen_free_string, destroy: __showen_destroy, get_capabilities: __showen_get_capabilities, self_test: __showen_self_test, }; }; }