diff --git a/Cargo.lock b/Cargo.lock index 301e687..4188286 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,6 +93,7 @@ version = "0.1.0" dependencies = [ "dim_sdk", "lazy_static", + "log", ] [[package]] @@ -208,6 +209,7 @@ dependencies = [ "chrono", "dim_sdk", "lazy_static", + "log", ] [[package]] @@ -236,6 +238,7 @@ dependencies = [ "bytes", "lazy_static", "libc", + "log", ] [[package]] @@ -296,6 +299,7 @@ version = "0.1.0" dependencies = [ "dim_sdk", "lazy_static", + "log", ] [[package]] @@ -599,6 +603,7 @@ dependencies = [ "anyhow", "dim_sdk", "lazy_static", + "log", "regex", "serde", "toml", diff --git a/config/main.template.toml b/config/main.template.toml index 70b5387..b3c63e9 100644 --- a/config/main.template.toml +++ b/config/main.template.toml @@ -1,6 +1,9 @@ +refresh_ms=100 +sock_path="~/dim.sock" # must be full path seperator=" | " [plugins] +path="./plugins" blacklist=["example_c", "example_rust"] as_whitelist=false template=["counter", "battery", "clock"] diff --git a/config/main.toml b/config/main.toml index 70b5387..b3c63e9 100644 --- a/config/main.toml +++ b/config/main.toml @@ -1,6 +1,9 @@ +refresh_ms=100 +sock_path="~/dim.sock" # must be full path seperator=" | " [plugins] +path="./plugins" blacklist=["example_c", "example_rust"] as_whitelist=false template=["counter", "battery", "clock"] diff --git a/dim_plugins/battery/Cargo.toml b/dim_plugins/battery/Cargo.toml index a4bc6a1..f99f1ac 100644 --- a/dim_plugins/battery/Cargo.toml +++ b/dim_plugins/battery/Cargo.toml @@ -10,3 +10,4 @@ crate-type=["cdylib"] [dependencies] dim_sdk = {path="../../sdk/rust/dim_sdk"} lazy_static = "1.4.0" +log = "0.4.21" diff --git a/dim_plugins/battery/src/lib.rs b/dim_plugins/battery/src/lib.rs index e53010f..128cefc 100644 --- a/dim_plugins/battery/src/lib.rs +++ b/dim_plugins/battery/src/lib.rs @@ -1,5 +1,5 @@ use std::{fmt::Write, path::PathBuf}; -use dim_sdk::{plugin_info, Context, DimPlugin}; +use dim_sdk::{plugin_info, Context, DimPlugin, Message, Result}; use std::fs::read_to_string; plugin_info!( @@ -11,39 +11,38 @@ plugin_info!( #[derive(Debug, Clone)] struct Plug { -} - -impl Plug { - pub fn new() -> Self { - Self { - } - } + ctx: Context } const BATT0: &'static str = "/sys/class/power_supply/BAT0/capacity"; const BATT1: &'static str = "/sys/class/power_supply/BAT1/capacity"; impl DimPlugin for Plug { - fn init(&mut self, _ctx: Context) { + fn init(ctx: Context) -> Result { + dim_sdk::DimLogger::new(&ctx).init(); + Ok(Self { + ctx + }) } - fn poll(&mut self, f: &mut dim_sdk::CBuffer) -> dim_sdk::Result<()> { - //TODO: Quick fix, make this better, more portable - let path = if PathBuf::from(BATT0).exists() { BATT0 } else { BATT1 }; + fn on_message(&mut self, msg: Message) -> Result<()> { + self.ctx.buf.reset(); + match msg.name.as_str() { + "poll" => { + //TODO: Quick fix, make this better, more portable + let path = if PathBuf::from(BATT0).exists() { BATT0 } else { BATT1 }; - let contents = read_to_string(path)?; - let cleaned_contents: String = contents.chars().filter(|c| c.is_digit(10)).collect(); + let contents = read_to_string(path)?; + let cleaned_contents: String = contents.chars().filter(|c| c.is_digit(10)).collect(); - write!(f, "Battery: {}%", cleaned_contents)?; + write!(self.ctx, "Battery: {}%", cleaned_contents)?; + } + _ => () + } Ok(()) } - fn pre_reload(&mut self) { - } - fn post_reload(&mut self) { - - } - fn free(&mut self) { - - } + fn pre_reload(&mut self) -> Result<()> { Ok(()) } + fn post_reload(&mut self) -> Result<()> { Ok(()) } + fn free(&mut self) -> Result<()> { Ok(()) } } diff --git a/dim_plugins/clock/src/lib.rs b/dim_plugins/clock/src/lib.rs index 9121d54..5ba6ed7 100644 --- a/dim_plugins/clock/src/lib.rs +++ b/dim_plugins/clock/src/lib.rs @@ -1,6 +1,6 @@ use std::fmt::Write; use chrono::prelude::*; -use dim_sdk::{plugin_info, Context, DimPlugin}; +use dim_sdk::{plugin_info, Context, DimPlugin, Result}; plugin_info!( Plug, // Your main global structs name that implements `DimPlugin` @@ -11,27 +11,31 @@ plugin_info!( #[derive(Debug, Clone)] struct Plug { + ctx: Context } -impl Plug { - pub fn new() -> Self { - Self { - } - } -} impl DimPlugin for Plug { - fn init(&mut self, _ctx: Context) {} - fn poll(&mut self, f: &mut dim_sdk::CBuffer) -> dim_sdk::Result<()> { - let local: DateTime = Local::now(); - let formatted_time = local.format("%H:%M (%Y-%m-%d)").to_string(); - log::info!("Hello from clocky :3"); - write!(f, "Time: {}", formatted_time)?; + fn init(ctx: Context) -> Result { + Ok(Self { + ctx + }) + } + fn on_message(&mut self, msg: dim_sdk::Message) -> Result<()> { + self.ctx.buf.reset(); + match msg.name.as_str() { + "poll" => { + let local: DateTime = Local::now(); + let formatted_time = local.format("%H:%M (%Y-%m-%d)").to_string(); + write!(self.ctx, "Time: {}", formatted_time)?; + } + _ => () + } Ok(()) } - fn pre_reload(&mut self) {} - fn post_reload(&mut self) {} - fn free(&mut self) {} + fn pre_reload(&mut self) -> Result<()> { Ok(()) } + fn post_reload(&mut self) -> Result<()> { Ok(()) } + fn free(&mut self) -> Result<()> { Ok(()) } } diff --git a/dim_plugins/counter/Cargo.toml b/dim_plugins/counter/Cargo.toml index d162f52..cdf3e3f 100644 --- a/dim_plugins/counter/Cargo.toml +++ b/dim_plugins/counter/Cargo.toml @@ -11,3 +11,4 @@ crate-type=["cdylib"] chrono = "0.4.38" dim_sdk = {path="../../sdk/rust/dim_sdk"} lazy_static = "1.4.0" +log = "0.4.21" diff --git a/dim_plugins/counter/src/lib.rs b/dim_plugins/counter/src/lib.rs index 4d0490a..3bfa9c0 100644 --- a/dim_plugins/counter/src/lib.rs +++ b/dim_plugins/counter/src/lib.rs @@ -1,5 +1,5 @@ use std::fmt::Write; -use dim_sdk::{plugin_info, Context, DimPlugin}; +use dim_sdk::{plugin_info, Context, DimPlugin, Message, Result}; use chrono::{NaiveDate, Local}; plugin_info!( @@ -11,30 +11,32 @@ plugin_info!( #[derive(Debug, Clone)] struct Plug { -} - -impl Plug { - pub fn new() -> Self { - Self { - - } - } + ctx: Context } impl DimPlugin for Plug { - fn init(&mut self, _ctx: Context) {} - fn poll(&mut self, f: &mut dim_sdk::CBuffer) -> dim_sdk::Result<()> { + fn init(ctx: Context) -> Result { + Ok(Self { + ctx + }) + } + fn on_message(&mut self, msg: Message) -> Result<()> { + self.ctx.buf.reset(); + match msg.name.as_str() { + "poll" => { + let start_date = NaiveDate::from_ymd_opt(2024, 3, 8).expect("Invalid date"); + let current_date = Local::now().date_naive(); + let duration = current_date.signed_duration_since(start_date); - let start_date = NaiveDate::from_ymd_opt(2024, 3, 8).expect("Invalid date"); - let current_date = Local::now().date_naive(); - let duration = current_date.signed_duration_since(start_date); - - write!(f, "{} days <3", duration.num_days())?; + write!(self.ctx, "{} days <3", duration.num_days())?; + } + _ => () + } Ok(()) } - fn pre_reload(&mut self) {} - fn post_reload(&mut self) {} - fn free(&mut self) {} + fn pre_reload(&mut self) -> Result<()> { Ok(()) } + fn post_reload(&mut self) -> Result<()> { Ok(()) } + fn free(&mut self) -> Result<()> { Ok(()) } } diff --git a/dim_plugins/example_c/src/main.c b/dim_plugins/example_c/src/main.c index 428458a..addd358 100644 --- a/dim_plugins/example_c/src/main.c +++ b/dim_plugins/example_c/src/main.c @@ -1,7 +1,8 @@ #include #include #include - +// Only use this define once, cause it defines funcitons +#define PLUG_IMPL #include "dim_sdk.h" PLUG_INFO("example_c", "0.0.1", "GPLv3") @@ -9,31 +10,38 @@ PLUG_INFO("example_c", "0.0.1", "GPLv3") typedef struct plug_t { char* some_data; int count; + plug_ctx_t ctx; } plug_t; plug_t* p = {0}; -void plug_init(plug_ctx_t* ctx) { - (void) ctx; +// If any function returns 0/NULL the plugin will be disabled +int plug_init(plug_ctx_t* ctx) { + setup_ctx(ctx); p = malloc(sizeof(plug_t)); assert(p != NULL && "Buy more ram KEKW"); p->count = 0; + p->ctx = *ctx; - printf("Hello from plugin"); + // log(INFO, "Hewo fwom C :3"); + return 0; } void* plug_pre_reload() { return p; } -void plug_post_reload(void *state) { +int plug_post_reload(void *state) { p = state; + return 0; } -void plug_poll(char *buf, size_t len) { - snprintf(buf, len, "Hello from C! (%d)", p->count++); +int plug_on_msg(plug_msg_t* msg) { + snprintf(p->ctx.buf, BUF_SZ, "Hello from C! (%d)", p->count++); + return 0; } -void plug_free() { +int plug_free() { free(p); + return 0; } diff --git a/dim_plugins/example_rust/Cargo.toml b/dim_plugins/example_rust/Cargo.toml index 730ee59..c7c3a39 100644 --- a/dim_plugins/example_rust/Cargo.toml +++ b/dim_plugins/example_rust/Cargo.toml @@ -10,3 +10,4 @@ crate-type=["cdylib"] [dependencies] dim_sdk = {path="../../sdk/rust/dim_sdk"} lazy_static = "1.4.0" +log = "0.4.21" diff --git a/dim_plugins/example_rust/src/lib.rs b/dim_plugins/example_rust/src/lib.rs index ecccbae..ff23574 100644 --- a/dim_plugins/example_rust/src/lib.rs +++ b/dim_plugins/example_rust/src/lib.rs @@ -1,5 +1,5 @@ use std::fmt::Write; -use dim_sdk::{plugin_info, Context, DimPlugin}; +use dim_sdk::{plugin_info, Context, DimPlugin, Message, Result}; plugin_info!( Plug, // Your main global structs name that implements `DimPlugin` @@ -10,37 +10,45 @@ plugin_info!( #[derive(Debug, Clone)] struct Plug { + ctx: Context, counter: usize, } -impl Plug { - pub fn new() -> Self { - Self { - counter: 0 - } - } -} - impl DimPlugin for Plug { - fn init(&mut self, _ctx: Context) { + fn init(ctx: Context) -> dim_sdk::Result { // Initialise data, this will run once, it will not run again after reload + // log::info!("hewo from rust :3"); + Ok(Self { + ctx, + counter: 0 + }) } - fn poll(&mut self, f: &mut dim_sdk::CBuffer) -> dim_sdk::Result<()> { - // Write to buffer the text you want to display, keep this short - write!(f, "Hello from rust! ({})", self.counter)?; - self.counter += 1; + fn on_message(&mut self, msg: Message) -> dim_sdk::Result<()> { + self.ctx.buf.reset(); + match msg.name.as_str() { + "poll" => { + // Write to buffer the text you want to display, keep this short + write!(self.ctx, "Hello from rust! ({})", self.counter)?; + self.counter += 1; + } + _ => () + } + Ok(()) } - fn pre_reload(&mut self) { + fn pre_reload(&mut self) -> Result<()> { // Do stuff before reload, probably save important things because theres a good chance // (especially on rust) that if you change the data layout it will die + Ok(()) } - fn post_reload(&mut self) { + fn post_reload(&mut self) -> Result<()>{ // Do stuff after reloading plugin, state a.k.a this struct has the same data, will crash // if the data layout changed + Ok(()) } - fn free(&mut self) { + fn free(&mut self) -> Result<()> { // Yout probably dont need this but its for freeing things before the plugin gets unloaded + Ok(()) } } diff --git a/dim_plugins/volume/Cargo.toml b/dim_plugins/volume/Cargo.toml index f429b4e..f11a401 100644 --- a/dim_plugins/volume/Cargo.toml +++ b/dim_plugins/volume/Cargo.toml @@ -11,6 +11,7 @@ crate-type=["cdylib"] anyhow = "1.0.86" dim_sdk = {path="../../sdk/rust/dim_sdk"} lazy_static = "1.4.0" +log = "0.4.21" regex = "1.10.5" serde = { version = "1.0.203", features = ["derive"] } toml = "0.8.14" diff --git a/dim_plugins/volume/src/lib.rs b/dim_plugins/volume/src/lib.rs index c5bfac2..dabf22c 100644 --- a/dim_plugins/volume/src/lib.rs +++ b/dim_plugins/volume/src/lib.rs @@ -1,6 +1,6 @@ -use std::{fmt::Write, path::PathBuf, process::Stdio}; -use dim_sdk::{plugin_info, Context, DimPlugin}; -use std::fs::read_to_string; +use std::{fmt::Write, process::Stdio}; +use anyhow::bail; +use dim_sdk::{plugin_info, Context, DimPlugin, Message, Result}; mod config; @@ -9,57 +9,60 @@ plugin_info!( "volume", // Plugin name "1.0.0", // Plugin Version (leave empty for none) "GPLv3" // Plugin license (leave empty for none) - ); +); #[derive(Debug, Clone)] struct Plug { cfg: config::Config, -} - -impl Plug { - pub fn new() -> Self { - Self { - cfg: config::Config::default() - } - } + ctx: Context } impl DimPlugin for Plug { - fn init(&mut self, ctx: Context) { - match config::Config::parse(&ctx.config_dir.join("config.toml")) { - Ok(c) => self.cfg = c, + fn init(ctx: Context) -> Result { + let cfg; + match config::Config::parse(&ctx.config_dir.join("config.toml").clone()) { + Ok(c) => cfg = c, Err(e) => { - eprintln!("ERROR: Failed to open config file: {e}"); + log::error!("Failed to open config file: {e}"); // TODO: Add function to disable the plugin - return; + bail!("") } } - } - fn poll(&mut self, f: &mut dim_sdk::CBuffer) -> dim_sdk::Result<()> { - let mut proc = { - let mut p = std::process::Command::new("sh"); - p.arg("-c"); - p.arg(&self.cfg.commands.get_volume); - p.stdout(Stdio::piped()); - p - }; - let output = String::from_utf8(proc.output()?.stdout)?; - let re = regex::Regex::new(&self.cfg.commands.get_volume_regex)?; - - if let Some(caps) = re.captures(&output) { - let volume = &caps["vol"]; - write!(f, "Vol: {volume}%")?; + Ok(Self { ctx, cfg }) + } + fn on_message(&mut self, msg: Message) -> Result<()> { + self.ctx.buf.reset(); + match msg.name.as_str() { + "poll" => { + let mut proc = { + let mut p = std::process::Command::new("sh"); + p.arg("-c"); + p.arg(&self.cfg.commands.get_volume); + p.stdout(Stdio::piped()); + p + }; + + let output = String::from_utf8(proc.output()?.stdout)?; + let re = regex::Regex::new(&self.cfg.commands.get_volume_regex)?; + + if let Some(caps) = re.captures(&output) { + let volume = &caps["vol"]; + write!(self.ctx, "Vol: {volume}%")?; + } + } + _ => () } Ok(()) } - fn pre_reload(&mut self) { + fn pre_reload(&mut self) -> Result<()> { + Ok(()) } - fn post_reload(&mut self) { - + fn post_reload(&mut self) -> Result<()> { + Ok(()) } - fn free(&mut self) { - + fn free(&mut self) -> Result<()> { + Ok(()) } } diff --git a/sdk/c_cxx/dim_sdk.h b/sdk/c_cxx/dim_sdk.h index dfa026f..fc805f3 100644 --- a/sdk/c_cxx/dim_sdk.h +++ b/sdk/c_cxx/dim_sdk.h @@ -4,32 +4,83 @@ #include +#define BUF_SZ 2048 + +#ifdef _WIN32 +#define PATH_SEP "\\" +#else +#define PATH_SEP "/" +#endif + +typedef enum log_level_t { + ERROR=1, + WARN=2, + INFO=3, + DEBUG=4, +} log_level_t; + typedef struct plug_info_t { char* name; char* version; char* license; } plug_info_t; +typedef struct plug_funcs_ctx_t { + void (*log)(char* module, log_level_t level, char* s); +} plug_funcs_ctx_t; + typedef struct plug_ctx_t { char* config_dir; + plug_funcs_ctx_t funcs; + char* buf; } plug_ctx_t; -#define PLUG_INFO(_name, _version, _license) \ +typedef struct plug_msg_t { + char* name; + char* value; +} plug_msg_t; + +// Functions defs +// User defined funcs +int plug_init(plug_ctx_t*); // Loads when DIM initialises +void* plug_pre_reload(); // Return a pointer to save state +int plug_post_reload(void*); // returns the same pointer after reload +int plug_on_msg(plug_msg_t*); // Write the message to `buf` with max `size` characters +int plug_free(); // Free everything before being killed + +// Lib funcs +void setup_ctx(plug_ctx_t* ctx); +void _log(char* module, log_level_t level, char* s); + + +// Macros +#define PLUG_INFO(_name, _version, _license) \ static plug_info_t PLUG_INFO_VAR = { .name=(_name), .version=(_version), .license=(_license)}; \ -void* plug_get_info() { \ - return &PLUG_INFO_VAR; \ +void* plug_get_info() { \ + return &PLUG_INFO_VAR; \ } +#define log(level, ...) do { \ + char* buf = (char*)malloc(BUF_SZ*sizeof(char)); \ + char* mod = (char*)malloc(BUF_SZ*sizeof(char)); \ + snprintf(mod, BUF_SZ*sizeof(char), \ + "(plug) %s/%s", PLUG_INFO_VAR.name, __FILE__); \ + snprintf(buf, BUF_SZ*sizeof(char), __VA_ARGS__); \ + _log(mod, level, buf); \ + free(buf); \ + free(mod); \ +} while (0) -#define PLUG_NAME(s) volatile char* PLUG_NAME = (s); -#define PLUG_VERSION(s) volatile char* PLUG_VERSION = (s); -#define PLUG_LICENSE(s) volatile char* PLUG_LICENSE = (s); +#ifdef PLUG_IMPL +static plug_ctx_t CTX = {0}; +void setup_ctx(plug_ctx_t* ctx) { + CTX = *ctx; +} -void plug_init(plug_ctx_t*); // Loads when DIM initialises -void* plug_pre_reload(); // Return a pointer to save state -void plug_post_reload(void*); // returns the same pointer after reload -void plug_poll(char*, size_t); // Write the message to `buf` with max `size` characters -void plug_free(); // Free everything before being killed +void _log(char* module, log_level_t level, char* s) { + (CTX.funcs.log)(module, level, s); +} #endif +#endif diff --git a/sdk/rust/dim_sdk/Cargo.toml b/sdk/rust/dim_sdk/Cargo.toml index 40c7f88..53eaa44 100644 --- a/sdk/rust/dim_sdk/Cargo.toml +++ b/sdk/rust/dim_sdk/Cargo.toml @@ -11,3 +11,4 @@ anyhow = "1.0.86" bytes = "1.6.0" lazy_static = { version = "1.4.0", features = ["spin"] } libc = "0.2.155" +log = { version = "0.4.21", features = ["std"] } diff --git a/sdk/rust/dim_sdk/src/c_buffer.rs b/sdk/rust/dim_sdk/src/c_buffer.rs index 4ae6f51..640baf2 100644 --- a/sdk/rust/dim_sdk/src/c_buffer.rs +++ b/sdk/rust/dim_sdk/src/c_buffer.rs @@ -1,33 +1,40 @@ -#[derive(Debug)] -pub struct CBuffer<'a> { - inner: &'a mut [i8], +#[derive(Debug, Clone)] +pub struct CBuffer { + inner: Vec, count: usize, capacity: usize } #[allow(dead_code)] // rust_analyzer too dumb to see my macro magic // i hate that macro as much as you do -impl CBuffer<'_> { - pub fn from_raw_parts_mut(buf: *mut i8, capacity: usize) -> Self { +impl CBuffer { + pub fn from_raw_parts_mut(buf: *mut u8, capacity: usize) -> Self { Self { inner: unsafe { - std::slice::from_raw_parts_mut(buf, capacity) + Vec::from_raw_parts(buf, capacity, capacity) }, capacity, count: 0, } } + + pub fn reset(&mut self) { + for i in 0..self.capacity { + self.inner[i] = 0; + } + self.count = 0; + } } -impl std::fmt::Write for CBuffer<'_> { +impl std::fmt::Write for CBuffer { fn write_str(&mut self, buf: &str) -> Result<(), std::fmt::Error> { for c in buf.as_bytes() { if self.count >= self.capacity - 1 { return Ok(()); } - self.inner[self.count] = *c as i8; + self.inner[self.count] = *c; self.count += 1; } self.inner[self.count] = 0; diff --git a/sdk/rust/dim_sdk/src/context.rs b/sdk/rust/dim_sdk/src/context.rs index 2f3eef7..30ebc8f 100644 --- a/sdk/rust/dim_sdk/src/context.rs +++ b/sdk/rust/dim_sdk/src/context.rs @@ -1,25 +1,46 @@ use std::{ffi::{c_char, CStr}, path::PathBuf}; +use crate::CBuffer; + +#[derive(Debug, Clone)] #[repr(C)] pub struct ContextRaw { - pub config_dir: *const c_char + pub config_dir: *const c_char, + pub funcs: PluginContextFuncs, + pub buf: *mut u8, } #[derive(Debug, Clone)] pub struct Context { - pub config_dir: PathBuf + pub config_dir: PathBuf, + pub funcs: PluginContextFuncs, + pub buf: CBuffer +} + +#[repr(C)] +#[derive(Debug, Clone)] +pub struct PluginContextFuncs { + pub log: unsafe extern "C" fn(module: *const c_char, level: log::Level, s: *const c_char), } impl Context { - pub fn new(cr: *const ContextRaw) -> Self { + pub fn new(cr: ContextRaw) -> Self { let config_dir = unsafe { - let v = CStr::from_ptr((*cr).config_dir); - PathBuf::from(v.to_string_lossy().to_string()) + let v = CStr::from_ptr(cr.config_dir); + PathBuf::from(v.to_string_lossy().to_string().clone()) }; - Self { - config_dir + config_dir, + funcs: cr.funcs.clone(), + buf: CBuffer::from_raw_parts_mut(cr.buf, crate::BUF_SZ) } } } + +impl std::fmt::Write for Context { + fn write_str(&mut self, buf: &str) -> Result<(), std::fmt::Error> { + self.buf.write_str(buf) + } +} + diff --git a/sdk/rust/dim_sdk/src/lib.rs b/sdk/rust/dim_sdk/src/lib.rs index 5c9d5e5..784c0a0 100644 --- a/sdk/rust/dim_sdk/src/lib.rs +++ b/sdk/rust/dim_sdk/src/lib.rs @@ -4,23 +4,55 @@ mod magic; mod c_buffer; mod plugin_info; mod context; +mod logger; + +use std::ffi::{c_char, CStr}; pub use c_buffer::*; pub use plugin_info::*; pub use anyhow::Result; pub use context::*; +pub use logger::*; + +pub const BUF_SZ: usize = 2048; + +pub trait DimPlugin: Clone{ + fn init(ctx: Context) -> Result; + fn pre_reload(&mut self) -> Result<()>; + fn post_reload(&mut self) -> Result<()>; + fn on_message(&mut self, msg: Message) -> Result<()>; + fn free(&mut self) -> Result<()>; +} + +#[derive(Debug)] +pub struct Message { + pub name: String, + pub value: String, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct MessageRaw { + pub name: *const c_char, + pub value: *const c_char, +} + + +impl Message { + pub fn new(msg: MessageRaw) -> Self { + unsafe { + Self { + name: CStr::from_ptr(msg.name.clone()) + .to_string_lossy().to_string(), + value: CStr::from_ptr(msg.value.clone()) + .to_string_lossy().to_string() + } + } + } -pub trait DimPlugin: Clone { - fn init(&mut self, ctx: Context); - fn pre_reload(&mut self); - fn post_reload(&mut self); - fn poll(&mut self, f: &mut CBuffer) -> Result<()>; - fn free(&mut self) {} } - - diff --git a/sdk/rust/dim_sdk/src/logger.rs b/sdk/rust/dim_sdk/src/logger.rs new file mode 100644 index 0000000..959c73e --- /dev/null +++ b/sdk/rust/dim_sdk/src/logger.rs @@ -0,0 +1,37 @@ +use std::ffi::{c_char, CString}; + +use crate::Context; + +#[allow(dead_code)] +#[derive(Debug, Clone)] +pub struct DimLogger { + log_fn: unsafe extern "C" fn(module: *const c_char, level: log::Level, s: *const c_char), +} + +impl log::Log for DimLogger { + fn enabled(&self, _: &log::Metadata) -> bool {true} + + fn log(&self, record: &log::Record) { + unsafe { + let module = format!("(plug) {}", record.module_path().unwrap_or("UNKNOWN")); + let module = CString::new(module).unwrap(); + let s = CString::new(format!("{}", record.args())).unwrap(); + (self.log_fn)(module.as_ptr(), record.level(), s.as_ptr()); + } + } + + fn flush(&self) {} +} + +impl DimLogger { + pub fn new(ctx: &Context) -> Self { + Self { + log_fn: ctx.funcs.log, + } + } + + pub fn init(&self) { + let _ = log::set_boxed_logger(Box::new(self.clone())) + .map(|()| log::set_max_level(log::LevelFilter::Debug)); + } +} diff --git a/sdk/rust/dim_sdk/src/magic.rs b/sdk/rust/dim_sdk/src/magic.rs index aa557b0..8809959 100644 --- a/sdk/rust/dim_sdk/src/magic.rs +++ b/sdk/rust/dim_sdk/src/magic.rs @@ -2,7 +2,7 @@ #[macro_export] macro_rules! plugin_info { ($typ:ty, $name:literal, $version:literal, $license:literal) => { - + lazy_static::lazy_static!( static ref PLUGIN_INFO: $crate::PluginInfo = $crate::PluginInfo::new($name, $version, $license); ); @@ -14,61 +14,87 @@ macro_rules! plugin_info { PLUGIN_INFO.get_raw_ptr() } - #[no_mangle] - unsafe extern "C" fn plug_init(ctx: *const $crate::ContextRaw) { - //PLUG = std::alloc::alloc(std::alloc::Layout::new::<$typ>()) as *mut $typ; - PLUG = Some(Plug::new()); - - let ctx = $crate::Context::new(ctx); - if let Some(p) = &mut PLUG { - p.init(ctx) - } else { - unreachable!(); + impl $typ { + pub fn get_plugin_info(&self) -> $crate::PluginInfo { + $crate::PluginInfo::new( + PLUGIN_INFO.name().as_str(), + PLUGIN_INFO.version().as_str(), + PLUGIN_INFO.license().as_str() + ) } } + #[no_mangle] + unsafe extern "C" fn plug_init(ctx: *const $crate::ContextRaw) -> i32 { + let ctx = $crate::Context::new((*ctx).clone()); + $crate::DimLogger::new(&ctx).init(); + match <$typ>::init(ctx) { + Ok(v) => { + PLUG = Some(v); + return 0; + } + Err(e) => { + log::error!("Had an error while initialising: {e}"); + return 1; + } + }; + } + #[no_mangle] unsafe extern "C" fn plug_pre_reload() -> *mut $typ { //TODO: Untested if let Some(p) = &mut PLUG { - p.pre_reload(); + if let Err(e) = p.pre_reload() { + log::error!("Had an error on pre reload: {e}"); + return std::ptr::null_mut(); + } return p as *mut $typ; } unreachable!(); } #[no_mangle] - unsafe extern "C" fn plug_post_reload(state: *mut $typ) { + unsafe extern "C" fn plug_post_reload(state: *mut $typ) -> i32 { PLUG = Some((*state).clone()); if let Some(p) = &mut PLUG { - p.post_reload(); - } else { - unreachable!(); - } - //TODO: Untested - } - - #[no_mangle] - unsafe extern "C" fn plug_poll(buf: *mut i8, len: std::ffi::c_uint) { - let mut buf = $crate::CBuffer::from_raw_parts_mut(buf, len as usize); - if let Some(p) = &mut PLUG { - if let Err(_e) = p.poll(&mut buf) { - // TODO: Handle error maybe? + if let Err(e) = p.post_reload() { + log::error!("Had an error on pre reload: {e}"); + return 1; } } else { unreachable!(); } + //TODO: Untested + return 0; } #[no_mangle] - unsafe extern "C" fn plug_free() { + unsafe extern "C" fn plug_on_msg(msg: *const $crate::MessageRaw) -> i32 { + let msg = $crate::Message::new((*msg).clone()); if let Some(p) = &mut PLUG { - // std::alloc::dealloc(PLUG as *mut u8, std::alloc::Layout::new::<$typ>()); - p.free(); + if let Err(e) = p.on_message(msg) { + log::error!("Had error on `on_msg`: {e}"); + return 1; + } } else { unreachable!(); } + return 0; + } + + #[no_mangle] + unsafe extern "C" fn plug_free() -> i32 { + if let Some(p) = &mut PLUG { + // std::alloc::dealloc(PLUG as *mut u8, std::alloc::Layout::new::<$typ>()); + if let Err(e) = p.free() { + log::error!("Had error on `free`: {e}"); + return 1; + } + } else { + unreachable!(); + } + return 0; } }; diff --git a/sdk/rust/dim_sdk/src/plugin_info.rs b/sdk/rust/dim_sdk/src/plugin_info.rs index 835de11..392f676 100644 --- a/sdk/rust/dim_sdk/src/plugin_info.rs +++ b/sdk/rust/dim_sdk/src/plugin_info.rs @@ -36,6 +36,16 @@ impl PluginInfo { pub fn get_raw_ptr(&self) -> *const c_void { &self.ptrs as *const _ as *const c_void } + + pub fn name(&self) -> String { + self._name.to_string_lossy().to_string() + } + pub fn version(&self) -> String { + self._version.to_string_lossy().to_string() + } + pub fn license(&self) -> String { + self._license.to_string_lossy().to_string() + } } unsafe impl Sync for PluginInfo {} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..a30b442 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,46 @@ +use clap::{Parser, Subcommand}; + + +#[derive(Debug, Parser)] +pub struct CliArgs { + /// Directory where all *.dim plugin files are stored + #[arg(long, short, default_value="./plugins")] + pub plugin_dir: camino::Utf8PathBuf, + + /// Directory where all configurations are stored + #[arg(long, short, default_value="./config")] + pub config_dir: camino::Utf8PathBuf, + + /// Print more debug info + #[arg(long, short)] + pub debug: bool, + + /// Socket path + #[arg(long, short)] + pub sock_path: Option, + + #[command(subcommand)] + pub subcommand: Option, +} + +#[derive(Debug, Subcommand)] +pub enum CliSubcommand { + /// Send a message through ipc to the running DIM instance + Send { + /// Command name, eg. `clock:reset`, + /// `clock:` will be stripped and the command will be sent + /// only to plugin `clock`, dont include it to send it to all plugins + #[arg(long, short)] + name: String, + + /// Value to send + #[arg(long, short, default_value="")] + value: String, + }, + + /// Reload one or all plugins + Reload { + /// (Optional) Which plugin to Reload + name: Option + } +} diff --git a/src/config.rs b/src/config.rs index f68727a..1cfa7b8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,10 +2,14 @@ use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; +use crate::cli::CliArgs; + #[derive(Debug, Serialize, Deserialize, Default)] pub struct Config { pub seperator: String, + pub refresh_ms: usize, + pub sock_path: String, pub plugins: ConfigPlugins, #[serde(skip)] @@ -14,24 +18,26 @@ pub struct Config { #[derive(Debug, Serialize, Deserialize, Default)] pub struct ConfigPlugins { + pub path: PathBuf, pub blacklist: Vec, pub as_whitelist: bool, pub template: Vec, } impl Config { - pub fn new(path: &Path) -> Result { - let cfg_path = path.join("main.toml"); + pub fn new(cli: CliArgs) -> Result { + let cfg_path = cli.config_dir.join("main.toml"); if !cfg_path.exists() { - println!("ERROR: {:?} doesnt exist", path); + println!("ERROR: {:?} doesnt exist", cli.config_dir); } let mut cfg: Self = toml::from_str( &std::fs::read_to_string(&cfg_path)? )?; - cfg.config_dir = path.to_path_buf(); + cfg.config_dir = cli.config_dir.as_std_path().to_path_buf(); + cfg.plugins.path = cli.plugin_dir.as_std_path().to_path_buf(); Ok(cfg) } } diff --git a/src/display.rs b/src/display.rs index 02680e5..d288a0c 100644 --- a/src/display.rs +++ b/src/display.rs @@ -80,7 +80,7 @@ impl Display { } } if !found { - log::warn!("Plugin '{}' was not found even though it was included in the template", tv); + // log::warn!("Plugin '{}' was not found even though it was included in the template", tv); } } diff --git a/src/ipc/client/mod.rs b/src/ipc/client/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/ipc/mod.rs b/src/ipc/mod.rs new file mode 100644 index 0000000..3413896 --- /dev/null +++ b/src/ipc/mod.rs @@ -0,0 +1,63 @@ +use std::ffi::{c_char, CString}; + +pub mod client; +pub mod server; + +#[derive(Debug)] +pub struct Message { + pub plugin: Option, + pub name: CString, + pub value: CString, + pub ffi: MessageFFI +} + +#[derive(Debug)] +#[repr(C)] +pub struct MessageFFI { + pub name: *const c_char, + pub value: *const c_char, +} + +impl Message { + pub fn from_string_kv(name: S, value: S) -> Self { + let mut name = name.to_string(); + let value = value.to_string(); + let plugin; + if name.contains(':') { + let ns: Vec<&str> = name.split(':').collect(); + plugin = Some(ns[0].to_string()); + name = ns[1..].join(":"); + + } else { + plugin = None; + } + + let name = CString::new(name).unwrap(); + let value = CString::new(value).unwrap(); + + + Self { + ffi: MessageFFI { + name: name.as_ptr(), + value: value.as_ptr() + }, + plugin, + name, + value, + } + } + + pub fn from_string(k: S) -> Self { + let k = k.to_string(); + let mut v = String::new(); + let mut name = k.clone(); + if k.contains('=') { + let kv: Vec<&str> = k.split('=').collect(); + name = kv[0].to_string(); + v = kv[1..].join("="); + } + + Self::from_string_kv(name, v) + } + +} diff --git a/src/ipc/server/mod.rs b/src/ipc/server/mod.rs new file mode 100644 index 0000000..af4b41a --- /dev/null +++ b/src/ipc/server/mod.rs @@ -0,0 +1,27 @@ +use std::{io::Read, os::unix::net::UnixStream}; + +use crate::config::Config; + +use super::Message; + + +pub struct IpcServer{ + sock: UnixStream +} + +impl IpcServer { + pub fn start(cfg: &Config) -> std::io::Result { + Ok(Self { + sock: UnixStream::connect(&cfg.sock_path)?, + }) + } + + pub fn poll(&mut self) -> Option { + let mut buf = String::new(); + let _ = self.sock.read_to_string(&mut buf); + if buf.is_empty() { + return None; + } + Some(Message::from_string(buf)) + } +} diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 0000000..d044dc6 --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,14 @@ +pub use crate::cli::CliArgs; + +pub fn init(ca: &CliArgs) { + if ca.debug { + env_logger::builder() + .filter(None, log::LevelFilter::Debug) + .init(); + } else { + env_logger::builder() + .filter(None, log::LevelFilter::Info) + .init(); + } +} + diff --git a/src/main.rs b/src/main.rs index 60228b8..893bec5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,48 +1,29 @@ #![allow(internal_features)] #![feature(core_intrinsics)] -use std::{ffi::c_char, process::ExitCode, time::Duration}; +use std::{process::ExitCode, time::Duration}; use clap::Parser; mod display; mod plugman; mod config; mod util; +mod cli; +mod logger; +mod ipc; -#[derive(Debug, Parser)] -struct CliArgs { - #[arg(long, short, default_value="./plugins")] - pub plugin_dir: camino::Utf8PathBuf, - #[arg(long, short, default_value="./config")] - pub config_dir: camino::Utf8PathBuf, - #[arg(long, short)] - pub debug: bool -} - -#[allow(non_camel_case_types)] -pub type c_str = *const c_char; - -// TODO: Clean up main function -// TODO: Make ffi safe abstraction for logger +// TODO: Make function available in ctx that disables the plugin // TODO: Set up ipc with unix sockets // TODO: Allow sending messages command -> running DIM instance -> plugin with ipc // TODO: Clickable bar: https://dwm.suckless.org/patches/statuscmd/ // TODO: Run code through clippy fn main() -> ExitCode { - let ca = CliArgs::parse(); + let ca = cli::CliArgs::parse(); - if ca.debug { - env_logger::builder() - .filter(None, log::LevelFilter::Debug) - .init(); - } else { - env_logger::builder() - .filter(None, log::LevelFilter::Info) - .init(); - } + logger::init(&ca); let config; - match config::Config::new(ca.config_dir.as_std_path()) { + match config::Config::new(ca) { Ok(v) => config = v, Err(e) => { log::error!("Failed to parse config: {e:?}"); @@ -55,7 +36,7 @@ fn main() -> ExitCode { let mut pm = plugman::PlugMan::new(1024); // idk tbh - if let Err(e) = pm.load(ca.plugin_dir.as_std_path().to_path_buf()){ + if let Err(e) = pm.load(&config.plugins.path){ log::error!("Failed to load plugins: {e:?}"); return ExitCode::from(3); } @@ -64,6 +45,8 @@ fn main() -> ExitCode { loop { let vals = pm.poll_plugins(); + + log::debug!("vals: {vals:?}"); if let Err(e) = disp.write_with_vec(&config, vals) { log::warn!("Failed to write too bar: {e}"); } diff --git a/src/plugman/mod.rs b/src/plugman/mod.rs index 66be77b..a76947d 100644 --- a/src/plugman/mod.rs +++ b/src/plugman/mod.rs @@ -1,4 +1,6 @@ -use std::{collections::HashMap, path::PathBuf}; +use std::{collections::HashMap, path::{Path, PathBuf}}; + +use crate::ipc::Message; use self::plugin::Plugin; @@ -19,7 +21,8 @@ impl PlugMan { } } - pub fn load(&mut self, dir: PathBuf) -> Result<(), PluginError>{ + pub fn load>(&mut self, dir: P) -> Result<(), PluginError>{ + let dir: &Path = dir.as_ref(); log::debug!("Loading plugins from {:?}", dir); let files = std::fs::read_dir(dir)?; @@ -50,7 +53,7 @@ impl PlugMan { } pub fn init_plugins(&mut self, cfg: &crate::config::Config) { - for plugin in &self.plugins { + for plugin in &mut self.plugins { plugin.init(cfg); } } @@ -73,20 +76,16 @@ impl PlugMan { pub fn poll_plugins(&mut self) -> HashMap { let mut answers = HashMap::new(); - let len = self.len_cap / self.plugins.len(); - for plugin in &self.plugins { + for plugin in &mut self.plugins { if !plugin.enabled() { continue; } - match plugin.poll(len) { + match plugin.poll() { Ok(v) => { - let mut pth = (plugin.name().clone(), PolledText::new(v)); - if pth.1.text().is_empty() { - pth.1.disable() + let pth = (plugin.name().clone(), PolledText::new(v)); + if !pth.1.text().is_empty() { + answers.insert(pth.0, pth.1); } - - - answers.insert(pth.0, pth.1); }, Err(e) => log::error!("Failed to poll plugin: {e}"), } @@ -94,6 +93,18 @@ impl PlugMan { } answers } + + pub fn send_msg(&mut self, msg: Message) { + for plugin in &mut self.plugins { + if let Some(pn) = &msg.plugin { + if *pn != plugin.name() { + continue; + } + } + plugin.send_msg(msg); + break; + } + } } #[derive(Debug, Clone)] diff --git a/src/plugman/plugin/context.rs b/src/plugman/plugin/context.rs index bd74d38..cd16cfa 100644 --- a/src/plugman/plugin/context.rs +++ b/src/plugman/plugin/context.rs @@ -1,4 +1,5 @@ -use std::ffi::{c_char, CString}; +use std::ffi::{c_char, CStr, CString}; +use super::Plugin; pub struct PluginContextContainer { pub inner: PluginContext, @@ -8,24 +9,42 @@ pub struct PluginContextContainer { #[repr(C)] pub struct PluginContext { - pub config_dir: *const c_char + pub config_dir: *const c_char, + funcs: PluginContextFuncs, + buf: *mut u8 +} + +#[repr(C)] +pub struct PluginContextFuncs { + log: unsafe extern "C" fn(module: *const c_char, level: log::Level, s: *const c_char), } impl PluginContext { - pub fn new(config_dir: &CString) -> Self { + pub fn new(plug: &Plugin, config_dir: &CString) -> Self { Self { - config_dir: config_dir.as_ptr() + config_dir: config_dir.as_ptr(), + funcs: PluginContextFuncs { + log: plug_log, + }, + buf: plug.buf } } } impl PluginContextContainer { - pub fn new(config_dir: &String) -> Self { + pub fn new(plug: &Plugin, config_dir: &String) -> Self { let _config_dir = CString::new(config_dir.clone()).unwrap(); Self { - inner: PluginContext::new(&_config_dir), + inner: PluginContext::new(plug, &_config_dir), _config_dir, } } } + +pub unsafe extern "C" fn plug_log(module: *const c_char, level: log::Level, s: *const c_char) { + let module = CStr::from_ptr(module).to_string_lossy().to_string(); + let s = CStr::from_ptr(s).to_string_lossy().to_string(); + + log::log!(target: &module, level, "{s}"); +} diff --git a/src/plugman/plugin/loader.rs b/src/plugman/plugin/loader.rs index 38add1a..aad7069 100644 --- a/src/plugman/plugin/loader.rs +++ b/src/plugman/plugin/loader.rs @@ -1,17 +1,16 @@ -use std::ffi::c_char; - use dlopen::raw::Library; +use crate::ipc::MessageFFI; use super::{context::PluginContext, Plugin, PluginInfo}; #[derive(Debug, Clone, Copy)] pub struct PluginSyms { - pub(super) init: unsafe extern "C" fn(ctx: *const PluginContext), + pub(super) init: unsafe extern "C" fn(ctx: *const PluginContext) -> i32, pub(super) pre_reload: unsafe extern "C" fn() -> *const (), - pub(super) post_reload: unsafe extern "C" fn(state: *const ()), - pub(super) poll: unsafe extern "C" fn(buf: *mut c_char, len: usize), - pub(super) free: unsafe extern "C" fn(), + pub(super) post_reload: unsafe extern "C" fn(state: *const ()) -> i32, + pub(super) on_msg: unsafe extern "C" fn(msg: *const MessageFFI) -> i32, + pub(super) free: unsafe extern "C" fn() -> i32, pub(super) get_info: unsafe extern "C" fn() -> *const PluginInfo, } @@ -23,7 +22,7 @@ impl Plugin { init: unsafe { lib.symbol("plug_init")? }, pre_reload: unsafe { lib.symbol("plug_pre_reload")? }, post_reload: unsafe { lib.symbol("plug_post_reload")? }, - poll: unsafe { lib.symbol("plug_poll")? }, + on_msg: unsafe { lib.symbol("plug_on_msg")? }, free: unsafe { lib.symbol("plug_free")? }, get_info: unsafe { lib.symbol("plug_get_info")? }, diff --git a/src/plugman/plugin/mod.rs b/src/plugman/plugin/mod.rs index 0fc47ab..7de2a6c 100644 --- a/src/plugman/plugin/mod.rs +++ b/src/plugman/plugin/mod.rs @@ -2,35 +2,46 @@ use std::{alloc::Layout, ffi::{c_char, CStr}, path::PathBuf}; use dlopen::raw::Library; -use crate::{c_str, config::Config}; +use crate::{config::Config, ipc::{Message, MessageFFI}}; use self::context::{PluginContext, PluginContextContainer}; pub mod loader; pub mod context; +const MEM_LAYOUT: Result = Layout::from_size_align(2048, 1); + #[repr(C)] pub struct PluginInfo { - name: c_str, - version: c_str, - license: c_str, + name: *const c_char, + version: *const c_char, + license: *const c_char, } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct Plugin { syms: Option, path: PathBuf, lib: Option, enabled: bool, + buf: *mut u8 } impl Plugin { pub fn load(path: PathBuf) -> Result { - let mut s = Self::default(); - s.enabled = true; - s.path = path; + let buf = unsafe { + std::alloc::alloc_zeroed(MEM_LAYOUT.unwrap()) + }; + + let mut s = Self { + enabled: true, + path, + syms: None, + lib: None, + buf + }; s.reload_symbols()?; Ok(s) } @@ -39,51 +50,71 @@ impl Plugin { self.syms.unwrap() } - pub fn init(&self, cfg: &Config) { + pub fn init(&mut self, cfg: &Config) { let conf_dir = &cfg.config_dir.join(self.name()).to_string_lossy().to_string(); let _ = std::fs::create_dir(&conf_dir); // dont care - let ctx = PluginContextContainer::new(conf_dir); + let ctx = PluginContextContainer::new(self, conf_dir); unsafe { - (self.syms().init)(&ctx.inner as *const PluginContext) + if (self.syms().init)(&ctx.inner as *const PluginContext) != 0 { + log::warn!("`init` had an error, disabling"); + self.disable(); + } } } pub fn reload(&mut self) -> Result<(), dlopen::Error> { unsafe { let state = (self.syms().pre_reload)(); + + if state.is_null() { + log::warn!("`pre_reload` had an error, disabling"); + self.disable(); + } + if let Err(e) = self.reload_symbols() { self.disable(); return Err(e); } - (self.syms().post_reload)(state); + if (self.syms().post_reload)(state) != 0 { + log::warn!("`post_reload` had an error, disabling"); + self.disable(); + } + } Ok(()) } - pub fn poll(&self, cap: usize) -> Result { - let layout = Layout::from_size_align(cap, 1).unwrap(); - let buf = unsafe { - std::alloc::alloc_zeroed(layout) - }; - let s = unsafe { - (self.syms().poll)(buf as *mut i8, cap); - let len = libc::strlen(buf as *const i8); - if len > cap { - panic!("String len is bigger than allocatd"); + pub fn send_msg(&mut self, msg: Message) { + unsafe { + // log::debug!("Sending message to '{}': {:?}", self.name(), msg); + if (self.syms().on_msg)(&msg.ffi as *const MessageFFI) != 0 { + log::warn!("`on_msg` had an error, disabling"); + self.disable(); } - - String::from_raw_parts(buf, len, cap) - }; - - if !s.is_empty() { - log::debug!("Polled: {}", s); } + } + + pub fn poll(&mut self) -> Result { + self.send_msg(Message::from_string("poll")); + let s = unsafe { + CStr::from_ptr(self.buf as *const c_char).to_string_lossy().to_string() + }; + + if !s.is_empty() { + // log::debug!("Polled: {}", s); + } + Ok(s) } - pub fn free(self) { + + pub fn free(mut self) { unsafe { - (self.syms().free)(); + if (self.syms().free)() != 0 { + log::warn!("`free` had an error, disabling"); + self.disable(); // Literaly useless but eh + } + std::alloc::dealloc(self.buf, MEM_LAYOUT.unwrap()); } drop(self.lib.unwrap()); }