santui_core/plugin.rs
1use crate::auth::{AuthHandle, User};
2use crate::db_access::DbAccess;
3use crate::theme::Theme;
4use crossterm::event::{KeyEvent, MouseEvent};
5use ratatui::layout::Rect;
6use ratatui::Frame;
7use std::path::{Path, PathBuf};
8use std::sync::Arc;
9
10/// Factory that creates a `Box<dyn Plugin>` from an id, name, and binary path.
11/// The binary (`santui`) sets this to `IpcPluginHost::new_boxed`.
12pub type PluginFactory = Arc<dyn Fn(&str, &str, &Path) -> Box<dyn Plugin + Send> + Send + Sync>;
13
14/// A command that a plugin registers for the Ctrl+P palette.
15#[derive(Debug, Clone)]
16pub struct PluginCmdItem {
17 pub category: String,
18 pub label: String,
19}
20
21pub trait Plugin: Send {
22 fn id(&self) -> &str;
23 fn name(&self) -> &str;
24 fn init(&mut self, ctx: &mut PluginContext) -> Result<(), Box<dyn std::error::Error>>;
25 fn handle_key(&mut self, _key: KeyEvent) -> bool {
26 false
27 }
28 fn handle_mouse(&mut self, _event: &MouseEvent) -> bool {
29 false
30 }
31 fn render(&self, _f: &mut Frame, _area: Rect) {}
32 fn tick(&mut self) {}
33 fn on_focus(&mut self) {}
34 fn on_blur(&mut self) {}
35 fn on_theme_change(&mut self, _theme: &Theme) {}
36 fn on_user_update(&mut self, _user: Option<&User>) {}
37 fn status_hints(&self) -> Vec<(String, String)> {
38 vec![]
39 }
40 /// Palette commands that this plugin registers (category, label).
41 fn commands(&self) -> Vec<PluginCmdItem> {
42 vec![]
43 }
44 /// Called when a palette command from this plugin is selected.
45 /// `index` is the position in `commands()`.
46 fn handle_palette_command(&mut self, _index: usize) {}
47
48 /// Called when a plugin-to-plugin message arrives.
49 fn on_plugin_message(&mut self, _from: &str, _action: &str, _data: &str) {}
50
51 /// Called before the plugin is unloaded (hot-reload or app exit).
52 /// The plugin should flush state, close handles, and prepare to be dropped.
53 /// For IPC plugins this sends `Shutdown` and waits briefly for a response
54 /// before the child process is killed.
55 fn shutdown(&mut self) {}
56
57 /// Return the filesystem path to this plugin's binary, if it runs as an
58 /// external process. `None` for in-process plugins. Used by the hot-reload
59 /// mechanism to detect when the binary has been updated on disk.
60 fn binary_path(&self) -> Option<&Path> {
61 None
62 }
63
64 /// Whether the plugin process is still running.
65 /// Returns `true` for in-process plugins.
66 /// IPC plugins override this to check the child process.
67 fn is_alive(&self) -> bool {
68 true
69 }
70
71 /// Whether the plugin supports running in the background when the user
72 /// presses Esc (e.g., audio players that should keep playing).
73 /// Default is `false`. Override to return `true` for background-capable plugins.
74 fn can_background(&self) -> bool {
75 false
76 }
77
78 /// Set runtime capabilities (e.g. `"background"`) declared in the plugin manifest.
79 /// Default is no-op; IPC plugins override this to store the value.
80 fn set_capabilities(&mut self, _caps: Vec<String>) {}
81
82 /// Whether this plugin should stay loaded when the user presses Esc.
83 /// Persistent plugins are blurred but not shut down, keeping their
84 /// palette entries and background processes alive.
85 /// Default is `false`. Override to return `true` for plugins that
86 /// must not be unloaded (e.g., the registry plugin).
87 fn persistent(&self) -> bool {
88 false
89 }
90
91 /// Mark this plugin as persistent. Default is no-op; IPC plugins
92 /// override this to store the value.
93 fn set_persistent(&mut self, _persistent: bool) {}
94
95 /// Check if a pending Esc key response has been resolved.
96 /// Returns `Some(consumed)` if resolved, `None` if still pending
97 /// or no Esc was sent.
98 /// IPC plugins override this; in-process plugins don't need it.
99 fn take_pending_esc_result(&mut self) -> Option<bool> {
100 None
101 }
102
103 /// Process any pending requests from the plugin (e.g. DB read/write,
104 /// authentication). Called once per main loop iteration.
105 /// The default implementation is a no-op. IPC plugins override this
106 /// to forward `PluginRequest` variants to the host.
107 fn process_pending_requests(
108 &mut self,
109 _db: &mut dyn DbAccess,
110 _auth: Option<&Arc<dyn AuthHandle>>,
111 ) {
112 }
113}
114
115pub struct PluginContext {
116 pub theme: Theme,
117 pub auth: Option<Arc<dyn AuthHandle>>,
118 /// Santui data directory (platform-standard, e.g. `%APPDATA%/santui` on Windows). Plugins can use this
119 /// for persistent storage. The registry plugin uses it to find
120 /// installed plugins and `registry.toml`.
121 pub data_dir: PathBuf,
122}
123
124impl PluginContext {
125 pub fn new() -> Self {
126 PluginContext {
127 theme: Theme::default(),
128 auth: None,
129 data_dir: PathBuf::new(),
130 }
131 }
132}
133
134impl Default for PluginContext {
135 fn default() -> Self {
136 Self::new()
137 }
138}