add ssh mode
This commit is contained in:
parent
00df9f4d88
commit
bb4dcb6908
5 changed files with 171 additions and 39 deletions
|
@ -6,12 +6,10 @@ Worf is written in Rust on top of GTK4.
|
||||||
It aims to be a drop in replacement for wofi in most part, so it is (almost) compatible with its
|
It aims to be a drop in replacement for wofi in most part, so it is (almost) compatible with its
|
||||||
configuration and css files. See below for differences
|
configuration and css files. See below for differences
|
||||||
|
|
||||||
## Not finished
|
## Not finished
|
||||||
* [ ] dmenu
|
|
||||||
* [ ] run
|
* [ ] run
|
||||||
* [ ] key support
|
* [ ] key support
|
||||||
* [ ] full config support
|
* [ ] full config support
|
||||||
* [ ] ssh mode
|
|
||||||
* [ ] web search mode
|
* [ ] web search mode
|
||||||
* [ ] emoji finder
|
* [ ] emoji finder
|
||||||
* [ ] publish library
|
* [ ] publish library
|
||||||
|
|
|
@ -7,6 +7,7 @@ use clap::{Parser, ValueEnum};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use which::which;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ConfigurationError {
|
pub enum ConfigurationError {
|
||||||
|
@ -76,6 +77,9 @@ pub enum Mode {
|
||||||
|
|
||||||
/// Use is as calculator
|
/// Use is as calculator
|
||||||
Math,
|
Math,
|
||||||
|
|
||||||
|
/// Connect via ssh to a given host
|
||||||
|
Ssh,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
@ -108,6 +112,7 @@ impl FromStr for Mode {
|
||||||
"dmenu" => Ok(Mode::Dmenu),
|
"dmenu" => Ok(Mode::Dmenu),
|
||||||
"file" => Ok(Mode::File),
|
"file" => Ok(Mode::File),
|
||||||
"math" => Ok(Mode::Math),
|
"math" => Ok(Mode::Math),
|
||||||
|
"ssh" => Ok(Mode::Ssh),
|
||||||
"auto" => Ok(Mode::Auto),
|
"auto" => Ok(Mode::Auto),
|
||||||
_ => Err(ArgsError::InvalidParameter(
|
_ => Err(ArgsError::InvalidParameter(
|
||||||
format!("{s} is not a valid argument, see help for details").to_owned(),
|
format!("{s} is not a valid argument, see help for details").to_owned(),
|
||||||
|
@ -192,6 +197,19 @@ pub struct Config {
|
||||||
#[clap(short = 'k', long = "cache-file")]
|
#[clap(short = 'k', long = "cache-file")]
|
||||||
pub cache_file: Option<String>,
|
pub cache_file: Option<String>,
|
||||||
|
|
||||||
|
/// Defines which terminal to use. defaults to the first one found:
|
||||||
|
/// * kitty
|
||||||
|
/// * gnome-terminal
|
||||||
|
/// * konsole
|
||||||
|
/// * xfce4-terminal
|
||||||
|
/// * lxterminal
|
||||||
|
/// * xterm
|
||||||
|
/// * alacritty
|
||||||
|
/// * terminator
|
||||||
|
///
|
||||||
|
/// Must be configured including the needed arguments to launch something
|
||||||
|
/// i.e. 'kitty -c'
|
||||||
|
#[serde(default = "default_terminal")]
|
||||||
#[clap(short = 't', long = "term")]
|
#[clap(short = 't', long = "term")]
|
||||||
pub term: Option<String>,
|
pub term: Option<String>,
|
||||||
|
|
||||||
|
@ -570,6 +588,29 @@ pub fn default_width() -> Option<String> {
|
||||||
Some("50%".to_owned())
|
Some("50%".to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// allowed because option is needed for serde macro
|
||||||
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn default_terminal() -> Option<String> {
|
||||||
|
let terminals = [
|
||||||
|
("gnome-terminal", vec!["--"]),
|
||||||
|
("konsole", vec!["-e"]),
|
||||||
|
("xfce4-terminal", vec!["--command"]),
|
||||||
|
("xterm", vec!["-e"]),
|
||||||
|
("alacritty", vec!["-e"]),
|
||||||
|
("lxterminal", vec!["-e"]),
|
||||||
|
("kitty", vec!["-e"]),
|
||||||
|
("tilix", vec!["-e"]),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (term, launch) in &terminals {
|
||||||
|
if which(term).is_ok() {
|
||||||
|
return Some(format!("{term} {}", launch.join(" ")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
// allowed because option is needed for serde macro
|
// allowed because option is needed for serde macro
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -692,6 +733,7 @@ pub fn load_config(args_opt: Option<Config>) -> Result<Config, ConfigurationErro
|
||||||
Mode::Math => merge_result.prompt = Some("math".to_owned()),
|
Mode::Math => merge_result.prompt = Some("math".to_owned()),
|
||||||
Mode::File => merge_result.prompt = Some("file".to_owned()),
|
Mode::File => merge_result.prompt = Some("file".to_owned()),
|
||||||
Mode::Auto => merge_result.prompt = Some("auto".to_owned()),
|
Mode::Auto => merge_result.prompt = Some("auto".to_owned()),
|
||||||
|
Mode::Ssh => merge_result.prompt = Some("ssh".to_owned()),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,10 @@ use gdk4::glib::{Propagation, timeout_add_local};
|
||||||
use gdk4::prelude::{Cast, DisplayExt, MonitorExt};
|
use gdk4::prelude::{Cast, DisplayExt, MonitorExt};
|
||||||
use gdk4::{Display, Key};
|
use gdk4::{Display, Key};
|
||||||
use gtk4::glib::ControlFlow;
|
use gtk4::glib::ControlFlow;
|
||||||
use gtk4::prelude::{ApplicationExt, ApplicationExtManual, BoxExt, EditableExt, FlowBoxChildExt, GestureSingleExt, GtkWindowExt, ListBoxRowExt, NativeExt, OrientableExt, WidgetExt};
|
use gtk4::prelude::{
|
||||||
|
ApplicationExt, ApplicationExtManual, BoxExt, EditableExt, FlowBoxChildExt, GestureSingleExt,
|
||||||
|
GtkWindowExt, ListBoxRowExt, NativeExt, OrientableExt, WidgetExt,
|
||||||
|
};
|
||||||
use gtk4::{
|
use gtk4::{
|
||||||
Align, EventControllerKey, Expander, FlowBox, FlowBoxChild, GestureClick, Image, Label,
|
Align, EventControllerKey, Expander, FlowBox, FlowBoxChild, GestureClick, Image, Label,
|
||||||
ListBox, ListBoxRow, NaturalWrapMode, Ordering, PolicyType, ScrolledWindow, SearchEntry,
|
ListBox, ListBoxRow, NaturalWrapMode, Ordering, PolicyType, ScrolledWindow, SearchEntry,
|
||||||
|
@ -243,7 +246,7 @@ fn build_ui<T, P>(
|
||||||
.set_keyboard_mode(KeyboardMode::Exclusive);
|
.set_keyboard_mode(KeyboardMode::Exclusive);
|
||||||
ui_elements.window.set_namespace(Some("worf"));
|
ui_elements.window.set_namespace(Some("worf"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let window_done = Instant::now();
|
let window_done = Instant::now();
|
||||||
|
|
||||||
if let Some(location) = config.location.as_ref() {
|
if let Some(location) = config.location.as_ref() {
|
||||||
|
|
154
src/lib/mode.rs
154
src/lib/mode.rs
|
@ -316,6 +316,56 @@ impl<T: Clone> ItemProvider<T> for FileItemProvider<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct SshProvider<T: Clone> {
|
||||||
|
elements: Vec<MenuItem<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> SshProvider<T> {
|
||||||
|
fn new(menu_item_data: T, config: &Config) -> Self {
|
||||||
|
let re = Regex::new(r"(?m)^\s*Host\s+(.+)$").unwrap();
|
||||||
|
let items: Vec<_> = dirs::home_dir()
|
||||||
|
.map(|home| home.join(".ssh").join("config"))
|
||||||
|
.filter(|path| path.exists())
|
||||||
|
.map(|path| fs::read_to_string(&path).unwrap_or_default())
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|content| {
|
||||||
|
re.captures_iter(&content)
|
||||||
|
.flat_map(|cap| {
|
||||||
|
cap[1]
|
||||||
|
.split_whitespace()
|
||||||
|
.map(|host| {
|
||||||
|
log::debug!("found ssh host {host}");
|
||||||
|
MenuItem::new(
|
||||||
|
host.to_owned(),
|
||||||
|
Some("computer".to_owned()),
|
||||||
|
config.term.clone().map(|cmd| format!("{cmd} ssh {host}")),
|
||||||
|
vec![],
|
||||||
|
None,
|
||||||
|
0.0,
|
||||||
|
Some(menu_item_data.clone()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Self { elements: items }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> ItemProvider<T> for SshProvider<T> {
|
||||||
|
fn get_elements(&mut self, _: Option<&str>) -> Vec<MenuItem<T>> {
|
||||||
|
self.elements.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_sub_elements(&mut self, _: &MenuItem<T>) -> Option<Vec<MenuItem<T>>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct MathProvider<T: Clone> {
|
struct MathProvider<T: Clone> {
|
||||||
menu_item_data: T,
|
menu_item_data: T,
|
||||||
|
@ -415,7 +465,7 @@ enum AutoRunType {
|
||||||
Math,
|
Math,
|
||||||
DRun,
|
DRun,
|
||||||
File,
|
File,
|
||||||
// Ssh,
|
Ssh,
|
||||||
// WebSearch,
|
// WebSearch,
|
||||||
// Emoji,
|
// Emoji,
|
||||||
// Run,
|
// Run,
|
||||||
|
@ -426,14 +476,16 @@ struct AutoItemProvider {
|
||||||
drun: DRunProvider<AutoRunType>,
|
drun: DRunProvider<AutoRunType>,
|
||||||
file: FileItemProvider<AutoRunType>,
|
file: FileItemProvider<AutoRunType>,
|
||||||
math: MathProvider<AutoRunType>,
|
math: MathProvider<AutoRunType>,
|
||||||
|
ssh: SshProvider<AutoRunType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AutoItemProvider {
|
impl AutoItemProvider {
|
||||||
fn new() -> Self {
|
fn new(config: &Config) -> Self {
|
||||||
AutoItemProvider {
|
AutoItemProvider {
|
||||||
drun: DRunProvider::new(AutoRunType::DRun),
|
drun: DRunProvider::new(AutoRunType::DRun),
|
||||||
file: FileItemProvider::new(AutoRunType::File),
|
file: FileItemProvider::new(AutoRunType::File),
|
||||||
math: MathProvider::new(AutoRunType::Math),
|
math: MathProvider::new(AutoRunType::Math),
|
||||||
|
ssh: SshProvider::new(AutoRunType::Ssh, config),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -453,6 +505,8 @@ impl ItemProvider<AutoRunType> for AutoItemProvider {
|
||||||
|| trimmed_search.starts_with('~')
|
|| trimmed_search.starts_with('~')
|
||||||
{
|
{
|
||||||
self.file.get_elements(search_opt)
|
self.file.get_elements(search_opt)
|
||||||
|
} else if trimmed_search.starts_with("ssh") {
|
||||||
|
self.ssh.get_elements(search_opt)
|
||||||
} else {
|
} else {
|
||||||
self.drun.get_elements(search_opt)
|
self.drun.get_elements(search_opt)
|
||||||
}
|
}
|
||||||
|
@ -494,40 +548,44 @@ pub fn d_run(config: &Config) -> Result<(), ModeError> {
|
||||||
/// Will return `Err`
|
/// Will return `Err`
|
||||||
/// * if it was not able to spawn the process
|
/// * if it was not able to spawn the process
|
||||||
pub fn auto(config: &Config) -> Result<(), ModeError> {
|
pub fn auto(config: &Config) -> Result<(), ModeError> {
|
||||||
let mut provider = AutoItemProvider::new();
|
let mut provider = AutoItemProvider::new(config);
|
||||||
let cache_path = provider.drun.cache_path.clone();
|
let cache_path = provider.drun.cache_path.clone();
|
||||||
let mut cache = provider.drun.cache.clone();
|
let mut cache = provider.drun.cache.clone();
|
||||||
let mut cfg_clone = config.clone();
|
let mut cfg_clone = config.clone();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// todo ues a arc instead of cloning the config
|
// todo ues a arc instead of cloning the config
|
||||||
let selection_result = gui::show(cfg_clone.clone(), provider.clone(), false);
|
let selection_result = gui::show(cfg_clone.clone(), provider.clone(), true);
|
||||||
|
|
||||||
match selection_result {
|
if let Ok(mut selection_result) = selection_result {
|
||||||
Ok(selection_result) => {
|
if let Some(data) = &selection_result.data {
|
||||||
if let Some(data) = &selection_result.data {
|
match data {
|
||||||
match data {
|
AutoRunType::Math => {
|
||||||
AutoRunType::Math => {
|
cfg_clone.prompt = Some(selection_result.label.clone());
|
||||||
cfg_clone.prompt = Some(selection_result.label.clone());
|
provider.math.elements.push(selection_result);
|
||||||
provider.math.elements.push(selection_result);
|
}
|
||||||
}
|
AutoRunType::DRun => {
|
||||||
AutoRunType::DRun => {
|
update_drun_cache_and_run(cache_path, &mut cache, selection_result)?;
|
||||||
update_drun_cache_and_run(cache_path, &mut cache, selection_result)?;
|
break;
|
||||||
break;
|
}
|
||||||
}
|
AutoRunType::File => {
|
||||||
AutoRunType::File => {
|
if let Some(action) = selection_result.action {
|
||||||
if let Some(action) = selection_result.action {
|
spawn_fork(&action, selection_result.working_dir.as_ref())?;
|
||||||
spawn_fork(&action, selection_result.working_dir.as_ref())?;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
AutoRunType::Ssh => {
|
||||||
|
ssh_launch(&selection_result, config)?;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if selection_result.label.starts_with("ssh") {
|
||||||
|
selection_result.label = selection_result.label.chars().skip(4).collect();
|
||||||
|
ssh_launch(&selection_result, config)?;
|
||||||
}
|
}
|
||||||
Err(_) => {
|
} else {
|
||||||
log::error!("No item selected");
|
log::error!("No item selected");
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -557,6 +615,37 @@ pub fn file(config: &Config) -> Result<(), ModeError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ssh_launch<T: Clone>(menu_item: &MenuItem<T>, config: &Config) -> Result<(), ModeError> {
|
||||||
|
if let Some(action) = &menu_item.action {
|
||||||
|
spawn_fork(action, None)?;
|
||||||
|
} else {
|
||||||
|
let cmd = config
|
||||||
|
.term
|
||||||
|
.clone()
|
||||||
|
.map(|s| format!("{s} ssh {}", menu_item.label));
|
||||||
|
if let Some(cmd) = cmd {
|
||||||
|
spawn_fork(&cmd, None)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ModeError::MissingAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return `Err`
|
||||||
|
/// * if it was not able to spawn the process
|
||||||
|
/// * if it didn't find a terminal
|
||||||
|
pub fn ssh(config: &Config) -> Result<(), ModeError> {
|
||||||
|
let provider = SshProvider::new(String::new(), config);
|
||||||
|
let selection_result = gui::show(config.clone(), provider, true);
|
||||||
|
if let Ok(mi) = selection_result {
|
||||||
|
ssh_launch(&mi, config)?;
|
||||||
|
} else {
|
||||||
|
log::error!("No item selected");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn math(config: &Config) {
|
pub fn math(config: &Config) {
|
||||||
let mut cfg_clone = config.clone();
|
let mut cfg_clone = config.clone();
|
||||||
let mut calc: Vec<MenuItem<String>> = vec![];
|
let mut calc: Vec<MenuItem<String>> = vec![];
|
||||||
|
@ -564,15 +653,12 @@ pub fn math(config: &Config) {
|
||||||
let mut provider = MathProvider::new(String::new());
|
let mut provider = MathProvider::new(String::new());
|
||||||
provider.add_elements(&mut calc.clone());
|
provider.add_elements(&mut calc.clone());
|
||||||
let selection_result = gui::show(cfg_clone.clone(), provider, true);
|
let selection_result = gui::show(cfg_clone.clone(), provider, true);
|
||||||
match selection_result {
|
if let Ok(mi) = selection_result {
|
||||||
Ok(mi) => {
|
cfg_clone.prompt = Some(mi.label.clone());
|
||||||
cfg_clone.prompt = Some(mi.label.clone());
|
calc.push(mi);
|
||||||
calc.push(mi);
|
} else {
|
||||||
}
|
log::error!("No item selected");
|
||||||
Err(_) => {
|
break;
|
||||||
log::error!("No item selected");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,9 @@ fn main() -> anyhow::Result<()> {
|
||||||
Mode::Math => {
|
Mode::Math => {
|
||||||
mode::math(&config);
|
mode::math(&config);
|
||||||
}
|
}
|
||||||
|
Mode::Ssh => {
|
||||||
|
mode::ssh(&config).map_err(|e| anyhow!(e))?;
|
||||||
|
}
|
||||||
Mode::Auto => {
|
Mode::Auto => {
|
||||||
mode::auto(&config).map_err(|e| anyhow!(e))?;
|
mode::auto(&config).map_err(|e| anyhow!(e))?;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue