mirror of
https://github.com/k0kubun/xremap
synced 2024-11-19 09:25:31 +00:00
handle multiple config files (#269)
* handle multiple config files * shorter variable name * run formatter * extend virtual modifiers too
This commit is contained in:
parent
89ac4e6aca
commit
bec3ce9bd5
@ -17,7 +17,7 @@ use keymap::Keymap;
|
|||||||
use modmap::Modmap;
|
use modmap::Modmap;
|
||||||
use nix::sys::inotify::{AddWatchFlags, InitFlags, Inotify};
|
use nix::sys::inotify::{AddWatchFlags, InitFlags, Inotify};
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
use std::{collections::HashMap, error, fs, path::Path, time::SystemTime};
|
use std::{collections::HashMap, error, fs, path::PathBuf, time::SystemTime};
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
key::parse_key,
|
key::parse_key,
|
||||||
@ -46,12 +46,21 @@ pub struct Config {
|
|||||||
pub keymap_table: HashMap<Key, Vec<KeymapEntry>>,
|
pub keymap_table: HashMap<Key, Vec<KeymapEntry>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_config(filename: &Path) -> Result<Config, Box<dyn error::Error>> {
|
pub fn load_configs(filenames: &Vec<PathBuf>) -> Result<Config, Box<dyn error::Error>> {
|
||||||
let yaml = fs::read_to_string(&filename)?;
|
// Assumes filenames is non-empty
|
||||||
|
let yaml = fs::read_to_string(&filenames[0])?;
|
||||||
let mut config: Config = serde_yaml::from_str(&yaml)?;
|
let mut config: Config = serde_yaml::from_str(&yaml)?;
|
||||||
|
|
||||||
|
for filename in &filenames[1..] {
|
||||||
|
let yaml = fs::read_to_string(&filename)?;
|
||||||
|
let c: Config = serde_yaml::from_str(&yaml)?;
|
||||||
|
config.modmap.extend(c.modmap);
|
||||||
|
config.keymap.extend(c.keymap);
|
||||||
|
config.virtual_modifiers.extend(c.virtual_modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
// Timestamp for --watch=config
|
// Timestamp for --watch=config
|
||||||
config.modify_time = filename.metadata()?.modified().ok();
|
config.modify_time = filenames.last().and_then(|path| path.metadata().ok()?.modified().ok());
|
||||||
|
|
||||||
// Convert keymap for efficient keymap lookup
|
// Convert keymap for efficient keymap lookup
|
||||||
config.keymap_table = build_keymap_table(&config.keymap);
|
config.keymap_table = build_keymap_table(&config.keymap);
|
||||||
@ -59,14 +68,16 @@ pub fn load_config(filename: &Path) -> Result<Config, Box<dyn error::Error>> {
|
|||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config_watcher(watch: bool, file: &Path) -> anyhow::Result<Option<Inotify>> {
|
pub fn config_watcher(watch: bool, files: &Vec<PathBuf>) -> anyhow::Result<Option<Inotify>> {
|
||||||
if watch {
|
if watch {
|
||||||
let inotify = Inotify::init(InitFlags::IN_NONBLOCK)?;
|
let inotify = Inotify::init(InitFlags::IN_NONBLOCK)?;
|
||||||
inotify.add_watch(
|
for file in files {
|
||||||
file.parent().expect("config file has a parent directory"),
|
inotify.add_watch(
|
||||||
AddWatchFlags::IN_CREATE | AddWatchFlags::IN_MOVED_TO,
|
file.parent().expect("config file has a parent directory"),
|
||||||
)?;
|
AddWatchFlags::IN_CREATE | AddWatchFlags::IN_MOVED_TO,
|
||||||
inotify.add_watch(file, AddWatchFlags::IN_MODIFY)?;
|
)?;
|
||||||
|
inotify.add_watch(file, AddWatchFlags::IN_MODIFY)?;
|
||||||
|
}
|
||||||
Ok(Some(inotify))
|
Ok(Some(inotify))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
57
src/main.rs
57
src/main.rs
@ -6,7 +6,7 @@ use anyhow::{anyhow, bail, Context};
|
|||||||
use clap::{AppSettings, ArgEnum, IntoApp, Parser};
|
use clap::{AppSettings, ArgEnum, IntoApp, Parser};
|
||||||
use clap_complete::Shell;
|
use clap_complete::Shell;
|
||||||
use client::build_client;
|
use client::build_client;
|
||||||
use config::{config_watcher, load_config};
|
use config::{config_watcher, load_configs};
|
||||||
use device::InputDevice;
|
use device::InputDevice;
|
||||||
use event::Event;
|
use event::Event;
|
||||||
use nix::libc::ENODEV;
|
use nix::libc::ENODEV;
|
||||||
@ -17,7 +17,7 @@ use nix::sys::timerfd::{ClockId, TimerFd, TimerFlags};
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::stdout;
|
use std::io::stdout;
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
mod action;
|
mod action;
|
||||||
@ -45,7 +45,7 @@ struct Opts {
|
|||||||
/// Targets to watch
|
/// Targets to watch
|
||||||
///
|
///
|
||||||
/// - device: add new devices automatically
|
/// - device: add new devices automatically
|
||||||
/// - config: reload the config automatically
|
/// - config: reload the configs automatically
|
||||||
#[clap(
|
#[clap(
|
||||||
long,
|
long,
|
||||||
arg_enum,
|
arg_enum,
|
||||||
@ -67,9 +67,9 @@ struct Opts {
|
|||||||
/// - in fish: xremap --completions fish | source
|
/// - in fish: xremap --completions fish | source
|
||||||
#[clap(long, arg_enum, display_order = 100, value_name = "SHELL", verbatim_doc_comment)]
|
#[clap(long, arg_enum, display_order = 100, value_name = "SHELL", verbatim_doc_comment)]
|
||||||
completions: Option<Shell>,
|
completions: Option<Shell>,
|
||||||
/// Config file
|
/// Config file(s)
|
||||||
#[clap(required_unless_present = "completions")]
|
#[clap(required_unless_present = "completions", multiple_values = true)]
|
||||||
config: Option<PathBuf>,
|
configs: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(ArgEnum, Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(ArgEnum, Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
@ -94,7 +94,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
ignore: ignore_filter,
|
ignore: ignore_filter,
|
||||||
mouse,
|
mouse,
|
||||||
watch,
|
watch,
|
||||||
config,
|
configs,
|
||||||
completions,
|
completions,
|
||||||
} = Opts::parse();
|
} = Opts::parse();
|
||||||
|
|
||||||
@ -104,10 +104,22 @@ fn main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
let config_path = config.expect("config is set, if not completions");
|
let config_paths = match configs[..] {
|
||||||
let mut config = match config::load_config(&config_path) {
|
[] => panic!("config is set, if not completions"),
|
||||||
|
_ => configs,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut config = match config::load_configs(&config_paths) {
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
Err(e) => bail!("Failed to load config '{}': {}", config_path.display(), e),
|
Err(e) => bail!(
|
||||||
|
"Failed to load config '{}': {}",
|
||||||
|
config_paths
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.to_string_lossy())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("', '"),
|
||||||
|
e
|
||||||
|
),
|
||||||
};
|
};
|
||||||
let watch_devices = watch.contains(&WatchTargets::Device);
|
let watch_devices = watch.contains(&WatchTargets::Device);
|
||||||
let watch_config = watch.contains(&WatchTargets::Config);
|
let watch_config = watch.contains(&WatchTargets::Config);
|
||||||
@ -121,7 +133,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
Err(e) => bail!("Failed to prepare input devices: {}", e),
|
Err(e) => bail!("Failed to prepare input devices: {}", e),
|
||||||
};
|
};
|
||||||
let device_watcher = device_watcher(watch_devices).context("Setting up device watcher")?;
|
let device_watcher = device_watcher(watch_devices).context("Setting up device watcher")?;
|
||||||
let config_watcher = config_watcher(watch_config, &config_path).context("Setting up config watcher")?;
|
let config_watcher = config_watcher(watch_config, &config_paths).context("Setting up config watcher")?;
|
||||||
let watchers: Vec<_> = device_watcher.iter().chain(config_watcher.iter()).collect();
|
let watchers: Vec<_> = device_watcher.iter().chain(config_watcher.iter()).collect();
|
||||||
let mut handler = EventHandler::new(timer, &config.default_mode, delay, build_client());
|
let mut handler = EventHandler::new(timer, &config.default_mode, delay, build_client());
|
||||||
let output_device = match output_device(input_devices.values().next().map(InputDevice::bus_type)) {
|
let output_device = match output_device(input_devices.values().next().map(InputDevice::bus_type)) {
|
||||||
@ -166,7 +178,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
&device_filter,
|
&device_filter,
|
||||||
&ignore_filter,
|
&ignore_filter,
|
||||||
mouse,
|
mouse,
|
||||||
&config_path,
|
&config_paths,
|
||||||
)? {
|
)? {
|
||||||
break 'event_loop ReloadEvent::ReloadConfig;
|
break 'event_loop ReloadEvent::ReloadConfig;
|
||||||
}
|
}
|
||||||
@ -183,10 +195,17 @@ fn main() -> anyhow::Result<()> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
ReloadEvent::ReloadConfig => {
|
ReloadEvent::ReloadConfig => {
|
||||||
match (config.modify_time, config_path.metadata().and_then(|m| m.modified())) {
|
match (
|
||||||
(Some(last_mtime), Ok(current_mtim)) if last_mtime == current_mtim => continue,
|
config.modify_time,
|
||||||
|
config_paths
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.metadata().ok()?.modified().ok())
|
||||||
|
.flatten()
|
||||||
|
.max(),
|
||||||
|
) {
|
||||||
|
(Some(last_mtime), Some(current_mtim)) if last_mtime == current_mtim => continue,
|
||||||
_ => {
|
_ => {
|
||||||
if let Ok(c) = load_config(&config_path) {
|
if let Ok(c) = load_configs(&config_paths) {
|
||||||
println!("Reloading Config");
|
println!("Reloading Config");
|
||||||
config = c;
|
config = c;
|
||||||
}
|
}
|
||||||
@ -281,12 +300,16 @@ fn handle_config_changes(
|
|||||||
device_filter: &[String],
|
device_filter: &[String],
|
||||||
ignore_filter: &[String],
|
ignore_filter: &[String],
|
||||||
mouse: bool,
|
mouse: bool,
|
||||||
config_path: &Path,
|
config_paths: &Vec<PathBuf>,
|
||||||
) -> anyhow::Result<bool> {
|
) -> anyhow::Result<bool> {
|
||||||
for event in &events {
|
for event in &events {
|
||||||
match (event.mask, &event.name) {
|
match (event.mask, &event.name) {
|
||||||
// Dir events
|
// Dir events
|
||||||
(_, Some(name)) if name == config_path.file_name().expect("Config path has a file name") => {
|
(_, Some(name))
|
||||||
|
if config_paths
|
||||||
|
.iter()
|
||||||
|
.any(|p| name == p.file_name().expect("Config path has a file name")) =>
|
||||||
|
{
|
||||||
return Ok(false)
|
return Ok(false)
|
||||||
}
|
}
|
||||||
// File events
|
// File events
|
||||||
|
Loading…
Reference in New Issue
Block a user