pinnacle/src/state.rs

613 lines
20 KiB
Rust
Raw Normal View History

2023-08-01 11:06:35 -05:00
// SPDX-License-Identifier: GPL-3.0-or-later
2023-06-25 17:18:50 -05:00
2023-08-14 20:43:35 -05:00
mod api_handlers;
2023-06-17 18:55:04 -05:00
use std::{
cell::RefCell,
2023-06-17 18:55:04 -05:00
os::{fd::AsRawFd, unix::net::UnixStream},
2023-08-16 10:34:50 -05:00
path::{Path, PathBuf},
sync::{Arc, Mutex},
time::Duration,
2023-06-17 18:55:04 -05:00
};
2023-06-17 21:02:58 -05:00
use crate::{
2023-06-21 14:48:38 -05:00
api::{
2023-09-05 20:45:29 -05:00
msg::{
window_rules::{WindowRule, WindowRuleCondition},
CallbackId, ModifierMask, Msg,
},
2023-08-16 10:38:21 -05:00
PinnacleSocketSource, DEFAULT_SOCKET_DIR,
2023-06-21 14:48:38 -05:00
},
2023-08-28 22:53:24 -05:00
backend::{udev::Udev, winit::Winit, BackendData},
cursor::Cursor,
2023-06-17 21:02:58 -05:00
focus::FocusState,
grab::resize_grab::ResizeSurfaceState,
2023-08-16 10:34:50 -05:00
metaconfig::Metaconfig,
2023-08-15 17:26:17 -05:00
tag::TagId,
2023-09-08 19:56:40 -05:00
window::WindowElement,
2023-06-17 21:02:58 -05:00
};
2023-08-16 11:28:35 -05:00
use anyhow::Context;
use calloop::futures::Scheduler;
2023-06-02 16:01:48 -05:00
use smithay::{
2023-06-09 20:29:17 -05:00
backend::renderer::element::RenderElementStates,
desktop::{
utils::{
surface_presentation_feedback_flags_from_states, surface_primary_scanout_output,
OutputPresentationFeedback,
},
2023-07-24 18:59:05 -05:00
PopupManager, Space,
2023-06-09 20:29:17 -05:00
},
input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState},
output::Output,
2023-06-02 16:01:48 -05:00
reexports::{
2023-06-17 18:55:04 -05:00
calloop::{
self, channel::Event, generic::Generic, Interest, LoopHandle, LoopSignal, Mode,
PostAction,
},
2023-06-02 16:01:48 -05:00
wayland_server::{
backend::{ClientData, ClientId, DisconnectReason},
protocol::wl_surface::WlSurface,
Display, DisplayHandle,
2023-06-02 16:01:48 -05:00
},
},
2023-09-08 19:56:40 -05:00
utils::{Clock, Logical, Monotonic, Point, Size},
2023-06-02 16:01:48 -05:00
wayland::{
compositor::{self, CompositorClientState, CompositorState},
2023-06-02 16:01:48 -05:00
data_device::DataDeviceState,
2023-06-09 20:29:17 -05:00
dmabuf::DmabufFeedback,
fractional_scale::FractionalScaleManagerState,
2023-06-02 16:01:48 -05:00
output::OutputManagerState,
primary_selection::PrimarySelectionState,
2023-08-14 20:43:35 -05:00
shell::{wlr_layer::WlrLayerShellState, xdg::XdgShellState},
2023-06-02 16:01:48 -05:00
shm::ShmState,
2023-06-09 20:29:17 -05:00
socket::ListeningSocketSource,
viewporter::ViewporterState,
2023-06-02 16:01:48 -05:00
},
xwayland::{X11Wm, XWayland, XWaylandEvent},
2023-06-02 16:01:48 -05:00
};
2023-08-28 22:53:24 -05:00
use crate::input::InputState;
pub enum Backend {
Winit(Winit),
Udev(Udev),
}
impl Backend {
pub fn seat_name(&self) -> String {
match self {
Backend::Winit(winit) => winit.seat_name(),
Backend::Udev(udev) => udev.seat_name(),
}
}
pub fn early_import(&mut self, surface: &WlSurface) {
match self {
Backend::Winit(winit) => winit.early_import(surface),
Backend::Udev(udev) => udev.early_import(surface),
}
}
}
2023-06-02 16:01:48 -05:00
2023-06-21 18:58:49 -05:00
/// The main state of the application.
2023-08-28 22:53:24 -05:00
pub struct State {
pub backend: Backend,
2023-06-02 16:01:48 -05:00
pub loop_signal: LoopSignal,
2023-08-28 22:53:24 -05:00
pub loop_handle: LoopHandle<'static, CalloopData>,
pub display_handle: DisplayHandle,
2023-06-02 16:01:48 -05:00
pub clock: Clock<Monotonic>,
2023-07-24 18:59:05 -05:00
pub space: Space<WindowElement>,
pub move_mode: bool,
2023-06-09 20:29:17 -05:00
pub socket_name: String,
2023-08-28 22:53:24 -05:00
pub seat: Seat<State>,
2023-06-02 16:01:48 -05:00
pub compositor_state: CompositorState,
pub data_device_state: DataDeviceState,
pub seat_state: SeatState<Self>,
pub shm_state: ShmState,
pub output_manager_state: OutputManagerState,
pub xdg_shell_state: XdgShellState,
pub viewporter_state: ViewporterState,
pub fractional_scale_manager_state: FractionalScaleManagerState,
pub primary_selection_state: PrimarySelectionState,
2023-08-04 18:48:10 -05:00
pub layer_shell_state: WlrLayerShellState,
pub input_state: InputState,
2023-06-17 18:55:04 -05:00
pub api_state: ApiState,
2023-06-17 21:02:58 -05:00
pub focus_state: FocusState,
2023-06-02 16:01:48 -05:00
pub popup_manager: PopupManager,
pub cursor_status: CursorImageStatus,
pub pointer_location: Point<f64, Logical>,
2023-08-02 18:18:51 -05:00
pub dnd_icon: Option<WlSurface>,
2023-07-24 18:59:05 -05:00
pub windows: Vec<WindowElement>,
2023-09-05 22:13:43 -05:00
pub window_rules: Vec<(WindowRuleCondition, WindowRule)>,
pub async_scheduler: Scheduler<()>,
2023-08-15 17:26:17 -05:00
pub config_process: async_process::Child,
2023-07-11 11:59:38 -05:00
// TODO: move into own struct
// | basically just clean this mess up
pub output_callback_ids: Vec<CallbackId>,
pub xwayland: XWayland,
pub xwm: Option<X11Wm>,
pub xdisplay: Option<u32>,
2023-07-02 18:15:44 -05:00
}
2023-08-28 22:53:24 -05:00
impl State {
2023-07-02 18:15:44 -05:00
pub fn init(
2023-08-28 22:53:24 -05:00
backend: Backend,
2023-07-02 18:15:44 -05:00
display: &mut Display<Self>,
loop_signal: LoopSignal,
2023-08-28 22:53:24 -05:00
loop_handle: LoopHandle<'static, CalloopData>,
2023-08-16 11:28:35 -05:00
) -> anyhow::Result<Self> {
2023-07-02 18:15:44 -05:00
let socket = ListeningSocketSource::new_auto()?;
let socket_name = socket.socket_name().to_os_string();
std::env::set_var("WAYLAND_DISPLAY", socket_name.clone());
2023-08-06 19:41:48 -05:00
tracing::info!(
"Set WAYLAND_DISPLAY to {}",
socket_name.clone().to_string_lossy()
);
2023-07-02 18:15:44 -05:00
// Opening a new process will use up a few file descriptors, around 10 for Alacritty, for
// example. Because of this, opening up only around 100 processes would exhaust the file
// descriptor limit on my system (Arch btw) and cause a "Too many open files" crash.
//
// To fix this, I just set the limit to be higher. As Pinnacle is the whole graphical
// environment, I *think* this is ok.
2023-08-06 19:41:48 -05:00
tracing::info!("Trying to raise file descriptor limit...");
2023-07-02 18:15:44 -05:00
if let Err(err) = smithay::reexports::nix::sys::resource::setrlimit(
smithay::reexports::nix::sys::resource::Resource::RLIMIT_NOFILE,
65536,
65536 * 2,
) {
tracing::error!("Could not raise fd limit: errno {err}");
2023-08-06 19:41:48 -05:00
} else {
tracing::info!("Fd raise success!");
2023-07-02 18:15:44 -05:00
}
loop_handle.insert_source(socket, |stream, _metadata, data| {
data.display
.handle()
.insert_client(stream, Arc::new(ClientState::default()))
.expect("Could not insert client into loop handle");
})?;
loop_handle.insert_source(
Generic::new(
display.backend().poll_fd().as_raw_fd(),
Interest::READ,
Mode::Level,
),
|_readiness, _metadata, data| {
data.display.dispatch_clients(&mut data.state)?;
Ok(PostAction::Continue)
},
)?;
let (tx_channel, rx_channel) = calloop::channel::channel::<Msg>();
2023-08-16 10:34:50 -05:00
let config_dir = get_config_dir();
2023-08-06 19:41:48 -05:00
2023-08-16 10:34:50 -05:00
let metaconfig = crate::metaconfig::parse(&config_dir)?;
let socket_dir = {
2023-08-16 10:38:21 -05:00
let dir_string = shellexpand::full(
metaconfig
.socket_dir
.as_deref()
.unwrap_or(DEFAULT_SOCKET_DIR),
)?
.to_string();
2023-08-16 10:34:50 -05:00
// 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 pathbuf = PathBuf::from(&dir_string).canonicalize()?;
std::env::set_current_dir(current_dir)?;
pathbuf
};
2023-08-16 11:28:35 -05:00
let socket_source = PinnacleSocketSource::new(tx_channel, &socket_dir)
.context("Failed to create socket source")?;
2023-08-06 19:41:48 -05:00
2023-08-16 10:34:50 -05:00
let ConfigReturn {
reload_keybind,
kill_keybind,
config_child_handle,
} = start_config(metaconfig, &config_dir)?;
2023-08-16 11:28:35 -05:00
let insert_ret = loop_handle.insert_source(socket_source, |stream, _, data| {
2023-07-02 18:15:44 -05:00
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");
}
2023-08-16 11:28:35 -05:00
});
if let Err(err) = insert_ret {
anyhow::bail!("Failed to insert socket source into event loop: {err}");
}
2023-07-02 18:15:44 -05:00
let (executor, sched) =
calloop::futures::executor::<()>().expect("Couldn't create executor");
2023-08-16 11:28:35 -05:00
if let Err(err) = loop_handle.insert_source(executor, |_, _, _| {}) {
anyhow::bail!("Failed to insert async executor into event loop: {err}");
}
2023-07-02 18:15:44 -05:00
let display_handle = display.handle();
let mut seat_state = SeatState::new();
2023-08-28 22:53:24 -05:00
let mut seat = seat_state.new_wl_seat(&display_handle, backend.seat_name());
2023-07-02 18:15:44 -05:00
seat.add_pointer();
seat.add_keyboard(XkbConfig::default(), 200, 25)?;
loop_handle.insert_idle(|data| {
data.state
.loop_handle
.insert_source(rx_channel, |msg, _, data| match msg {
Event::Msg(msg) => data.state.handle_msg(msg),
Event::Closed => todo!(),
})
.expect("failed to insert rx_channel into loop");
});
2023-07-24 18:59:05 -05:00
tracing::debug!("before xwayland");
let xwayland = {
let (xwayland, channel) = XWayland::new(&display_handle);
let clone = display_handle.clone();
2023-07-24 18:59:05 -05:00
tracing::debug!("inserting into loop");
let res = loop_handle.insert_source(channel, move |event, _, data| match event {
XWaylandEvent::Ready {
connection,
client,
client_fd: _,
display,
} => {
tracing::debug!("XWaylandEvent ready");
let mut wm = X11Wm::start_wm(
data.state.loop_handle.clone(),
clone.clone(),
connection,
client,
2023-07-24 18:59:05 -05:00
)
.expect("failed to attach x11wm");
let cursor = Cursor::load();
let image = cursor.get_image(1, Duration::ZERO);
wm.set_cursor(
&image.pixels_rgba,
Size::from((image.width as u16, image.height as u16)),
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);
}
XWaylandEvent::Exited => {
data.state.xwm.take();
}
});
if let Err(err) = res {
tracing::error!("Failed to insert XWayland source into loop: {err}");
}
xwayland
};
2023-07-24 18:59:05 -05:00
tracing::debug!("after xwayland");
2023-07-02 18:15:44 -05:00
Ok(Self {
2023-08-28 22:53:24 -05:00
backend,
2023-07-02 18:15:44 -05:00
loop_signal,
loop_handle,
display_handle: display_handle.clone(),
2023-07-02 18:15:44 -05:00
clock: Clock::<Monotonic>::new()?,
compositor_state: CompositorState::new::<Self>(&display_handle),
data_device_state: DataDeviceState::new::<Self>(&display_handle),
seat_state,
pointer_location: (0.0, 0.0).into(),
shm_state: ShmState::new::<Self>(&display_handle, vec![]),
2023-07-24 18:59:05 -05:00
space: Space::<WindowElement>::default(),
2023-07-02 18:15:44 -05:00
cursor_status: CursorImageStatus::Default,
output_manager_state: OutputManagerState::new_with_xdg_output::<Self>(&display_handle),
xdg_shell_state: XdgShellState::new::<Self>(&display_handle),
viewporter_state: ViewporterState::new::<Self>(&display_handle),
fractional_scale_manager_state: FractionalScaleManagerState::new::<Self>(
&display_handle,
),
primary_selection_state: PrimarySelectionState::new::<Self>(&display_handle),
2023-08-04 18:48:10 -05:00
layer_shell_state: WlrLayerShellState::new::<Self>(&display_handle),
2023-08-15 17:26:17 -05:00
input_state: InputState::new(reload_keybind, kill_keybind),
2023-07-02 18:15:44 -05:00
api_state: ApiState::new(),
focus_state: FocusState::new(),
2023-07-02 18:15:44 -05:00
seat,
2023-08-02 18:18:51 -05:00
dnd_icon: None,
2023-07-02 18:15:44 -05:00
move_mode: false,
socket_name: socket_name.to_string_lossy().to_string(),
popup_manager: PopupManager::default(),
async_scheduler: sched,
2023-08-15 17:26:17 -05:00
config_process: config_child_handle,
2023-07-02 18:15:44 -05:00
windows: vec![],
2023-09-05 20:45:29 -05:00
window_rules: vec![],
2023-07-11 11:59:38 -05:00
output_callback_ids: vec![],
xwayland,
xwm: None,
xdisplay: None,
2023-07-02 18:15:44 -05:00
})
}
2023-08-31 20:35:54 -05:00
/// Schedule `run` to run when `condition` returns true.
///
/// This will continually reschedule `run` in the event loop if `condition` returns false.
pub fn schedule<F1, F2>(&self, condition: F1, run: F2)
where
F1: Fn(&mut CalloopData) -> bool + 'static,
F2: FnOnce(&mut CalloopData) + 'static,
{
self.loop_handle.insert_idle(|data| {
Self::schedule_inner(data, condition, run);
});
}
2023-09-05 20:45:29 -05:00
/// Schedule something to be done when `condition` returns true.
2023-08-31 20:35:54 -05:00
fn schedule_inner<F1, F2>(data: &mut CalloopData, condition: F1, run: F2)
where
F1: Fn(&mut CalloopData) -> bool + 'static,
F2: FnOnce(&mut CalloopData) + 'static,
{
if !condition(data) {
data.state.loop_handle.insert_idle(|data| {
Self::schedule_inner(data, condition, run);
});
return;
}
run(data);
}
2023-06-05 21:08:37 -05:00
}
2023-08-16 10:34:50 -05:00
fn get_config_dir() -> PathBuf {
let config_dir = std::env::var("PINNACLE_CONFIG_DIR").unwrap_or_else(|_| {
let default_config_dir =
std::env::var("XDG_CONFIG_HOME").unwrap_or("~/.config".to_string());
PathBuf::from(default_config_dir)
.join("pinnacle")
.to_string_lossy()
.to_string()
});
2023-09-05 20:45:29 -05:00
2023-08-16 10:34:50 -05:00
PathBuf::from(shellexpand::tilde(&config_dir).to_string())
}
/// This should be called *after* you have created the [`PinnacleSocketSource`] to ensure
/// PINNACLE_SOCKET is set correctly for use in API implementations.
2023-08-16 11:28:35 -05:00
fn start_config(metaconfig: Metaconfig, config_dir: &Path) -> anyhow::Result<ConfigReturn> {
2023-08-15 17:26:17 -05:00
let reload_keybind = metaconfig.reload_keybind;
let kill_keybind = metaconfig.kill_keybind;
let mut command = metaconfig.command.split(' ');
let arg1 = command.next().expect("empty command");
std::env::set_var("PINNACLE_DIR", std::env::current_dir()?);
2023-08-15 17:26:17 -05:00
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(),
2023-09-05 20:45:29 -05:00
// Expand nonexistent vars to an empty string instead of crashing
2023-08-15 17:26:17 -05:00
|var| Ok::<_, ()>(Some(std::env::var(var).unwrap_or("".to_string()))),
)
.ok()?
.to_string(),
))
} else {
None
}
})
.collect::<Vec<_>>();
2023-08-14 20:19:38 -05:00
2023-08-15 17:26:17 -05:00
tracing::debug!("Config envs are {:?}", envs);
2023-08-14 20:19:38 -05:00
2023-09-05 20:45:29 -05:00
// Using async_process's Child instead of std::process because I don't have to spawn my own
// thread to wait for the child
2023-08-15 17:26:17 -05:00
let child = async_process::Command::new(arg1)
.args(command)
.envs(envs)
2023-08-16 10:38:21 -05:00
.current_dir(config_dir)
2023-08-15 17:26:17 -05:00
.spawn()
.expect("failed to spawn config");
2023-08-14 20:19:38 -05:00
2023-08-15 17:26:17 -05:00
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,
}
2023-08-28 22:53:24 -05:00
impl State {
2023-08-16 11:28:35 -05:00
pub fn restart_config(&mut self) -> anyhow::Result<()> {
2023-08-15 17:26:17 -05:00
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();
2023-09-05 20:45:29 -05:00
self.window_rules.clear();
2023-08-15 17:26:17 -05:00
tracing::debug!("Killing old config");
if let Err(err) = self.config_process.kill() {
tracing::warn!("Error when killing old config: {err}");
}
2023-08-16 10:34:50 -05:00
let config_dir = get_config_dir();
2023-08-16 11:28:35 -05:00
let metaconfig =
crate::metaconfig::parse(&config_dir).context("Failed to parse metaconfig.toml")?;
2023-08-16 10:34:50 -05:00
2023-08-15 17:26:17 -05:00
let ConfigReturn {
reload_keybind,
kill_keybind,
config_child_handle,
2023-08-16 10:34:50 -05:00
} = start_config(metaconfig, &config_dir)?;
2023-08-15 17:26:17 -05:00
self.input_state.reload_keybind = reload_keybind;
self.input_state.kill_keybind = kill_keybind;
self.config_process = config_child_handle;
Ok(())
}
2023-08-14 20:19:38 -05:00
}
2023-08-28 22:53:24 -05:00
pub struct CalloopData {
pub display: Display<State>,
pub state: State,
2023-06-02 16:01:48 -05:00
}
#[derive(Default)]
pub struct ClientState {
pub compositor_state: CompositorClientState,
}
2023-09-05 20:45:29 -05:00
2023-06-02 16:01:48 -05:00
impl ClientData for ClientState {
fn initialized(&self, _client_id: ClientId) {}
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
}
2023-06-09 20:29:17 -05:00
#[derive(Debug, Copy, Clone)]
pub struct SurfaceDmabufFeedback<'a> {
pub render_feedback: &'a DmabufFeedback,
pub scanout_feedback: &'a DmabufFeedback,
}
2023-06-21 19:08:29 -05:00
// TODO: docs
2023-06-09 20:29:17 -05:00
pub fn take_presentation_feedback(
output: &Output,
2023-07-24 18:59:05 -05:00
space: &Space<WindowElement>,
2023-06-09 20:29:17 -05:00
render_element_states: &RenderElementStates,
) -> OutputPresentationFeedback {
let mut output_presentation_feedback = OutputPresentationFeedback::new(output);
space.elements().for_each(|window| {
if space.outputs_for_element(window).contains(output) {
window.take_presentation_feedback(
&mut output_presentation_feedback,
surface_primary_scanout_output,
|surface, _| {
surface_presentation_feedback_flags_from_states(surface, render_element_states)
},
);
}
});
2023-08-08 14:28:09 -05:00
let map = smithay::desktop::layer_map_for_output(output);
for layer_surface in map.layers() {
layer_surface.take_presentation_feedback(
&mut output_presentation_feedback,
surface_primary_scanout_output,
|surface, _| {
surface_presentation_feedback_flags_from_states(surface, render_element_states)
},
);
}
2023-06-09 20:29:17 -05:00
output_presentation_feedback
}
2023-06-17 18:55:04 -05:00
2023-06-21 19:08:29 -05:00
/// State containing the config API's stream.
2023-06-17 21:02:58 -05:00
#[derive(Default)]
2023-06-17 18:55:04 -05:00
pub struct ApiState {
2023-07-11 11:59:38 -05:00
// TODO: this may not need to be in an arc mutex because of the move to async
2023-06-21 14:48:38 -05:00
pub stream: Option<Arc<Mutex<UnixStream>>>,
2023-06-17 18:55:04 -05:00
}
2023-06-17 21:02:58 -05:00
impl ApiState {
pub fn new() -> Self {
Default::default()
}
}
pub trait WithState {
2023-09-05 20:45:29 -05:00
type State;
/// Access data map state.
///
/// RefCell Safety: This function will panic if called within itself.
fn with_state<F, T>(&self, func: F) -> T
where
F: FnMut(&mut Self::State) -> T;
}
#[derive(Default, Debug)]
pub struct WlSurfaceState {
pub resize_state: ResizeSurfaceState,
}
impl WithState for WlSurface {
type State = WlSurfaceState;
fn with_state<F, T>(&self, mut func: F) -> T
where
F: FnMut(&mut Self::State) -> T,
{
compositor::with_states(self, |states| {
states
.data_map
.insert_if_missing(RefCell::<Self::State>::default);
let state = states
.data_map
.get::<RefCell<Self::State>>()
.expect("This should never happen");
func(&mut state.borrow_mut())
})
}
}