Move config handling out of state.rs

This commit is contained in:
Ottatop 2023-09-20 16:43:50 -05:00
parent 5c88ceac83
commit c83f136cf7
4 changed files with 203 additions and 173 deletions

View file

@ -1490,7 +1490,7 @@ fn render_surface<'a>(
.collect::<Vec<_>>();
if !pending_wins.is_empty() {
tracing::debug!("Skipping frame, waiting on {pending_wins:?}");
// tracing::debug!("Skipping frame, waiting on {pending_wins:?}");
for win in windows.iter() {
win.send_frame(output, clock.now(), Some(Duration::ZERO), |_, _| {
Some(output.clone())

View file

@ -252,7 +252,7 @@ pub fn run_winit() -> anyhow::Result<()> {
.collect::<Vec<_>>();
if !pending_wins.is_empty() {
tracing::debug!("Skipping frame, waiting on {pending_wins:?}");
// tracing::debug!("Skipping frame, waiting on {pending_wins:?}");
for win in state.windows.iter() {
win.send_frame(
&output,

View file

@ -1,13 +1,23 @@
pub mod api;
use std::path::Path;
use crate::config::api::{msg::ModifierMask, PinnacleSocketSource};
use std::{
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
use anyhow::Context;
use calloop::channel::Sender;
use smithay::input::keyboard::keysyms;
use toml::Table;
use api::msg::Modifier;
use crate::{
state::{State, WithState},
tag::TagId,
};
#[derive(serde::Deserialize, Debug)]
pub struct Metaconfig {
pub command: String,
@ -97,7 +107,7 @@ pub enum Key {
Escape = keysyms::KEY_Escape,
}
pub fn parse(config_dir: &Path) -> anyhow::Result<Metaconfig> {
fn parse(config_dir: &Path) -> anyhow::Result<Metaconfig> {
let config_dir = config_dir.join("metaconfig.toml");
let metaconfig =
@ -105,3 +115,161 @@ pub fn parse(config_dir: &Path) -> anyhow::Result<Metaconfig> {
toml::from_str(&metaconfig).context("Failed to deserialize toml")
}
fn get_config_dir() -> PathBuf {
let config_dir = std::env::var("PINNACLE_CONFIG_DIR")
.ok()
.and_then(|s| Some(PathBuf::from(shellexpand::full(&s).ok()?.to_string())));
config_dir.unwrap_or(crate::XDG_BASE_DIRS.get_config_home())
}
pub fn start_config(tx_channel: Sender<api::msg::Msg>) -> anyhow::Result<ConfigReturn> {
let config_dir = get_config_dir();
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 {
// 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")?;
std::env::set_var("PINNACLE_DIR", std::env::current_dir()?);
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);
// Using async_process's Child instead of std::process because I don't have to spawn my own
// thread to wait for the child
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())
.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) -> anyhow::Result<()> {
tracing::info!("Restarting config");
tracing::debug!("Clearing tags");
for output in self.space.outputs() {
output.with_state(|state| state.tags.clear());
}
TagId::reset();
tracing::debug!("Clearing mouse and keybinds");
self.input_state.keybinds.clear();
self.input_state.mousebinds.clear();
self.window_rules.clear();
tracing::debug!("Killing old config");
if let Err(err) = self.api_state.config_process.kill() {
tracing::warn!("Error when killing old config: {err}");
}
self.loop_handle.remove(self.api_state.socket_token);
let ConfigReturn {
reload_keybind,
kill_keybind,
config_child_handle,
socket_source,
} = start_config(self.api_state.tx_channel.clone())?;
let socket_token = self
.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");
}
})?;
self.input_state.reload_keybind = reload_keybind;
self.input_state.kill_keybind = kill_keybind;
self.api_state.config_process = config_child_handle;
self.api_state.socket_token = socket_token;
Ok(())
}
}

View file

@ -5,29 +5,25 @@ mod api_handlers;
use std::{
cell::RefCell,
os::{fd::AsRawFd, unix::net::UnixStream},
path::{Path, PathBuf},
sync::{Arc, Mutex},
time::Duration,
};
use crate::{
backend::{udev::Udev, winit::Winit, BackendData},
config::api::{
msg::{
config::{
api::msg::{
window_rules::{WindowRule, WindowRuleCondition},
CallbackId, ModifierMask, Msg,
CallbackId, Msg,
},
PinnacleSocketSource,
ConfigReturn,
},
config::Metaconfig,
cursor::Cursor,
focus::FocusState,
grab::resize_grab::ResizeSurfaceState,
tag::TagId,
window::WindowElement,
};
use anyhow::Context;
use calloop::futures::Scheduler;
use calloop::{channel::Sender, futures::Scheduler, RegistrationToken};
use smithay::{
backend::renderer::element::RenderElementStates,
desktop::{
@ -137,7 +133,6 @@ pub struct State {
pub window_rules: Vec<(WindowRuleCondition, WindowRule)>,
pub async_scheduler: Scheduler<()>,
pub config_process: async_process::Child,
// TODO: move into own struct
// | basically just clean this mess up
@ -203,39 +198,14 @@ impl State {
let (tx_channel, rx_channel) = calloop::channel::channel::<Msg>();
let config_dir = get_config_dir();
tracing::debug!("config dir is {:?}", config_dir);
let metaconfig = crate::config::parse(&config_dir)?;
// If a socket is provided in the metaconfig, use it.
let socket_dir = if let Some(socket_dir) = &metaconfig.socket_dir {
// 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 ConfigReturn {
reload_keybind,
kill_keybind,
config_child_handle,
} = start_config(metaconfig, &config_dir)?;
socket_source,
} = crate::config::start_config(tx_channel.clone())?;
let insert_ret = loop_handle.insert_source(socket_source, |stream, _, data| {
let socket_token_ret = loop_handle.insert_source(socket_source, |stream, _, data| {
if let Some(old_stream) = data
.state
.api_state
@ -250,9 +220,12 @@ impl State {
}
});
if let Err(err) = insert_ret {
anyhow::bail!("Failed to insert socket source into event loop: {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::<()>().expect("Couldn't create executor");
@ -265,7 +238,7 @@ impl State {
let mut seat = seat_state.new_wl_seat(&display_handle, backend.seat_name());
seat.add_pointer();
seat.add_keyboard(XkbConfig::default(), 200, 25)?;
seat.add_keyboard(XkbConfig::default(), 500, 25)?;
loop_handle.insert_idle(|data| {
data.state
@ -277,7 +250,6 @@ impl State {
.expect("failed to insert rx_channel into loop");
});
tracing::debug!("before xwayland");
let xwayland = {
let (xwayland, channel) = XWayland::new(&display_handle);
let clone = display_handle.clone();
@ -289,7 +261,6 @@ impl State {
client_fd: _,
display,
} => {
tracing::debug!("XWaylandEvent ready");
let mut wm = X11Wm::start_wm(
data.state.loop_handle.clone(),
clone.clone(),
@ -297,6 +268,7 @@ impl State {
client,
)
.expect("failed to attach x11wm");
let cursor = Cursor::load();
let image = cursor.get_image(1, Duration::ZERO);
wm.set_cursor(
@ -305,7 +277,9 @@ impl State {
Point::from((image.xhot as u16, image.yhot as u16)),
)
.expect("failed to set xwayland default cursor");
tracing::debug!("setting xwm and xdisplay");
data.state.xwm = Some(wm);
data.state.xdisplay = Some(display);
}
@ -318,7 +292,7 @@ impl State {
}
xwayland
};
tracing::debug!("after xwayland");
tracing::debug!("xwayland set up");
Ok(Self {
backend,
@ -343,7 +317,12 @@ impl State {
layer_shell_state: WlrLayerShellState::new::<Self>(&display_handle),
input_state: InputState::new(reload_keybind, kill_keybind),
api_state: ApiState::new(),
api_state: ApiState {
stream: None,
socket_token,
tx_channel,
config_process: config_child_handle,
},
focus_state: FocusState::new(),
seat,
@ -356,7 +335,6 @@ impl State {
popup_manager: PopupManager::default(),
async_scheduler: sched,
config_process: config_child_handle,
windows: vec![],
window_rules: vec![],
@ -399,120 +377,6 @@ impl State {
}
}
fn get_config_dir() -> PathBuf {
let config_dir = std::env::var("PINNACLE_CONFIG_DIR")
.ok()
.and_then(|s| Some(PathBuf::from(shellexpand::full(&s).ok()?.to_string())));
config_dir.unwrap_or(crate::XDG_BASE_DIRS.get_config_home())
}
/// This should be called *after* you have created the [`PinnacleSocketSource`] to ensure
/// PINNACLE_SOCKET is set correctly for use in API implementations.
fn start_config(metaconfig: Metaconfig, config_dir: &Path) -> anyhow::Result<ConfigReturn> {
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")?;
std::env::set_var("PINNACLE_DIR", std::env::current_dir()?);
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);
// Using async_process's Child instead of std::process because I don't have to spawn my own
// thread to wait for the child
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())
.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,
})
}
struct ConfigReturn {
reload_keybind: (ModifierMask, u32),
kill_keybind: (ModifierMask, u32),
config_child_handle: async_process::Child,
}
impl State {
pub fn restart_config(&mut self) -> anyhow::Result<()> {
tracing::info!("Restarting config");
tracing::debug!("Clearing tags");
for output in self.space.outputs() {
output.with_state(|state| state.tags.clear());
}
TagId::reset();
tracing::debug!("Clearing mouse- and keybinds");
self.input_state.keybinds.clear();
self.input_state.mousebinds.clear();
self.window_rules.clear();
tracing::debug!("Killing old config");
if let Err(err) = self.config_process.kill() {
tracing::warn!("Error when killing old config: {err}");
}
let config_dir = get_config_dir();
let metaconfig =
crate::config::parse(&config_dir).context("Failed to parse metaconfig.toml")?;
let ConfigReturn {
reload_keybind,
kill_keybind,
config_child_handle,
} = start_config(metaconfig, &config_dir)?;
self.input_state.reload_keybind = reload_keybind;
self.input_state.kill_keybind = kill_keybind;
self.config_process = config_child_handle;
Ok(())
}
}
pub struct CalloopData {
pub display: Display<State>,
pub state: State,
@ -569,17 +433,15 @@ pub fn take_presentation_feedback(
output_presentation_feedback
}
/// State containing the config API's stream.
#[derive(Default)]
pub struct ApiState {
// TODO: this may not need to be in an arc mutex because of the move to async
/// The stream API messages are being sent through
pub stream: Option<Arc<Mutex<UnixStream>>>,
}
impl ApiState {
pub fn new() -> Self {
Default::default()
}
/// A token used to remove the socket source from the event loop on config restart
pub socket_token: RegistrationToken,
/// The sending channel used to send API messages from the socket source to a handler
pub tx_channel: Sender<Msg>,
pub config_process: async_process::Child,
}
pub trait WithState {