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
|
||||
configuration and css files. See below for differences
|
||||
|
||||
## Not finished
|
||||
* [ ] dmenu
|
||||
## Not finished
|
||||
* [ ] run
|
||||
* [ ] key support
|
||||
* [ ] full config support
|
||||
* [ ] ssh mode
|
||||
* [ ] web search mode
|
||||
* [ ] emoji finder
|
||||
* [ ] publish library
|
||||
|
|
|
@ -7,6 +7,7 @@ use clap::{Parser, ValueEnum};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use thiserror::Error;
|
||||
use which::which;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ConfigurationError {
|
||||
|
@ -76,6 +77,9 @@ pub enum Mode {
|
|||
|
||||
/// Use is as calculator
|
||||
Math,
|
||||
|
||||
/// Connect via ssh to a given host
|
||||
Ssh,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
@ -108,6 +112,7 @@ impl FromStr for Mode {
|
|||
"dmenu" => Ok(Mode::Dmenu),
|
||||
"file" => Ok(Mode::File),
|
||||
"math" => Ok(Mode::Math),
|
||||
"ssh" => Ok(Mode::Ssh),
|
||||
"auto" => Ok(Mode::Auto),
|
||||
_ => Err(ArgsError::InvalidParameter(
|
||||
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")]
|
||||
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")]
|
||||
pub term: Option<String>,
|
||||
|
||||
|
@ -570,6 +588,29 @@ pub fn default_width() -> Option<String> {
|
|||
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
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
#[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::File => merge_result.prompt = Some("file".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::{Display, Key};
|
||||
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::{
|
||||
Align, EventControllerKey, Expander, FlowBox, FlowBoxChild, GestureClick, Image, Label,
|
||||
ListBox, ListBoxRow, NaturalWrapMode, Ordering, PolicyType, ScrolledWindow, SearchEntry,
|
||||
|
@ -243,7 +246,7 @@ fn build_ui<T, P>(
|
|||
.set_keyboard_mode(KeyboardMode::Exclusive);
|
||||
ui_elements.window.set_namespace(Some("worf"));
|
||||
}
|
||||
|
||||
|
||||
let window_done = Instant::now();
|
||||
|
||||
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)]
|
||||
struct MathProvider<T: Clone> {
|
||||
menu_item_data: T,
|
||||
|
@ -415,7 +465,7 @@ enum AutoRunType {
|
|||
Math,
|
||||
DRun,
|
||||
File,
|
||||
// Ssh,
|
||||
Ssh,
|
||||
// WebSearch,
|
||||
// Emoji,
|
||||
// Run,
|
||||
|
@ -426,14 +476,16 @@ struct AutoItemProvider {
|
|||
drun: DRunProvider<AutoRunType>,
|
||||
file: FileItemProvider<AutoRunType>,
|
||||
math: MathProvider<AutoRunType>,
|
||||
ssh: SshProvider<AutoRunType>,
|
||||
}
|
||||
|
||||
impl AutoItemProvider {
|
||||
fn new() -> Self {
|
||||
fn new(config: &Config) -> Self {
|
||||
AutoItemProvider {
|
||||
drun: DRunProvider::new(AutoRunType::DRun),
|
||||
file: FileItemProvider::new(AutoRunType::File),
|
||||
math: MathProvider::new(AutoRunType::Math),
|
||||
ssh: SshProvider::new(AutoRunType::Ssh, config),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -453,6 +505,8 @@ impl ItemProvider<AutoRunType> for AutoItemProvider {
|
|||
|| trimmed_search.starts_with('~')
|
||||
{
|
||||
self.file.get_elements(search_opt)
|
||||
} else if trimmed_search.starts_with("ssh") {
|
||||
self.ssh.get_elements(search_opt)
|
||||
} else {
|
||||
self.drun.get_elements(search_opt)
|
||||
}
|
||||
|
@ -494,40 +548,44 @@ pub fn d_run(config: &Config) -> Result<(), ModeError> {
|
|||
/// Will return `Err`
|
||||
/// * if it was not able to spawn the process
|
||||
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 mut cache = provider.drun.cache.clone();
|
||||
let mut cfg_clone = config.clone();
|
||||
|
||||
loop {
|
||||
// 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 {
|
||||
Ok(selection_result) => {
|
||||
if let Some(data) = &selection_result.data {
|
||||
match data {
|
||||
AutoRunType::Math => {
|
||||
cfg_clone.prompt = Some(selection_result.label.clone());
|
||||
provider.math.elements.push(selection_result);
|
||||
}
|
||||
AutoRunType::DRun => {
|
||||
update_drun_cache_and_run(cache_path, &mut cache, selection_result)?;
|
||||
break;
|
||||
}
|
||||
AutoRunType::File => {
|
||||
if let Some(action) = selection_result.action {
|
||||
spawn_fork(&action, selection_result.working_dir.as_ref())?;
|
||||
}
|
||||
break;
|
||||
if let Ok(mut selection_result) = selection_result {
|
||||
if let Some(data) = &selection_result.data {
|
||||
match data {
|
||||
AutoRunType::Math => {
|
||||
cfg_clone.prompt = Some(selection_result.label.clone());
|
||||
provider.math.elements.push(selection_result);
|
||||
}
|
||||
AutoRunType::DRun => {
|
||||
update_drun_cache_and_run(cache_path, &mut cache, selection_result)?;
|
||||
break;
|
||||
}
|
||||
AutoRunType::File => {
|
||||
if let Some(action) = selection_result.action {
|
||||
spawn_fork(&action, selection_result.working_dir.as_ref())?;
|
||||
}
|
||||
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(_) => {
|
||||
log::error!("No item selected");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
log::error!("No item selected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -557,6 +615,37 @@ pub fn file(config: &Config) -> Result<(), ModeError> {
|
|||
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) {
|
||||
let mut cfg_clone = config.clone();
|
||||
let mut calc: Vec<MenuItem<String>> = vec![];
|
||||
|
@ -564,15 +653,12 @@ pub fn math(config: &Config) {
|
|||
let mut provider = MathProvider::new(String::new());
|
||||
provider.add_elements(&mut calc.clone());
|
||||
let selection_result = gui::show(cfg_clone.clone(), provider, true);
|
||||
match selection_result {
|
||||
Ok(mi) => {
|
||||
cfg_clone.prompt = Some(mi.label.clone());
|
||||
calc.push(mi);
|
||||
}
|
||||
Err(_) => {
|
||||
log::error!("No item selected");
|
||||
break;
|
||||
}
|
||||
if let Ok(mi) = selection_result {
|
||||
cfg_clone.prompt = Some(mi.label.clone());
|
||||
calc.push(mi);
|
||||
} else {
|
||||
log::error!("No item selected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,9 @@ fn main() -> anyhow::Result<()> {
|
|||
Mode::Math => {
|
||||
mode::math(&config);
|
||||
}
|
||||
Mode::Ssh => {
|
||||
mode::ssh(&config).map_err(|e| anyhow!(e))?;
|
||||
}
|
||||
Mode::Auto => {
|
||||
mode::auto(&config).map_err(|e| anyhow!(e))?;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue