mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
Move config handling out of state.rs
This commit is contained in:
parent
5c88ceac83
commit
c83f136cf7
4 changed files with 203 additions and 173 deletions
|
@ -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())
|
||||
|
|
|
@ -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,
|
||||
|
|
172
src/config.rs
172
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<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(())
|
||||
}
|
||||
}
|
||||
|
|
200
src/state.rs
200
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::<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 {
|
||||
|
|
Loading…
Reference in a new issue