Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default Modifier #23

Merged
merged 30 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c944c04
feat: Add default modifier to config struct
SergioRibera Sep 18, 2022
fa15a03
feat: Add test to new parse config
SergioRibera Sep 18, 2022
1ba8c6b
feat: make better test
SergioRibera Sep 18, 2022
060a506
feat: fix readme
SergioRibera Sep 18, 2022
9d5a0ca
feat: add more test for parser
SergioRibera Sep 18, 2022
27a9b35
feat: add subkeybind validation test
SergioRibera Sep 18, 2022
a961aec
feat: remove print lines from test and add chord command test
SergioRibera Sep 18, 2022
a731958
feat: remove print lines from test
SergioRibera Sep 18, 2022
a801d6e
fix: clippy warnings
SergioRibera Sep 18, 2022
7209fea
fix: clippy warnings
SergioRibera Sep 18, 2022
244f355
Move individual tests to test file
SergioRibera Oct 5, 2022
d3363e8
fix clippy warnings and format
SergioRibera Oct 5, 2022
b3e5ffd
feat: Impl TryFrom for Config from String
SergioRibera Oct 16, 2022
1326789
fix: Fix tests, change from_string to try_from
SergioRibera Oct 16, 2022
a99d83c
make clippy (pedantic) happy
VuiMuich Dec 14, 2022
d435150
Merge branch 'main' into feature/modifier_optional
VuiMuich Dec 14, 2022
7b6cb85
fix remaining merge issues
VuiMuich Dec 14, 2022
6dcabd2
Merge branch 'main' into feature/modifier_optional
VuiMuich Dec 14, 2022
2231e69
Merge branch 'clippy' into feature/modifier_optional
VuiMuich Dec 14, 2022
5b29df7
make clippy happy again
VuiMuich Dec 14, 2022
55683d1
fix part of the tests
VuiMuich Dec 15, 2022
ceee98e
alias media keys
VuiMuich Jun 27, 2023
3c38717
clippy auto fix
VuiMuich Jun 27, 2023
38d254f
bump `lefthk-core` to `0.2.0` to better reflect breaking changes
VuiMuich Jun 28, 2023
5ae6b1a
Merge branch 'main' into main
mautamu Oct 21, 2023
b121f14
Merge branch 'main' into feature/modifier_optional
mautamu Oct 21, 2023
139c932
Merge branch 'main' into feature/modifier_optional
VuiMuich Oct 23, 2023
3310bc4
fix fmt and clippy, merge main
VuiMuich Oct 23, 2023
5862ea0
fix: clippy test
SergioRibera Oct 24, 2023
d29aa0d
fix: format style
SergioRibera Oct 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ LeftHK - A hotkey daemon written in Rust
The configuration file should be created in ~/.config/lefthk/ and called config.ron. If the configuration file is not created the program will exit.
Example config:
```ron
#![enable(implicit_some)]
Config(
default_modifier: ["Mod4", "Shift"],
keybinds: [
Keybind(
command: Execute("st -e htop"),
modifier: ["Mod4", "Shift"],
key: Key("x"),
),
Keybind(
command: Executes(["st -e htop", "st -e bpytop"]),
modifier: ["Mod4", "Shift"],
key: Keys(["x", "m"]),
),
Keybind(
Expand Down
2 changes: 1 addition & 1 deletion lefthk-core/src/child.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl Children {
let (guard, _task_guard) = oneshot::channel();
let task_notify = Arc::new(Notify::new());
let notify = task_notify.clone();
let mut signals = Signals::new(&[signal::SIGCHLD]).expect("Couldn't setup signals.");
let mut signals = Signals::new([signal::SIGCHLD]).expect("Couldn't setup signals.");
tokio::task::spawn_blocking(move || loop {
if guard.is_closed() {
return;
Expand Down
2 changes: 1 addition & 1 deletion lefthk-core/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ impl Worker {
pub fn exec(&mut self, command: &str) -> Error {
let child = Command::new("sh")
.arg("-c")
.arg(&command)
.arg(command)
.stdin(Stdio::null())
.stdout(Stdio::null())
.spawn()?;
Expand Down
163 changes: 86 additions & 77 deletions lefthk/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::errors::{LeftError, Result};
use lefthk_core::config::Command as core_command;
use lefthk_core::config::Keybind as core_keybind;
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, fs, path::Path};
use std::{fs, path::Path};
use xdg::BaseDirectories;

#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
Expand Down Expand Up @@ -42,101 +42,100 @@ macro_rules! get_keys {
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)]
pub struct Keybind {
pub command: Command,
pub modifier: Vec<String>,
pub modifier: Option<Vec<String>>,
pub key: Key,
}

impl TryFrom<Keybind> for Vec<core_keybind> {
type Error = LeftError;

fn try_from(kb: Keybind) -> Result<Self> {
let command_key_pairs: Vec<(core_command, String)> = match kb.command {
Command::Chord(children) if !children.is_empty() => {
let key = get_key!(kb.key);
let children = children
.iter()
.filter_map(|kb| match TryFrom::try_from(kb.clone()) {
Ok(keybinds) => Some::<Vec<lefthk_core::config::Keybind>>(keybinds),
Err(err) => {
tracing::error!("Invalid key binding: {}\n{:?}", err, kb);
None
}
})
.flatten()
.collect();
fn try_from(kb: Keybind, default_modifier: Vec<String>) -> Result<Vec<core_keybind>> {
VuiMuich marked this conversation as resolved.
Show resolved Hide resolved
let command_key_pairs: Vec<(core_command, String)> = match kb.command {
Command::Chord(children) if !children.is_empty() => {
let key = get_key!(kb.key);
let children = children
.iter()
.filter_map(|kb| match try_from(kb.clone(), default_modifier.clone()) {
Ok(keybinds) => Some::<Vec<lefthk_core::config::Keybind>>(keybinds),
Err(err) => {
tracing::error!("Invalid key binding: {}\n{:?}", err, kb);
None
}
})
.flatten()
.collect();

vec![(core_command::Chord(children), key)]
}
Command::Chord(_) => return Err(LeftError::ChildrenNotFound),
Command::Execute(value) if !value.is_empty() => {
let keys = get_key!(kb.key);
vec![(core_command::Execute(value), keys)]
}
Command::Execute(_) => return Err(LeftError::ValueNotFound),
Command::Executes(values) if !values.is_empty() => {
let keys = get_keys!(kb.key);
if keys.len() != values.len() {
return Err(LeftError::NumberOfKeysDiffersFromValues);
}
values
.iter()
.enumerate()
.map(|(i, v)| (core_command::Execute(v.to_owned()), keys[i].clone()))
.collect()
}
Command::Executes(_) => return Err(LeftError::ValuesNotFound),
Command::ExitChord => {
let keys = get_key!(kb.key);
vec![(core_command::ExitChord, keys)]
}
Command::Reload => {
let keys = get_key!(kb.key);
vec![(core_command::Reload, keys)]
}
Command::Kill => {
let keys = get_key!(kb.key);
vec![(core_command::Kill, keys)]
vec![(core_command::Chord(children), key)]
}
Command::Chord(_) => return Err(LeftError::ChildrenNotFound),
Command::Execute(value) if !value.is_empty() => {
let keys = get_key!(kb.key);
vec![(core_command::Execute(value), keys)]
}
Command::Execute(_) => return Err(LeftError::ValueNotFound),
Command::Executes(values) if !values.is_empty() => {
let keys = get_keys!(kb.key);
if keys.len() != values.len() {
return Err(LeftError::NumberOfKeysDiffersFromValues);
}
};
let keybinds = command_key_pairs
.iter()
.map(|(c, k)| core_keybind {
command: c.clone(),
modifier: kb.modifier.clone(),
key: k.to_owned(),
})
.collect();
Ok(keybinds)
}
values
.iter()
.enumerate()
.map(|(i, v)| (core_command::Execute(v.to_owned()), keys[i].clone()))
.collect()
}
Command::Executes(_) => return Err(LeftError::ValuesNotFound),
Command::ExitChord => {
let keys = get_key!(kb.key);
vec![(core_command::ExitChord, keys)]
}
Command::Reload => {
let keys = get_key!(kb.key);
vec![(core_command::Reload, keys)]
}
Command::Kill => {
let keys = get_key!(kb.key);
vec![(core_command::Kill, keys)]
}
};
let keybinds = command_key_pairs
.iter()
.map(|(c, k)| core_keybind {
command: c.clone(),
modifier: kb
.modifier
.clone()
.unwrap_or_else(|| default_modifier.clone()),
key: k.to_owned(),
})
.collect();
Ok(keybinds)
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct Config {
keybinds: Vec<Keybind>,
pub default_modifier: Vec<String>,
pub keybinds: Vec<Keybind>,
}

impl lefthk_core::config::Config for Config {
fn mapped_bindings(&self) -> Vec<lefthk_core::config::Keybind> {
self.keybinds
.iter()
.filter_map(|kb| match TryFrom::try_from(kb.clone()) {
Ok(keybinds) => Some::<Vec<lefthk_core::config::Keybind>>(keybinds),
Err(err) => {
tracing::error!("Invalid key binding: {}\n{:?}", err, kb);
None
}
})
.filter_map(
|kb| match try_from(kb.clone(), self.default_modifier.clone()) {
Ok(keybinds) => Some::<Vec<lefthk_core::config::Keybind>>(keybinds),
Err(err) => {
tracing::error!("Invalid key binding: {}\n{:?}", err, kb);
None
}
},
)
.flatten()
.collect()
}
}

pub fn load() -> Result<Config> {
let path = BaseDirectories::with_prefix(lefthk_core::LEFTHK_DIR_NAME)?;
fs::create_dir_all(&path.get_config_home())?;
let file_name = path.place_config_file("config.ron")?;
if Path::new(&file_name).exists() {
let contents = fs::read_to_string(file_name)?;
impl Config {
pub fn from_string(contents: String) -> Result<Self> {
SergioRibera marked this conversation as resolved.
Show resolved Hide resolved
println!("{contents}");
let mut config: Config = ron::from_str(&contents)?;
let global_exit_chord = config
.keybinds
Expand All @@ -150,7 +149,17 @@ pub fn load() -> Result<Config> {
.collect();
propagate_exit_chord(chords, global_exit_chord);

return Ok(config);
Ok(config)
}
}

pub fn load() -> Result<Config> {
let path = BaseDirectories::with_prefix(lefthk_core::LEFTHK_DIR_NAME)?;
fs::create_dir_all(&path.get_config_home())?;
let file_name = path.place_config_file("config.ron")?;
if Path::new(&file_name).exists() {
let contents = fs::read_to_string(file_name)?;
Config::from_string(contents)?;
}
Err(LeftError::NoConfigFound)
}
Expand Down
Loading