Files
ShowenV2/plugin-sdk/src/lib.rs

1002 lines
36 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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<u8>, 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<String> {
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<String> {
/// 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<String> {
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<CapabilityTestResult> {
/// 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<CapabilityTestResult> {
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<MessageSender>,
/// # }
/// # 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<MessageSender>,
/// # }
/// # 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<MessageSender>,
/// }
///
/// 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<dyn std::any::Any + Send>) -> String {
let msg = if let Some(s) = payload.downcast_ref::<&'static str>() {
(*s).to_string()
} else if let Some(s) = payload.downcast_ref::<String>() {
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,
};
};
}