diff --git a/README.md b/README.md index 1da8bc2..417399a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ Swayr consists of a demon, and a client. The demon `swayrd` records window/workspace creations, deletions, and focus changes using sway's JSON IPC -interface. The client `swayr` offers subcommands, see `swayr --help`. +interface. The client `swayr` offers subcommands, see `swayr --help`, and +sends them to the demon which executes them. Right now, there are these subcommands: * `next-window` focuses the next window in depth-first iteration order of the @@ -82,16 +83,72 @@ and logging are optional. ## Configuration Swayr can be configured using the `~/.config/swayr/config.toml` config file. + If it doesn't exist, a simple default configuration will be created on the first invocation for use with the [wofi](https://todo.sr.ht/~scoopta/wofi) -launcher. It should be easy to adapt that default config for usage with other -launchers such as [dmenu](https://tools.suckless.org/dmenu/), +launcher. + +It should be easy to adapt that default config for usage with other launchers +such as [dmenu](https://tools.suckless.org/dmenu/), [bemenu](https://github.com/Cloudef/bemenu), [rofi](https://github.com/davatorium/rofi), a script spawning a terminal with [fzf](https://github.com/junegunn/fzf), or whatever. The only requirement is -that the launcher needs to be able to read the items to choose from from stdin. +that the launcher needs to be able to read the items to choose from from stdin, +and spit out the selected item to stdout. + +The default config looks like this: + +```toml +[launcher] +executable = 'wofi' +args = [ + '--show=dmenu', + '--allow-markup', + '--allow-images', + '--insensitive', + '--cache-file=/dev/null', + '--parse-search', + '--prompt={prompt}', +] + +[format] +window_format = '{urgency_start}“{title}”{urgency_end} — {app_name} on workspace {workspace_name} ({id})' +workspace_format = 'Workspace {name} ({id})' +urgency_start = '' +urgency_end = '' +``` -TODO: Show default config and describe it. +In the `[launcher]` section, you can specify the launchen/menu program using +the `executable` name or full path, and the `args` (flags and options) it +should get passed. If some argument contains the placeholder `{prompt}`, it is +replaced with a prompt such as "Switch to window" depending on context. + +In the `[format]` section, format strings are specified defining how selection +choises are to be layed out. `wofi` supports [pango +markup](https://docs.gtk.org/Pango/pango_markup.html) which makes it possible +to style the text using HTML and CSS. The following formats are supported +right now. +* `window_format` defines how windows are displayed. The placeholder `{title}` + is replaced with the window's title, `{app_name}` with the application name, + `{workspace_name}` with the name or number of the workspace the window is + shown, and `{id}` is the window's sway-internal con id. There are also the + placeholders `{urcency_start}` and `{urgency_end}` which get replaced by the + empty string if the window has no urgency flag, and with the values of the + same-named formats if the window has the urgency flag set. That makes it + possible to highlight urgent windows as shown in the default config. +* `workspace_format` defines how workspaces are displayed. There are the + placeholders `{name}` which gets replaced by the workspace's number or name, + and `{id}` which gets replaced by the sway-internal con id of the workspace. +* `urgency_start` is a string which replaces the `{urgency_start}` placeholder + in `window_format`. +* `urgency_end` is a string which replaces the `{urgency_end}` placeholder in + `window_format`. + +It is crucial that during selection (using wofi or some other launcher) each +window has a different display string. Therefore, it is highly recommended to +include the `{id}` placeholder at least in `window_format`. Otherwise, e.g., +two terminals (of the same terminal app) with the same working directory (and +therefore, the same title) wouldn't be distinguishable. ## Questions & Patches diff --git a/src/con.rs b/src/con.rs index 46d733e..794e82e 100644 --- a/src/con.rs +++ b/src/con.rs @@ -122,46 +122,22 @@ impl<'a> fmt::Display for Window<'a> { impl<'a> DisplayFormat for Window<'a> { fn format_for_display(&self, cfg: &cfg::Config) -> String { - let default = cfg::Config::default(); + let default_format = cfg::Format::default(); let fmt = cfg .format .as_ref() .and_then(|f| f.window_format.as_ref()) - .unwrap_or_else(|| { - default - .format - .as_ref() - .unwrap() - .window_format - .as_ref() - .unwrap() - }); + .unwrap_or_else(|| default_format.window_format.as_ref().unwrap()); let urgency_start = cfg .format .as_ref() .and_then(|f| f.urgency_start.as_ref()) - .unwrap_or_else(|| { - default - .format - .as_ref() - .unwrap() - .urgency_start - .as_ref() - .unwrap() - }); + .unwrap_or_else(|| default_format.urgency_start.as_ref().unwrap()); let urgency_end = cfg .format .as_ref() .and_then(|f| f.urgency_end.as_ref()) - .unwrap_or_else(|| { - default - .format - .as_ref() - .unwrap() - .urgency_end - .as_ref() - .unwrap() - }); + .unwrap_or_else(|| default_format.urgency_end.as_ref().unwrap()); fmt.replace("{id}", format!("{}", self.get_id()).as_str()) .replace( @@ -391,19 +367,13 @@ impl<'a> fmt::Display for Workspace<'a> { impl<'a> DisplayFormat for Workspace<'a> { fn format_for_display(&self, cfg: &cfg::Config) -> String { - let default = cfg::Config::default(); + let default_format = cfg::Format::default(); let fmt = cfg .format .as_ref() .and_then(|f| f.workspace_format.as_ref()) .unwrap_or_else(|| { - default - .format - .as_ref() - .unwrap() - .workspace_format - .as_ref() - .unwrap() + default_format.workspace_format.as_ref().unwrap() }); fmt.replace("{id}", format!("{}", self.get_id()).as_str()) diff --git a/src/config.rs b/src/config.rs index f635a06..3e848ab 100644 --- a/src/config.rs +++ b/src/config.rs @@ -28,30 +28,51 @@ pub struct Config { pub format: Option, } +impl Default for Launcher { + fn default() -> Self { + Launcher { + executable: Some("wofi".to_string()), + args: Some(vec![ + "--show=dmenu".to_string(), + "--allow-markup".to_string(), + "--allow-images".to_string(), + "--insensitive".to_string(), + "--cache-file=/dev/null".to_string(), + "--parse-search".to_string(), + "--prompt={prompt}".to_string(), + ]), + } + } +} + +impl Default for Format { + fn default() -> Self { + Format { + window_format: Some( + "{urgency_start}“{title}”{urgency_end} \ + — {app_name} on workspace {workspace_name} \ + ({id})" + .to_string(), + ), + workspace_format: Some( + "Workspace {name} \ + ({id})" + .to_string(), + ), + urgency_start: Some( + "" + .to_string(), + ), + urgency_end: Some("".to_string()), + } + } +} + impl Default for Config { fn default() -> Self { Config { - launcher: Some(Launcher { - executable: Some("wofi".to_string()), - args: Some(vec![ - "--show=dmenu".to_string(), - "--allow-markup".to_string(), - "--allow-images".to_string(), - "--insensitive".to_string(), - "--cache-file=/dev/null".to_string(), - "--parse-search".to_string(), - "--prompt={prompt}".to_string(), - ]), - }), - format: Some(Format { - window_format: Some( - "{urgency_start}“{title}”{urgency_end} — {app_name} on workspace {workspace_name} ({id})" - .to_string(), - ), - workspace_format: Some("Workspace {name} ({id})".to_string()), - urgency_start: Some("".to_string()), - urgency_end: Some("".to_string()) - }), + launcher: Some(Launcher::default()), + format: Some(Format::default()), } } } diff --git a/src/util.rs b/src/util.rs index 50d9634..5094f72 100644 --- a/src/util.rs +++ b/src/util.rs @@ -52,27 +52,17 @@ where map.insert(s, c); } - let default = cfg::Config::default(); + let launcher_default = cfg::Launcher::default(); let launcher_exec = cfg .launcher .as_ref() .and_then(|l| l.executable.as_ref()) - .unwrap_or_else(|| { - default - .launcher - .as_ref() - .unwrap() - .executable - .as_ref() - .unwrap() - }); + .unwrap_or_else(|| launcher_default.executable.as_ref().unwrap()); let args: Vec = cfg .launcher .as_ref() .and_then(|l| l.args.as_ref()) - .unwrap_or_else(|| { - default.launcher.as_ref().unwrap().args.as_ref().unwrap() - }) + .unwrap_or_else(|| launcher_default.args.as_ref().unwrap()) .iter() .map(|a| a.replace("{prompt}", prompt)) .collect();