1002 lines
36 KiB
Rust
1002 lines
36 KiB
Rust
//! 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,
|
||
};
|
||
};
|
||
}
|