mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-25 09:59:21 +01:00
Fallback to default config on metaconfig parse fail
This commit is contained in:
parent
10f4ebf25c
commit
cce7ca8314
5 changed files with 117 additions and 176 deletions
|
@ -18,7 +18,8 @@
|
|||
|
||||
# The command Pinnacle will run on startup and when you reload your config.
|
||||
# Paths are relative to the directory the metaconfig.toml file is in.
|
||||
command = "lua example_config.lua"
|
||||
# This must be an array.
|
||||
command = ["lua", "example_config.lua"]
|
||||
|
||||
### Keybinds ###
|
||||
# Each keybind takes in a table with two fields: `modifiers` and `key`.
|
||||
|
|
214
src/config.rs
214
src/config.rs
|
@ -7,7 +7,6 @@ use std::{
|
|||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use calloop::channel::Sender;
|
||||
use smithay::input::keyboard::keysyms;
|
||||
use toml::Table;
|
||||
|
||||
|
@ -25,7 +24,7 @@ use self::api::msg::{
|
|||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
pub struct Metaconfig {
|
||||
pub command: String,
|
||||
pub command: Vec<String>,
|
||||
pub envs: Option<Table>,
|
||||
pub reload_keybind: Keybind,
|
||||
pub kill_keybind: Keybind,
|
||||
|
@ -135,106 +134,8 @@ pub fn get_config_dir() -> PathBuf {
|
|||
config_dir.unwrap_or(crate::XDG_BASE_DIRS.get_config_home())
|
||||
}
|
||||
|
||||
pub fn start_config(
|
||||
tx_channel: Sender<api::msg::Msg>,
|
||||
config_dir: impl AsRef<Path>,
|
||||
) -> anyhow::Result<ConfigReturn> {
|
||||
std::env::set_var("PINNACLE_LIB_DIR", crate::XDG_BASE_DIRS.get_data_home());
|
||||
|
||||
let config_dir = config_dir.as_ref();
|
||||
tracing::debug!("config dir is {:?}", config_dir);
|
||||
|
||||
let metaconfig = parse(config_dir)?;
|
||||
|
||||
// If a socket is provided in the metaconfig, use it.
|
||||
let socket_dir = if let Some(socket_dir) = &metaconfig.socket_dir {
|
||||
let socket_dir = shellexpand::full(socket_dir)?.to_string();
|
||||
|
||||
// cd into the metaconfig dir and canonicalize to preserve relative paths
|
||||
// like ./dir/here
|
||||
let current_dir = std::env::current_dir()?;
|
||||
|
||||
std::env::set_current_dir(config_dir)?;
|
||||
let socket_dir = PathBuf::from(socket_dir).canonicalize()?;
|
||||
std::env::set_current_dir(current_dir)?;
|
||||
socket_dir
|
||||
} else {
|
||||
// Otherwise, use $XDG_RUNTIME_DIR. If that doesn't exist, use /tmp.
|
||||
crate::XDG_BASE_DIRS
|
||||
.get_runtime_directory()
|
||||
.cloned()
|
||||
.unwrap_or(PathBuf::from(crate::config::api::DEFAULT_SOCKET_DIR))
|
||||
};
|
||||
|
||||
let socket_source = PinnacleSocketSource::new(tx_channel, &socket_dir)
|
||||
.context("Failed to create socket source")?;
|
||||
|
||||
let reload_keybind = metaconfig.reload_keybind;
|
||||
let kill_keybind = metaconfig.kill_keybind;
|
||||
|
||||
let mut command = metaconfig.command.split(' ');
|
||||
|
||||
let arg1 = command
|
||||
.next()
|
||||
.context("command in metaconfig.toml was empty")?;
|
||||
|
||||
let envs = metaconfig
|
||||
.envs
|
||||
.unwrap_or(toml::map::Map::new())
|
||||
.into_iter()
|
||||
.filter_map(|(key, val)| {
|
||||
if let toml::Value::String(string) = val {
|
||||
Some((
|
||||
key,
|
||||
shellexpand::full_with_context(
|
||||
&string,
|
||||
|| std::env::var("HOME").ok(),
|
||||
// Expand nonexistent vars to an empty string instead of crashing
|
||||
|var| Ok::<_, ()>(Some(std::env::var(var).unwrap_or("".to_string()))),
|
||||
)
|
||||
.ok()?
|
||||
.to_string(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
tracing::debug!("Config envs are {:?}", envs);
|
||||
|
||||
let child = async_process::Command::new(arg1)
|
||||
.args(command)
|
||||
.envs(envs)
|
||||
.current_dir(config_dir)
|
||||
.stdout(async_process::Stdio::inherit())
|
||||
.stderr(async_process::Stdio::inherit())
|
||||
.kill_on_drop(true)
|
||||
.spawn()
|
||||
.expect("failed to spawn config");
|
||||
|
||||
tracing::info!("Started config with {}", metaconfig.command);
|
||||
|
||||
let reload_mask = ModifierMask::from(reload_keybind.modifiers);
|
||||
let kill_mask = ModifierMask::from(kill_keybind.modifiers);
|
||||
|
||||
Ok(ConfigReturn {
|
||||
reload_keybind: (reload_mask, reload_keybind.key as u32),
|
||||
kill_keybind: (kill_mask, kill_keybind.key as u32),
|
||||
config_child_handle: child,
|
||||
socket_source,
|
||||
})
|
||||
}
|
||||
|
||||
pub struct ConfigReturn {
|
||||
pub reload_keybind: (ModifierMask, u32),
|
||||
pub kill_keybind: (ModifierMask, u32),
|
||||
pub config_child_handle: async_process::Child,
|
||||
pub socket_source: PinnacleSocketSource,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn restart_config(&mut self, config_dir: impl AsRef<Path>) -> anyhow::Result<()> {
|
||||
pub fn start_config(&mut self, config_dir: impl AsRef<Path>) -> anyhow::Result<()> {
|
||||
let config_dir = config_dir.as_ref();
|
||||
|
||||
tracing::info!("Restarting config");
|
||||
|
@ -253,17 +154,100 @@ impl State {
|
|||
|
||||
tracing::debug!("Killing old config");
|
||||
|
||||
self.loop_handle.remove(self.api_state.socket_token);
|
||||
if let Some(token) = self.api_state.socket_token {
|
||||
// Should only happen if parsing the metaconfig failed
|
||||
self.loop_handle.remove(token);
|
||||
}
|
||||
|
||||
let ConfigReturn {
|
||||
reload_keybind,
|
||||
kill_keybind,
|
||||
mut config_child_handle,
|
||||
socket_source,
|
||||
} = start_config(self.api_state.tx_channel.clone(), config_dir)?;
|
||||
let tx_channel = self.api_state.tx_channel.clone();
|
||||
|
||||
std::env::set_var("PINNACLE_LIB_DIR", crate::XDG_BASE_DIRS.get_data_home());
|
||||
|
||||
tracing::debug!("config dir is {:?}", config_dir);
|
||||
|
||||
let metaconfig = match parse(config_dir) {
|
||||
Ok(metaconfig) => metaconfig,
|
||||
Err(_) => {
|
||||
self.start_config(crate::XDG_BASE_DIRS.get_data_home().join("lua"))?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
// If a socket is provided in the metaconfig, use it.
|
||||
let socket_dir = if let Some(socket_dir) = &metaconfig.socket_dir {
|
||||
let socket_dir = shellexpand::full(socket_dir)?.to_string();
|
||||
|
||||
// cd into the metaconfig dir and canonicalize to preserve relative paths
|
||||
// like ./dir/here
|
||||
let current_dir = std::env::current_dir()?;
|
||||
|
||||
std::env::set_current_dir(config_dir)?;
|
||||
let socket_dir = PathBuf::from(socket_dir).canonicalize()?;
|
||||
std::env::set_current_dir(current_dir)?;
|
||||
socket_dir
|
||||
} else {
|
||||
// Otherwise, use $XDG_RUNTIME_DIR. If that doesn't exist, use /tmp.
|
||||
crate::XDG_BASE_DIRS
|
||||
.get_runtime_directory()
|
||||
.cloned()
|
||||
.unwrap_or(PathBuf::from(crate::config::api::DEFAULT_SOCKET_DIR))
|
||||
};
|
||||
|
||||
let socket_source = PinnacleSocketSource::new(tx_channel, &socket_dir)
|
||||
.context("Failed to create socket source")?;
|
||||
|
||||
let reload_keybind = metaconfig.reload_keybind;
|
||||
let kill_keybind = metaconfig.kill_keybind;
|
||||
|
||||
let mut command = metaconfig.command.iter();
|
||||
|
||||
let arg1 = command
|
||||
.next()
|
||||
.context("command in metaconfig.toml was empty")?;
|
||||
|
||||
let envs = metaconfig
|
||||
.envs
|
||||
.unwrap_or(toml::map::Map::new())
|
||||
.into_iter()
|
||||
.filter_map(|(key, val)| {
|
||||
if let toml::Value::String(string) = val {
|
||||
Some((
|
||||
key,
|
||||
shellexpand::full_with_context(
|
||||
&string,
|
||||
|| std::env::var("HOME").ok(),
|
||||
// Expand nonexistent vars to an empty string instead of crashing
|
||||
|var| Ok::<_, ()>(Some(std::env::var(var).unwrap_or("".to_string()))),
|
||||
)
|
||||
.ok()?
|
||||
.to_string(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
tracing::debug!("Config envs are {:?}", envs);
|
||||
|
||||
let mut child = async_process::Command::new(arg1)
|
||||
.args(command)
|
||||
.envs(envs)
|
||||
.current_dir(config_dir)
|
||||
.stdout(async_process::Stdio::inherit())
|
||||
.stderr(async_process::Stdio::inherit())
|
||||
.kill_on_drop(true)
|
||||
.spawn()
|
||||
.expect("failed to spawn config");
|
||||
|
||||
tracing::info!("Started config with {:?}", metaconfig.command);
|
||||
|
||||
let reload_mask = ModifierMask::from(reload_keybind.modifiers);
|
||||
let kill_mask = ModifierMask::from(kill_keybind.modifiers);
|
||||
|
||||
let reload_keybind = (reload_mask, reload_keybind.key as u32);
|
||||
let kill_keybind = (kill_mask, kill_keybind.key as u32);
|
||||
|
||||
// TODO: there is some code dup in here and in state.rs because the state doesn't
|
||||
// | exist on creation
|
||||
let socket_token = self
|
||||
.loop_handle
|
||||
.insert_source(socket_source, |stream, _, data| {
|
||||
|
@ -281,18 +265,18 @@ impl State {
|
|||
}
|
||||
})?;
|
||||
|
||||
self.input_state.reload_keybind = reload_keybind;
|
||||
self.input_state.kill_keybind = kill_keybind;
|
||||
self.api_state.socket_token = socket_token;
|
||||
self.input_state.reload_keybind = Some(reload_keybind);
|
||||
self.input_state.kill_keybind = Some(kill_keybind);
|
||||
self.api_state.socket_token = Some(socket_token);
|
||||
|
||||
let loop_handle = self.loop_handle.clone();
|
||||
|
||||
let _ = self.async_scheduler.schedule(async move {
|
||||
let _ = config_child_handle.status().await;
|
||||
let _ = child.status().await;
|
||||
|
||||
loop_handle.insert_idle(|data| {
|
||||
data.state
|
||||
.restart_config(crate::XDG_BASE_DIRS.get_data_home().join("lua"))
|
||||
.start_config(crate::XDG_BASE_DIRS.get_data_home().join("lua"))
|
||||
.expect("failed to load default config");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -243,7 +243,7 @@ pub struct ApiState {
|
|||
/// The stream API messages are being sent through.
|
||||
pub stream: Option<Arc<Mutex<UnixStream>>>,
|
||||
/// A token used to remove the socket source from the event loop on config restart.
|
||||
pub socket_token: RegistrationToken,
|
||||
pub socket_token: Option<RegistrationToken>,
|
||||
/// The sending channel used to send API messages received from the socket source to a handler.
|
||||
pub tx_channel: Sender<Msg>,
|
||||
}
|
||||
|
|
20
src/input.rs
20
src/input.rs
|
@ -27,23 +27,19 @@ use smithay::{
|
|||
|
||||
use crate::state::State;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct InputState {
|
||||
/// A hashmap of modifier keys and keycodes to callback IDs
|
||||
pub keybinds: HashMap<(ModifierMask, u32), CallbackId>,
|
||||
/// A hashmap of modifier keys and mouse button codes to callback IDs
|
||||
pub mousebinds: HashMap<(ModifierMask, u32, MouseEdge), CallbackId>,
|
||||
pub reload_keybind: (ModifierMask, u32),
|
||||
pub kill_keybind: (ModifierMask, u32),
|
||||
pub reload_keybind: Option<(ModifierMask, u32)>,
|
||||
pub kill_keybind: Option<(ModifierMask, u32)>,
|
||||
}
|
||||
|
||||
impl InputState {
|
||||
pub fn new(reload_keybind: (ModifierMask, u32), kill_keybind: (ModifierMask, u32)) -> Self {
|
||||
Self {
|
||||
keybinds: HashMap::new(),
|
||||
mousebinds: HashMap::new(),
|
||||
reload_keybind,
|
||||
kill_keybind,
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,9 +199,9 @@ impl State {
|
|||
(None, None) => ()
|
||||
}
|
||||
|
||||
if (modifier_mask, mod_sym) == kill_keybind {
|
||||
if Some((modifier_mask, mod_sym)) == kill_keybind {
|
||||
return FilterResult::Intercept(KeyAction::Quit);
|
||||
} else if (modifier_mask, mod_sym) == reload_keybind {
|
||||
} else if Some((modifier_mask, mod_sym)) == reload_keybind {
|
||||
return FilterResult::Intercept(KeyAction::ReloadConfig);
|
||||
} else if let mut vt @ keysyms::KEY_XF86Switch_VT_1..=keysyms::KEY_XF86Switch_VT_12 =
|
||||
keysym.modified_sym() {
|
||||
|
@ -255,7 +251,7 @@ impl State {
|
|||
self.loop_signal.stop();
|
||||
}
|
||||
Some(KeyAction::ReloadConfig) => {
|
||||
self.restart_config(crate::config::get_config_dir())
|
||||
self.start_config(crate::config::get_config_dir())
|
||||
.expect("failed to restart config");
|
||||
}
|
||||
None => {}
|
||||
|
|
54
src/state.rs
54
src/state.rs
|
@ -2,18 +2,13 @@
|
|||
|
||||
mod api_handlers;
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
os::fd::AsRawFd,
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
use std::{cell::RefCell, os::fd::AsRawFd, sync::Arc, time::Duration};
|
||||
|
||||
use crate::{
|
||||
backend::{udev::Udev, winit::Winit, BackendData},
|
||||
config::{
|
||||
api::{msg::Msg, ApiState},
|
||||
Config, ConfigReturn,
|
||||
Config,
|
||||
},
|
||||
cursor::Cursor,
|
||||
focus::FocusState,
|
||||
|
@ -190,53 +185,18 @@ impl State {
|
|||
|
||||
let (tx_channel, rx_channel) = calloop::channel::channel::<Msg>();
|
||||
|
||||
let ConfigReturn {
|
||||
reload_keybind,
|
||||
kill_keybind,
|
||||
mut config_child_handle,
|
||||
socket_source,
|
||||
} = crate::config::start_config(tx_channel.clone(), crate::config::get_config_dir())?;
|
||||
|
||||
let socket_token_ret = loop_handle.insert_source(socket_source, |stream, _, data| {
|
||||
if let Some(old_stream) = data
|
||||
.state
|
||||
.api_state
|
||||
.stream
|
||||
.replace(Arc::new(Mutex::new(stream)))
|
||||
{
|
||||
old_stream
|
||||
.lock()
|
||||
.expect("Couldn't lock old stream")
|
||||
.shutdown(std::net::Shutdown::Both)
|
||||
.expect("Couldn't shutdown old stream");
|
||||
loop_handle.insert_idle(|data| {
|
||||
if let Err(err) = data.state.start_config(crate::config::get_config_dir()) {
|
||||
panic!("failed to start config: {err}");
|
||||
}
|
||||
});
|
||||
|
||||
let socket_token = match socket_token_ret {
|
||||
Ok(token) => token,
|
||||
Err(err) => {
|
||||
anyhow::bail!("Failed to insert socket source into event loop: {err}");
|
||||
}
|
||||
};
|
||||
|
||||
let (executor, sched) = calloop::futures::executor::<()>()?;
|
||||
|
||||
if let Err(err) = loop_handle.insert_source(executor, |_, _, _| {}) {
|
||||
anyhow::bail!("Failed to insert async executor into event loop: {err}");
|
||||
}
|
||||
|
||||
let loop_handle_clone = loop_handle.clone();
|
||||
|
||||
let _ = sched.schedule(async move {
|
||||
let _ = config_child_handle.status().await;
|
||||
|
||||
loop_handle_clone.insert_idle(|data| {
|
||||
data.state
|
||||
.restart_config(crate::XDG_BASE_DIRS.get_data_home().join("lua"))
|
||||
.expect("failed to load default config");
|
||||
});
|
||||
});
|
||||
|
||||
let display_handle = display.handle();
|
||||
let mut seat_state = SeatState::new();
|
||||
|
||||
|
@ -322,10 +282,10 @@ impl State {
|
|||
primary_selection_state: PrimarySelectionState::new::<Self>(&display_handle),
|
||||
layer_shell_state: WlrLayerShellState::new::<Self>(&display_handle),
|
||||
|
||||
input_state: InputState::new(reload_keybind, kill_keybind),
|
||||
input_state: InputState::new(),
|
||||
api_state: ApiState {
|
||||
stream: None,
|
||||
socket_token,
|
||||
socket_token: None,
|
||||
tx_channel,
|
||||
},
|
||||
focus_state: FocusState::new(),
|
||||
|
|
Loading…
Reference in a new issue