support custom auto typing fixes #91
This commit is contained in:
parent
024e24d4de
commit
b63b02830a
8 changed files with 122 additions and 44 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2925,6 +2925,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"env_logger",
|
||||
"log",
|
||||
"serde",
|
||||
"worf",
|
||||
]
|
||||
|
||||
|
|
|
@ -471,7 +471,7 @@ fn main() -> Result<(), String> {
|
|||
.init();
|
||||
|
||||
let mut cfg = HyprSpaceConfig::parse();
|
||||
cfg.worf = worf::config::load_config(Some(&cfg.worf)).unwrap_or(cfg.worf);
|
||||
cfg.worf = worf::config::load_worf_config(Some(&cfg.worf)).unwrap_or(cfg.worf);
|
||||
if cfg.worf.prompt().is_none() {
|
||||
cfg.worf.set_prompt(cfg.hypr_space_mode().to_string());
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ fn main() -> Result<(), String> {
|
|||
|
||||
let args = config::parse_args();
|
||||
let config = Arc::new(RwLock::new(
|
||||
config::load_config(Some(&args)).unwrap_or(args),
|
||||
config::load_worf_config(Some(&args)).unwrap_or(args),
|
||||
));
|
||||
|
||||
let cache_path = desktop::cache_file_path(&config.read().unwrap(), "worf-hyprswitch")
|
||||
|
|
|
@ -7,8 +7,5 @@ edition = "2024"
|
|||
worf = {path = "../../worf"}
|
||||
env_logger = "0.11.8"
|
||||
log = "0.4.27"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
|
||||
# todo re-add this
|
||||
#[features]
|
||||
#default = [] # nothing enabled by default
|
||||
#warden = ["worf-warden"]
|
||||
|
|
|
@ -9,4 +9,25 @@ Simple password manager build upon these additional tools aside worf
|
|||
|
||||
The idea it taken from https://github.com/mattydebie/bitwarden-rofi/blob/master/bwmenu
|
||||
|
||||
## Custom auto typing
|
||||
* Auto typing supports custom keys. Just pass user name with `$U` and pw with `$P`
|
||||
* I.e. the default is `$U\t$P` which is user, tab, password.
|
||||
* This is using ydotool to type, so see their documentation for key input details.
|
||||
|
||||
### Example
|
||||
`~/.config/worf/warden`
|
||||
|
||||
```toml
|
||||
[custom_auto_types]
|
||||
# This will use User, enter, password for the demo entry.
|
||||
# You can use the id or the label as key, where id has higher precedence.
|
||||
Demo = "$U\n$P"
|
||||
# Will sleep 500ms before typing password
|
||||
# Any underscore will be ignored
|
||||
Delayed = "$U_\n_$S_500_$P"
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
|
@ -6,7 +7,6 @@ use std::{
|
|||
thread::sleep,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use worf::{
|
||||
config::{self, Config, CustomKeyHintLocation, Key},
|
||||
desktop::{copy_to_clipboard, spawn_fork},
|
||||
|
@ -120,20 +120,44 @@ fn keyboard_type(text: &str) {
|
|||
.expect("Failed to execute ydotool");
|
||||
}
|
||||
|
||||
fn keyboard_tab() {
|
||||
Command::new("ydotool")
|
||||
.arg("type")
|
||||
.arg("\t")
|
||||
.output()
|
||||
.expect("Failed to execute ydotool");
|
||||
fn parse_cmd(cmd: &str) -> (&str, Option<u64>, Option<&str>) {
|
||||
if let Some(pos) = cmd.find("$S") {
|
||||
let left = &cmd[..pos];
|
||||
let rest = &cmd[pos + 2..]; // Skip "$S"
|
||||
|
||||
// Extract digits after "$S"
|
||||
let num_part: String = rest.chars().take_while(|c| c.is_ascii_digit()).collect();
|
||||
|
||||
if let Ok(number) = num_part.parse::<u64>() {
|
||||
let right = &rest[num_part.len()..];
|
||||
return (left, Some(number), Some(right));
|
||||
}
|
||||
}
|
||||
|
||||
(cmd, None, None)
|
||||
}
|
||||
|
||||
fn keyboard_return() {
|
||||
Command::new("ydotool")
|
||||
.arg("type")
|
||||
.arg("\n")
|
||||
.output()
|
||||
.expect("Failed to execute ydotool");
|
||||
keyboard_type("\n");
|
||||
}
|
||||
|
||||
fn keyboard_auto_type(cmd: &str, id: &str) -> Result<(), String> {
|
||||
let user = rbw_get_user(id, false)?;
|
||||
let pw = rbw_get_password(id, false)?;
|
||||
|
||||
let ydo_string = cmd.replace('_', "").replace("$U", &user).replace("$P", &pw);
|
||||
|
||||
let (left, sleep_ms, right) = parse_cmd(&ydo_string);
|
||||
keyboard_type(left);
|
||||
if let Some(sleep_ms) = sleep_ms {
|
||||
sleep(Duration::from_millis(sleep_ms));
|
||||
}
|
||||
|
||||
if let Some(right) = right {
|
||||
keyboard_type(right);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rbw(cmd: &str, args: Option<Vec<&str>>) -> Result<String, String> {
|
||||
|
@ -281,7 +305,11 @@ fn key_lock() -> KeyBinding {
|
|||
}
|
||||
}
|
||||
|
||||
fn show(config: Arc<RwLock<Config>>, provider: Arc<Mutex<PasswordProvider>>) -> Result<(), String> {
|
||||
fn show(
|
||||
config: Arc<RwLock<Config>>,
|
||||
provider: Arc<Mutex<PasswordProvider>>,
|
||||
warden_config: WardenConfig,
|
||||
) -> Result<(), String> {
|
||||
match gui::show(
|
||||
&config,
|
||||
provider,
|
||||
|
@ -314,6 +342,7 @@ fn show(config: Arc<RwLock<Config>>, provider: Arc<Mutex<PasswordProvider>>) ->
|
|||
return show(
|
||||
config,
|
||||
Arc::new(Mutex::new(PasswordProvider::sub_provider(meta.ids)?)),
|
||||
warden_config.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -322,9 +351,13 @@ fn show(config: Arc<RwLock<Config>>, provider: Arc<Mutex<PasswordProvider>>) ->
|
|||
sleep(Duration::from_millis(500));
|
||||
if let Some(key) = selection.custom_key {
|
||||
if key == key_type_all() || key == key_type_all_and_enter() {
|
||||
keyboard_type(&rbw_get_user(id, false)?);
|
||||
keyboard_tab();
|
||||
keyboard_type(&rbw_get_password(id, false)?);
|
||||
let default = "$U\t$P".to_owned();
|
||||
let typing = warden_config
|
||||
.custom_auto_types
|
||||
.get(id)
|
||||
.or(warden_config.custom_auto_types.get(&selection.menu.label))
|
||||
.unwrap_or(&default);
|
||||
keyboard_auto_type(typing, id)?;
|
||||
} else if key == key_type_user() || key == key_type_user_and_enter() {
|
||||
keyboard_type(&rbw_get_user(id, false)?);
|
||||
} else if key == key_type_password() || key == key_type_password_and_enter() {
|
||||
|
@ -357,6 +390,11 @@ fn show(config: Arc<RwLock<Config>>, provider: Arc<Mutex<PasswordProvider>>) ->
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
struct WardenConfig {
|
||||
custom_auto_types: HashMap<String, String>,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
env_logger::Builder::new()
|
||||
.parse_filters(&env::var("RUST_LOG").unwrap_or_else(|_| "error".to_owned()))
|
||||
|
@ -364,10 +402,13 @@ fn main() -> Result<(), String> {
|
|||
.init();
|
||||
|
||||
let args = config::parse_args();
|
||||
let config = Arc::new(RwLock::new(
|
||||
config::load_config(Some(&args)).unwrap_or(args),
|
||||
let worf_config = Arc::new(RwLock::new(
|
||||
config::load_worf_config(Some(&args)).unwrap_or(args.clone()),
|
||||
));
|
||||
|
||||
let warden_config: WardenConfig = config::load_config(Some(&args), "worf", "warden")
|
||||
.map_err(|e| format!("failed to parse warden config {e}"))?;
|
||||
|
||||
if !groups().contains("input") {
|
||||
log::error!(
|
||||
"User must be in input group. 'sudo usermod -aG input $USER', then login again"
|
||||
|
@ -381,6 +422,8 @@ fn main() -> Result<(), String> {
|
|||
}
|
||||
|
||||
// todo eventually use a propper rust client for this, for now rbw is good enough
|
||||
let provider = Arc::new(Mutex::new(PasswordProvider::new(&config.read().unwrap())?));
|
||||
show(config, provider)
|
||||
let provider = Arc::new(Mutex::new(PasswordProvider::new(
|
||||
&worf_config.read().unwrap(),
|
||||
)?));
|
||||
show(worf_config, provider, warden_config)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::{env, fs, path::PathBuf, str::FromStr};
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
|
@ -950,11 +951,9 @@ fn style_path(full_path: Option<&String>) -> Result<PathBuf, Error> {
|
|||
/// # Errors
|
||||
///
|
||||
/// Will return Err when it cannot resolve any path or no style is found
|
||||
pub fn conf_path(full_path: Option<&String>) -> Result<PathBuf, Error> {
|
||||
let alternative_paths = path_alternatives(
|
||||
vec![dirs::config_dir()],
|
||||
&PathBuf::from("worf").join("config"),
|
||||
);
|
||||
pub fn conf_path(full_path: Option<&String>, folder: &str, name: &str) -> Result<PathBuf, Error> {
|
||||
let alternative_paths =
|
||||
path_alternatives(vec![dirs::config_dir()], &PathBuf::from(folder).join(name));
|
||||
resolve_path(full_path, alternative_paths.into_iter().collect())
|
||||
}
|
||||
|
||||
|
@ -996,22 +995,39 @@ pub fn resolve_path(
|
|||
/// * cannot parse the config file
|
||||
/// * no config file exists
|
||||
/// * config file and args cannot be merged
|
||||
pub fn load_config(args_opt: Option<&Config>) -> Result<Config, Error> {
|
||||
let config_path = conf_path(args_opt.as_ref().and_then(|c| c.cfg_path.as_ref()));
|
||||
pub fn load_worf_config(args_opt: Option<&Config>) -> Result<Config, Error> {
|
||||
let mut config = load_config(args_opt, "worf", "config")?;
|
||||
if let Some(args) = args_opt {
|
||||
let merge_result = merge_config_with_args(&mut config, args)
|
||||
.map_err(|e| Error::ParsingError(format!("{e}")))?;
|
||||
Ok(merge_result)
|
||||
} else {
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
///
|
||||
/// Will return Err when it
|
||||
/// * cannot read the config file
|
||||
/// * cannot parse the config file
|
||||
/// * no config file exists
|
||||
/// * config file and args cannot be merged
|
||||
pub fn load_config<T: DeserializeOwned>(
|
||||
args_opt: Option<&Config>,
|
||||
folder: &str,
|
||||
name: &str,
|
||||
) -> Result<T, Error> {
|
||||
let config_path = conf_path(
|
||||
args_opt.as_ref().and_then(|c| c.cfg_path.as_ref()),
|
||||
folder,
|
||||
name,
|
||||
);
|
||||
match config_path {
|
||||
Ok(path) => {
|
||||
log::debug!("loading config from {}", path.display());
|
||||
let toml_content = fs::read_to_string(path).map_err(|e| Error::Io(format!("{e}")))?;
|
||||
let mut config: Config =
|
||||
toml::from_str(&toml_content).map_err(|e| Error::ParsingError(format!("{e}")))?;
|
||||
|
||||
if let Some(args) = args_opt {
|
||||
let merge_result = merge_config_with_args(&mut config, args)
|
||||
.map_err(|e| Error::ParsingError(format!("{e}")))?;
|
||||
Ok(merge_result)
|
||||
} else {
|
||||
Ok(config)
|
||||
}
|
||||
toml::from_str(&toml_content).map_err(|e| Error::ParsingError(format!("{e}")))
|
||||
}
|
||||
|
||||
Err(e) => Err(Error::Io(format!("{e}"))),
|
||||
|
|
|
@ -95,7 +95,7 @@ fn main() {
|
|||
.init();
|
||||
|
||||
let mut config = MainConfig::parse();
|
||||
config.worf = config::load_config(Some(&config.worf)).unwrap_or(config.worf);
|
||||
config.worf = config::load_worf_config(Some(&config.worf)).unwrap_or(config.worf);
|
||||
if config.worf.prompt().is_none() {
|
||||
config.worf.set_prompt(config.show.to_string());
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue