From c83f136cf7d9b697383c4c2dd155bc1d3b0b99a0 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Wed, 20 Sep 2023 16:43:50 -0500 Subject: [PATCH] Move config handling out of state.rs --- src/backend/udev.rs | 2 +- src/backend/winit.rs | 2 +- src/config.rs | 172 ++++++++++++++++++++++++++++++++++++- src/state.rs | 200 +++++++------------------------------------ 4 files changed, 203 insertions(+), 173 deletions(-) diff --git a/src/backend/udev.rs b/src/backend/udev.rs index 7f4a06c..413116e 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -1490,7 +1490,7 @@ fn render_surface<'a>( .collect::>(); 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()) diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 94be3c2..412c296 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -252,7 +252,7 @@ pub fn run_winit() -> anyhow::Result<()> { .collect::>(); 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, diff --git a/src/config.rs b/src/config.rs index c3d727a..49a2ec0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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 { +fn parse(config_dir: &Path) -> anyhow::Result { let config_dir = config_dir.join("metaconfig.toml"); let metaconfig = @@ -105,3 +115,161 @@ pub fn parse(config_dir: &Path) -> anyhow::Result { 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) -> anyhow::Result { + 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::>(); + + 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(()) + } +} diff --git a/src/state.rs b/src/state.rs index ff2a622..0e01bd3 100644 --- a/src/state.rs +++ b/src/state.rs @@ -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::(); - 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::(&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 { - 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::>(); - - 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, 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>>, -} - -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, + pub config_process: async_process::Child, } pub trait WithState {