Fallback to default config on metaconfig parse fail

This commit is contained in:
Ottatop 2023-09-21 18:07:56 -05:00
parent 10f4ebf25c
commit cce7ca8314
5 changed files with 117 additions and 176 deletions

View file

@ -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`.

View file

@ -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");
});
});

View file

@ -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>,
}

View file

@ -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 => {}

View file

@ -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(),