aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Grois <andi@grois.info>2021-04-26 22:23:38 +0200
committerAndreas Grois <andi@grois.info>2021-04-26 22:23:38 +0200
commitb4341551960626fb892d2de785130ce14dec4754 (patch)
treedf9c357d06d8d0b38c174523c05e963e5a64e2fd
parent94a8deabd176c07fbf825627cbbbdb3b95b85b15 (diff)
Add options: List plugins, and get plugin help.
This fixes the TODO document's notes about not being able to get help for individual plugins, and about not having any means to list all loadable plugins.
-rw-r--r--clock/src/lib.rs30
-rw-r--r--swaystatus-plugin/src/lib.rs11
-rw-r--r--swaystatus/src/commandline/mod.rs54
-rw-r--r--swaystatus/src/config/tests.rs4
-rw-r--r--swaystatus/src/main.rs64
-rw-r--r--swaystatus/src/test_plugin.rs13
6 files changed, 157 insertions, 19 deletions
diff --git a/clock/src/lib.rs b/clock/src/lib.rs
index 109045b..76f5ea7 100644
--- a/clock/src/lib.rs
+++ b/clock/src/lib.rs
@@ -159,6 +159,36 @@ impl SwayStatusModule for ClockPlugin {
let config = ClockConfig::default();
Box::new(config)
}
+ fn print_help(&self) {
+ println!(
+r#"Swaystatus Clock plugin.
+
+This plugin is a simple wall clock, implemented as a thin wrapper around the chrono crate (https://github.com/chronotope/chrono). This plugin just has two options: The format of the to-be-printed time, and the update rate.
+
+The general format for a Clock configuration is:
+[Element.Config]
+Format = "<strftime format string>"
+
+[Element.Config.RefreshRate]
+Synchronization = "UtcSynchronized"
+PerThirtyMinutes = <integer>
+
+or alternatively
+[Element.Config]
+Format = "<strftime format string>"
+
+[Element.Config.RefreshRate]
+Synchronization = "NotSynchronized"
+Seconds = <float>
+
+The format is directly passed on to chrono and uses the strftime format. For available formatting options please see https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html or the strftime(3) man page.
+
+For the RefreshRate you can choose between two options. Unless you have a very special use case, you'll likely want to use the UtcSynchronized option. As the name implies this mode aims to update in sync with your computer's system clock. For instance, if you set it to update every second, the text of the clock will update within a few milliseconds after a full second of the system clock passed.
+Since it synchronizes with UTC, and time zones are in general offset by multiples of 30 minutes, 30 minutes has been chosen as maximum time between updates. Following the "make invalid states unrepresentable" paradigm, the actual update rate is set as a fraction of 30 minutes. For example, if you want to update every second, the "PerThirtyMinutes" field needs to be set to 1800. If you need updates every minute, you'll want toset "PerThirtyMinutes" to 30. Beware that setting PerThirtyMinutes above 36000 is not supported.
+
+The other option, "NotSynchronized" is for cases where 30/n minutes as an update rate is not desired. This simply waits approximately Seconds seconds between updates, but does not care about any synchronization to UTC. In other words, if you set this to 24 hours update rate, but launch the program at noon, that's when your date will update instead of midnight. Long story short, this mode only exists because it was easy to implement, and will probably never be useful to anybody."#
+);
+ }
}
impl ClockPlugin {
diff --git a/swaystatus-plugin/src/lib.rs b/swaystatus-plugin/src/lib.rs
index 1d2d22b..8a632cf 100644
--- a/swaystatus-plugin/src/lib.rs
+++ b/swaystatus-plugin/src/lib.rs
@@ -32,9 +32,13 @@
//! This is because loading plugins is by definition unsafe, and we need to make sure that nothing
//! can exist beyond the symbols from the dynamic library.
//!
+//! For implementors of this interface the same limitations apply, however afaik there is no way to
+//! enforce them. This means that you need to make sure that all additional threads you create are
+//! joined before the `run()` method of the runnable returns, otherwise your code will crash.
+//!
//! ## Note on linkage:
//! While you can't easily change how dependencies are linked into your plugin, you can choose to
-//! dynamically link against the Rust standard library. For memory reasons I'd strongly recommend
+//! dynamically link against the Rust standard library. For memory reasons I'd recommend
//! to do so. The easiest way is to set rustc compiler flags using .cargo/config in the project.
use erased_serde::serialize_trait_object;
@@ -141,6 +145,11 @@ pub trait SwayStatusModule {
///This is used for the command line option to print a default configuration.
///Let it return your config with all defaults (including optional fields).
fn get_default_config<'p>(&'p self) -> Box<dyn SwayStatusModuleInstance + 'p>;
+
+ ///This method should print a short summary of how to use and how to configure your plugin.
+ ///This is the place to explain enum variants not present in your sample config, or info on
+ ///advanced features.
+ fn print_help(&self);
}
///This is what `SwayStatusModuleInstance::make_runnable()` returns. The main function of your module.
diff --git a/swaystatus/src/commandline/mod.rs b/swaystatus/src/commandline/mod.rs
index a411768..0b00c20 100644
--- a/swaystatus/src/commandline/mod.rs
+++ b/swaystatus/src/commandline/mod.rs
@@ -4,10 +4,21 @@ use clap::*;
use std::path;
use gettextrs::*;
-pub struct CommandlineParameters {
- pub config_file : path::PathBuf,
+pub enum PluginHelpOption{
+ All,
+ List(Vec<String>)
+}
+pub enum CommandlineAction {
+ Run {
+ config_file : path::PathBuf,
+ },
+ PrintSampleConfig,
+ PluginHelp(PluginHelpOption),
+ ListPlugins
+}
+pub struct CommandlineParameters{
pub plugin_folder : path::PathBuf,
- pub print_sample_config : bool
+ pub action : CommandlineAction
}
/// Gets the config and plugin paths. Either from command line or from hardcoded defaults.
@@ -46,7 +57,24 @@ pub fn parse_commandline() -> CommandlineParameters {
.arg(
Arg::new("sampleconfig")
.long("print-sample-config")
+ .short('s')
.about(&*gettext("Prints a sample config file. Beware that the contents of the sample file depend on the loaded plugins, so don't forget to supply the plugins parameter as needed."))
+ .takes_value(false)
+ .conflicts_with_all(&["pluginhelp","pluginlist"]))
+ .arg(
+ Arg::new("pluginhelp")
+ .long("plugin-help")
+ .short('g')
+ .value_name(&*gettext("PLUGINS"))
+ .about(&*gettext("Prints plugin help messages. Either for a given list of plugins, or if no list given, for all loadable plugins."))
+ .min_values(0)
+ .setting(ArgSettings::MultipleValues)
+ .conflicts_with("pluginlist"))
+ .arg(
+ Arg::new("pluginlist")
+ .long("list-plugins")
+ .short('l')
+ .about(&*gettext("Prints a list of plugin names in the plugin folder."))
.takes_value(false))
.after_help(&*gettext!("If no config path is given, the code looks for the \"swaystatus/config\" file in your XDG config folder (typically \"$HOME/.config/\"). If that lookup fails, loading of \"/etc/swaystatus/config\" is attempted. Similarly, if no plugin folder is given, first the existence of a folder named \"$HOME/.local/lib/swaystatus\" is checked. If this folder does not exist, a default path set at compile time is used, which in your case is \"{}\"." , get_hardcoded_default_library_path()))
.help_template(&*gettext("\
@@ -61,11 +89,23 @@ OPTIONS:
{options}\n
{after-help}")).get_matches();
-
- let config_file = matches.value_of("config").map(path::PathBuf::from).unwrap_or_else(get_default_config);
let plugin_folder = matches.value_of("plugins").map(path::PathBuf::from).unwrap_or_else(get_default_plugin_directory);
- let print_sample_config = matches.is_present("sampleconfig");
- CommandlineParameters { config_file, plugin_folder , print_sample_config}
+ if matches.is_present("sampleconfig") {
+ CommandlineParameters { plugin_folder, action : CommandlineAction::PrintSampleConfig }
+ }
+ else if matches.is_present("pluginlist") {
+ CommandlineParameters {plugin_folder, action : CommandlineAction::ListPlugins }
+ }
+ else if let Some(iter) = matches.values_of("pluginhelp") {
+ CommandlineParameters {plugin_folder, action : CommandlineAction::PluginHelp(
+ if iter.len() == 0 { PluginHelpOption::All }
+ else {PluginHelpOption::List(iter.map(String::from).collect())}
+ )}
+ }
+ else {
+ let config_file = matches.value_of("config").map(path::PathBuf::from).unwrap_or_else(get_default_config);
+ CommandlineParameters {plugin_folder, action : CommandlineAction::Run { config_file }}
+ }
}
/// Searches for the config file in XDG paths. If not found there, instead the
diff --git a/swaystatus/src/config/tests.rs b/swaystatus/src/config/tests.rs
index 1d3782d..71071ea 100644
--- a/swaystatus/src/config/tests.rs
+++ b/swaystatus/src/config/tests.rs
@@ -82,7 +82,7 @@ fn custom_deserialize_optional_field_settings()
{
let p = get_plugin_database_with_test_plugin();
let test_config = String::from(
- "[[Elements]]\nPlugin = \"TestPlugin\"\n\n[Elements.Config]\nlines = 2\nskull = \"skully\"\n");
+ "[[Element]]\nPlugin = \"TestPlugin\"\n\n[Element.Config]\nlines = 2\nskull = \"skully\"\n");
let deserialized = SwaystatusConfig::deserialize(&test_config, &p).unwrap();
let serialized = toml::to_string(&deserialized).unwrap();
//println!("{}", serialized);
@@ -108,7 +108,7 @@ fn custom_deserialize_multiple_plugins()
{
let p = get_plugin_database_with_test_plugin();
let test_config = String::from(
- "[[Elements]]\nPlugin = \"TestPlugin\"\n\n[Elements.Config]\nlines = 2\nskull = \"bones\"\n\n[[Elements]]\nPlugin = \"TestPlugin\"\n\n[Elements.Config]\nlines = 5\nskull = \"pirate\"\n");
+ "[[Element]]\nPlugin = \"TestPlugin\"\n\n[Element.Config]\nlines = 2\nskull = \"bones\"\n\n[[Element]]\nPlugin = \"TestPlugin\"\n\n[Element.Config]\nlines = 5\nskull = \"pirate\"\n");
let deserialized = SwaystatusConfig::deserialize(&test_config, &p).unwrap();
let serialized = toml::to_string(&deserialized).unwrap();
//println!("{}", serialized);
diff --git a/swaystatus/src/main.rs b/swaystatus/src/main.rs
index 24d864f..c8a477f 100644
--- a/swaystatus/src/main.rs
+++ b/swaystatus/src/main.rs
@@ -10,6 +10,7 @@ use gettextrs::*;
use crossbeam_utils::thread;
use std::sync::mpsc;
+use commandline::CommandlineAction;
#[cfg(test)]
pub mod test_plugin;
@@ -25,12 +26,21 @@ fn main() {
eprintln!("Localization could not be loaded. Will use English instead.");
}
let commandline_parameters = commandline::parse_commandline();
-
- if commandline_parameters.print_sample_config {
- return print_sample_config(&commandline_parameters.plugin_folder);
+ match commandline_parameters.action {
+ CommandlineAction::PrintSampleConfig => {
+ print_sample_config(&commandline_parameters.plugin_folder);
+ }
+ CommandlineAction::ListPlugins => {
+ list_plugins(&commandline_parameters.plugin_folder);
+ }
+ CommandlineAction::PluginHelp(list) => {
+ print_plugin_help(&commandline_parameters.plugin_folder, list);
+ }
+ CommandlineAction::Run { config_file } => {
+ while !core_loop(&commandline_parameters.plugin_folder, &config_file) {}
+ }
}
- while !core_loop(&commandline_parameters.plugin_folder, &commandline_parameters.config_file) {}
}
/// Actually the main() function. Factored out so we can restart without actually restaring.
@@ -193,3 +203,49 @@ fn print_sample_config(plugin_path : &std::path::Path) {
config::SwaystatusConfig::print_sample_config(&plugins);
}
+
+fn list_plugins(plugin_path : &std::path::Path) {
+ let libraries = match plugin_database::Libraries::load_from_folder(plugin_path) {
+ Ok(x) => x,
+ Err(e) => {
+ print_plugin_load_error(e,plugin_path);
+ return;
+ }
+ };
+ let plugins = plugin_database::PluginDatabase::new(&libraries);
+ for (name, _) in plugins.get_name_and_plugin_iterator() {
+ println!("{}", name);
+ }
+}
+
+fn print_plugin_help(plugin_path : &std::path::Path, list : commandline::PluginHelpOption) {
+ let libraries = match plugin_database::Libraries::load_from_folder(plugin_path) {
+ Ok(x) => x,
+ Err(e) => {
+ print_plugin_load_error(e,plugin_path);
+ return;
+ }
+ };
+ let plugins = plugin_database::PluginDatabase::new(&libraries);
+ match list {
+ commandline::PluginHelpOption::All => {
+ for (n, p) in plugins.get_name_and_plugin_iterator() {
+ println!("{}\n",gettext!("Plugin: \"{}\"",n));
+ p.print_help();
+ println!("\n\n");
+ }
+ }
+ commandline::PluginHelpOption::List(l) => {
+ for name in l {
+ println!("{}\n",gettext!("Plugin: \"{}\"",name));
+ if let Some(p) = plugins.get_plugin(&name) {
+ p.print_help();
+ }
+ else {
+ println!("{}", gettext!("Plugin {} not found.", name));
+ }
+ println!("\n\n");
+ }
+ }
+ }
+}
diff --git a/swaystatus/src/test_plugin.rs b/swaystatus/src/test_plugin.rs
index d8f1270..4e265e2 100644
--- a/swaystatus/src/test_plugin.rs
+++ b/swaystatus/src/test_plugin.rs
@@ -13,11 +13,11 @@ pub struct TestRunnable;
pub struct DeadEndSend;
impl MsgMainToModule for DeadEndSend {
- fn send_quit(&self) -> Result<(),()> {
- Err(())
+ fn send_quit(&self) -> Result<(),PluginCommunicationError> {
+ Err(PluginCommunicationError)
}
- fn send_refresh(&self) -> Result<(),()> {
- Err(())
+ fn send_refresh(&self) -> Result<(),PluginCommunicationError> {
+ Err(PluginCommunicationError)
}
}
@@ -34,7 +34,7 @@ impl SwayStatusModuleRunnable for TestRunnable {
}
impl SwayStatusModuleInstance for TestConfig {
- fn make_runnable<'p>(&'p self, to_main : Box<dyn MsgModuleToMain + 'p>) -> (Box<dyn SwayStatusModuleRunnable + 'p>, Box<dyn MsgMainToModule + 'p>) {
+ fn make_runnable<'p>(&'p self, _to_main : Box<dyn MsgModuleToMain + 'p>) -> (Box<dyn SwayStatusModuleRunnable + 'p>, Box<dyn MsgMainToModule + 'p>) {
return (Box::new(TestRunnable), Box::new(DeadEndSend));
}
}
@@ -54,5 +54,8 @@ impl SwayStatusModule for TestPlugin {
};
return Box::new(config);
}
+ fn print_help(&self) {
+ println!("Not implemented for a test plugin. Hey, this only exists to test serializaiont!");
+ }
}