worf-warden support elements with multiple entires
This commit is contained in:
parent
344433e697
commit
a3d4fc8551
5 changed files with 212 additions and 122 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1917,6 +1917,7 @@ name = "worf-warden"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"env_logger",
|
||||
"worf",
|
||||
]
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
#!/bin/bash
|
||||
|
||||
# A list of options, one per line
|
||||
options="Option 1
|
||||
Option 2
|
||||
Option 3"
|
||||
options=""
|
||||
for i in $(seq 1 2000); do
|
||||
options+="Option $i"$'\n'
|
||||
done
|
||||
|
||||
# Pipe options to wofi and capture the selection
|
||||
selection=$(echo "$options" | cargo run -- --show dmenu)
|
||||
selection=$(echo "$options" | cargo run --bin worf -- --show dmenu --sort-order default)
|
||||
#selection=$(echo "$options" | wofi --show dmenu)
|
||||
|
||||
# Do something with the selection
|
||||
|
|
|
@ -6,6 +6,7 @@ edition = "2024"
|
|||
[dependencies]
|
||||
worf = {path = "../../worf"}
|
||||
anyhow = "1.0.98"
|
||||
env_logger = "0.11.8"
|
||||
|
||||
# todo re-add this
|
||||
#[features]
|
||||
|
|
|
@ -1,53 +1,102 @@
|
|||
use anyhow::anyhow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env;
|
||||
use std::process::Command;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
use worf_lib::config::Config;
|
||||
use worf_lib::desktop::spawn_fork;
|
||||
use worf_lib::gui::{ItemProvider, Key, KeyBinding, MenuItem, Modifier};
|
||||
use worf_lib::{config, gui};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct MenuItemMetaData {
|
||||
ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct PasswordProvider {
|
||||
items: Vec<MenuItem<String>>,
|
||||
items: Vec<MenuItem<MenuItemMetaData>>,
|
||||
}
|
||||
|
||||
fn split_at_tab(input: &str) -> Option<(&str, &str)> {
|
||||
let mut parts = input.splitn(2, '\t');
|
||||
Some((parts.next()?, parts.next()?))
|
||||
}
|
||||
|
||||
impl PasswordProvider {
|
||||
fn new(config: &Config) -> Self {
|
||||
let output = Command::new("rbw")
|
||||
.arg("list")
|
||||
.arg("--fields")
|
||||
.arg("id,name")
|
||||
.output()
|
||||
.expect("Failed to execute command");
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
// todo the own solution should support images.
|
||||
let mut items: Vec<_> = stdout
|
||||
let mut items = stdout
|
||||
.lines()
|
||||
.map(|line| {
|
||||
.filter_map(|s| split_at_tab(s))
|
||||
.fold(
|
||||
HashMap::new(),
|
||||
|mut acc: HashMap<String, Vec<String>>, (id, name)| {
|
||||
acc.entry(name.to_owned()).or_default().push(id.to_owned());
|
||||
acc
|
||||
},
|
||||
)
|
||||
.iter()
|
||||
.map(|(key, value)| {
|
||||
MenuItem::new(
|
||||
line.to_owned(),
|
||||
key.clone(),
|
||||
None,
|
||||
None,
|
||||
vec![],
|
||||
None,
|
||||
0.0,
|
||||
Some(String::new()),
|
||||
Some(MenuItemMetaData {
|
||||
ids: value.clone(),
|
||||
}),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
gui::apply_sort(&mut items, &config.sort_order());
|
||||
|
||||
Self { items }
|
||||
}
|
||||
|
||||
fn sub_provider(ids: Vec<String>) -> Self {
|
||||
Self {
|
||||
items: ids
|
||||
.iter()
|
||||
.map(|id| {
|
||||
MenuItem::new(
|
||||
rbw_get_user(id),
|
||||
None,
|
||||
None,
|
||||
vec![],
|
||||
None,
|
||||
0.0,
|
||||
Some(MenuItemMetaData {
|
||||
ids: vec![id.clone()],
|
||||
}),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemProvider<String> for PasswordProvider {
|
||||
fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec<MenuItem<String>>) {
|
||||
impl ItemProvider<MenuItemMetaData> for PasswordProvider {
|
||||
fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec<MenuItem<MenuItemMetaData>>) {
|
||||
(false, self.items.clone())
|
||||
}
|
||||
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<String>) -> (bool, Option<Vec<MenuItem<String>>>) {
|
||||
fn get_sub_elements(
|
||||
&mut self,
|
||||
_: &MenuItem<MenuItemMetaData>,
|
||||
) -> (bool, Option<Vec<MenuItem<MenuItemMetaData>>>) {
|
||||
(false, None)
|
||||
}
|
||||
}
|
||||
|
@ -80,10 +129,10 @@ fn keyboard_tab() {
|
|||
.expect("Failed to execute ydotool");
|
||||
}
|
||||
|
||||
fn rbw_get(name: &str, field: &str) -> String {
|
||||
fn rbw_get(id: &str, field: &str) -> String {
|
||||
let output = Command::new("rbw")
|
||||
.arg("get")
|
||||
.arg(name)
|
||||
.arg(id)
|
||||
.arg("--field")
|
||||
.arg(field)
|
||||
.output()
|
||||
|
@ -94,19 +143,153 @@ fn rbw_get(name: &str, field: &str) -> String {
|
|||
.to_string()
|
||||
}
|
||||
|
||||
fn rbw_get_user(name: &str) -> String {
|
||||
rbw_get(name, "user")
|
||||
fn rbw_get_user(id: &str) -> String {
|
||||
rbw_get(id, "user")
|
||||
}
|
||||
|
||||
fn rbw_get_password(name: &str) -> String {
|
||||
rbw_get(name, "password")
|
||||
fn rbw_get_password(id: &str) -> String {
|
||||
rbw_get(id, "password")
|
||||
}
|
||||
|
||||
fn rbw_get_totp(name: &str) -> String {
|
||||
rbw_get(name, "totp")
|
||||
fn rbw_get_totp(id: &str) -> String {
|
||||
rbw_get(id, "totp")
|
||||
}
|
||||
|
||||
fn key_type_all() -> KeyBinding {
|
||||
KeyBinding {
|
||||
key: Key::Num1,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+1</b> Type User".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn key_type_user() -> KeyBinding {
|
||||
KeyBinding {
|
||||
key: Key::Num2,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+2</b> Type User".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn key_type_password() -> KeyBinding {
|
||||
KeyBinding {
|
||||
key: Key::Num3,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+3</b> Type Password".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn key_type_totp() -> KeyBinding {
|
||||
KeyBinding {
|
||||
key: Key::Num4,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+3</b> Type Totp".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn key_reload() -> KeyBinding {
|
||||
KeyBinding {
|
||||
key: Key::R,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+r</b> Sync".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn key_urls() -> KeyBinding {
|
||||
KeyBinding {
|
||||
key: Key::U,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+u</b> Sync".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn key_names() -> KeyBinding {
|
||||
KeyBinding {
|
||||
key: Key::N,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+n</b> Sync".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn key_folders() -> KeyBinding {
|
||||
KeyBinding {
|
||||
key: Key::C,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+c</b> Sync".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn key_totp() -> KeyBinding {
|
||||
KeyBinding {
|
||||
key: Key::T,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+t</b> Sync".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn key_lock() -> KeyBinding {
|
||||
KeyBinding {
|
||||
key: Key::L,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+l</b> Sync".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn show(config: Config, provider: PasswordProvider) -> anyhow::Result<()> {
|
||||
match gui::show(
|
||||
config.clone(),
|
||||
provider,
|
||||
false,
|
||||
None,
|
||||
Some(vec![
|
||||
key_type_all(),
|
||||
key_type_user(),
|
||||
key_type_password(),
|
||||
key_type_totp(),
|
||||
key_reload(),
|
||||
key_urls(),
|
||||
key_names(),
|
||||
key_folders(),
|
||||
key_totp(),
|
||||
key_lock(),
|
||||
]),
|
||||
) {
|
||||
Ok(selection) => {
|
||||
if let Some(meta) = selection.menu.data {
|
||||
if meta.ids.len() > 1 {
|
||||
return show(config, PasswordProvider::sub_provider(meta.ids));
|
||||
}
|
||||
|
||||
let id = meta.ids.iter().next().unwrap_or(&selection.menu.label);
|
||||
|
||||
sleep(Duration::from_millis(250));
|
||||
if let Some(key) = selection.custom_key {
|
||||
if key == key_type_all() {
|
||||
keyboard_type(&rbw_get_user(&id));
|
||||
keyboard_tab();
|
||||
keyboard_type(&rbw_get_password(&id));
|
||||
} else if key == key_type_user() {
|
||||
keyboard_type(&rbw_get_user(&id));
|
||||
} else if key == key_type_password() {
|
||||
keyboard_type(&rbw_get_password(&id));
|
||||
} else if key == key_type_totp() {
|
||||
keyboard_type(&rbw_get_totp(&id));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("missing meta data"))
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(anyhow::anyhow!(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
env_logger::Builder::new()
|
||||
.parse_filters(&env::var("RUST_LOG").unwrap_or_else(|_| "error".to_owned()))
|
||||
.format_timestamp_micros()
|
||||
.init();
|
||||
|
||||
let args = config::parse_args();
|
||||
let config = config::load_config(Some(&args)).unwrap_or(args);
|
||||
|
@ -122,102 +305,5 @@ fn main() -> anyhow::Result<()> {
|
|||
// todo eventually use a propper rust client for this, for now rbw is good enough
|
||||
let provider = PasswordProvider::new(&config);
|
||||
|
||||
let type_all = KeyBinding {
|
||||
key: Key::Num1,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+1</b> Type All".to_string(),
|
||||
};
|
||||
|
||||
let type_user = KeyBinding {
|
||||
key: Key::Num2,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+2</b> Type User".to_string(),
|
||||
};
|
||||
|
||||
let type_password = KeyBinding {
|
||||
key: Key::Num3,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+3</b> Type Password".to_string(),
|
||||
};
|
||||
|
||||
let type_totp = KeyBinding {
|
||||
key: Key::Num4,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+3</b> Type Totp".to_string(),
|
||||
};
|
||||
|
||||
let reload = KeyBinding {
|
||||
key: Key::R,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+r</b> Sync".to_string(),
|
||||
};
|
||||
|
||||
let urls = KeyBinding {
|
||||
key: Key::U, // switch view to urls
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+u</b> Sync".to_string(),
|
||||
};
|
||||
|
||||
let names = KeyBinding {
|
||||
key: Key::N, // switch view to names
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+n</b> Sync".to_string(),
|
||||
};
|
||||
|
||||
let folders = KeyBinding {
|
||||
key: Key::C, // switch view to folders
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+c</b> Sync".to_string(),
|
||||
};
|
||||
|
||||
let totp = KeyBinding {
|
||||
key: Key::T,
|
||||
modifiers: Modifier::Alt, // switch view to totp
|
||||
label: "<b>Alt+t</b> Sync".to_string(),
|
||||
};
|
||||
|
||||
let lock = KeyBinding {
|
||||
key: Key::L,
|
||||
modifiers: Modifier::Alt,
|
||||
label: "<b>Alt+l</b> Sync".to_string(),
|
||||
};
|
||||
|
||||
match gui::show(
|
||||
config,
|
||||
provider,
|
||||
false,
|
||||
None,
|
||||
Some(vec![
|
||||
type_all.clone(),
|
||||
type_user.clone(),
|
||||
type_password.clone(),
|
||||
type_totp.clone(),
|
||||
reload.clone(),
|
||||
urls.clone(),
|
||||
names.clone(),
|
||||
folders.clone(),
|
||||
totp.clone(),
|
||||
lock.clone(),
|
||||
]),
|
||||
) {
|
||||
Ok(selection) => {
|
||||
let id = selection.menu.label.replace("\n", "");
|
||||
sleep(Duration::from_millis(250));
|
||||
if let Some(key) = selection.custom_key {
|
||||
if key == type_all {
|
||||
keyboard_type(&rbw_get_user(&id));
|
||||
keyboard_tab();
|
||||
keyboard_type(&rbw_get_password(&id));
|
||||
} else if key == type_user {
|
||||
keyboard_type(&rbw_get_user(&id));
|
||||
} else if key == type_password {
|
||||
keyboard_type(&rbw_get_password(&id));
|
||||
} else if key == type_totp {
|
||||
keyboard_type(&rbw_get_totp(&id));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(anyhow::anyhow!(e)),
|
||||
}
|
||||
Ok(())
|
||||
show(config, provider)
|
||||
}
|
||||
|
|
|
@ -598,6 +598,7 @@ impl DMenuProvider {
|
|||
.lines()
|
||||
.map(String::from)
|
||||
.map(|s| MenuItem::new(s.clone(), None, None, vec![], None, 0.0, None))
|
||||
.rev()
|
||||
.collect();
|
||||
|
||||
gui::apply_sort(&mut items, sort_order);
|
||||
|
|
Loading…
Add table
Reference in a new issue