Too lazy to make propper edit but i changed the
polling system to a message one so we can have more ways we can interract with plugins (reload/ reset/ on click events)
This commit is contained in:
46
src/cli.rs
Normal file
46
src/cli.rs
Normal file
@@ -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<camino::Utf8PathBuf>,
|
||||
|
||||
#[command(subcommand)]
|
||||
pub subcommand: Option<CliSubcommand>,
|
||||
}
|
||||
|
||||
#[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<String>
|
||||
}
|
||||
}
|
||||
@@ -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<String>,
|
||||
pub as_whitelist: bool,
|
||||
pub template: Vec<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new(path: &Path) -> Result<Self, ConfigError> {
|
||||
let cfg_path = path.join("main.toml");
|
||||
pub fn new(cli: CliArgs) -> Result<Self, ConfigError> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
0
src/ipc/client/mod.rs
Normal file
0
src/ipc/client/mod.rs
Normal file
63
src/ipc/mod.rs
Normal file
63
src/ipc/mod.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use std::ffi::{c_char, CString};
|
||||
|
||||
pub mod client;
|
||||
pub mod server;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Message {
|
||||
pub plugin: Option<String>,
|
||||
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<S: ToString>(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<S: ToString>(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)
|
||||
}
|
||||
|
||||
}
|
||||
27
src/ipc/server/mod.rs
Normal file
27
src/ipc/server/mod.rs
Normal file
@@ -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<Self> {
|
||||
Ok(Self {
|
||||
sock: UnixStream::connect(&cfg.sock_path)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn poll(&mut self) -> Option<Message> {
|
||||
let mut buf = String::new();
|
||||
let _ = self.sock.read_to_string(&mut buf);
|
||||
if buf.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(Message::from_string(buf))
|
||||
}
|
||||
}
|
||||
14
src/logger.rs
Normal file
14
src/logger.rs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
39
src/main.rs
39
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}");
|
||||
}
|
||||
|
||||
@@ -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<P: AsRef<Path>>(&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<String, PolledText> {
|
||||
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)]
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
|
||||
@@ -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")? },
|
||||
|
||||
@@ -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, std::alloc::LayoutError> = 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<loader::PluginSyms>,
|
||||
path: PathBuf,
|
||||
lib: Option<Library>,
|
||||
enabled: bool,
|
||||
buf: *mut u8
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl Plugin {
|
||||
pub fn load(path: PathBuf) -> Result<Self, dlopen::Error> {
|
||||
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<String, std::string::FromUtf8Error> {
|
||||
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<String, std::string::FromUtf8Error> {
|
||||
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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user