diff --git a/src/backend/udev.rs b/src/backend/udev.rs index 44f9cb2..75faa4e 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -225,7 +225,13 @@ pub fn run_udev() -> Result<(), Box> { pointer_images: Vec::new(), pointer_element: PointerElement::default(), }; - let mut state = State::init( + + // + // + // + // + + let mut state = State::::init( data, &mut display, event_loop.get_signal(), @@ -236,6 +242,39 @@ pub fn run_udev() -> Result<(), Box> { * Initialize the udev backend */ let udev_backend = UdevBackend::new(state.seat.name())?; + + for (device_id, path) in udev_backend.device_list() { + if let Err(err) = DrmNode::from_dev_id(device_id) + .map_err(DeviceAddError::DrmNode) + .and_then(|node| state.device_added(node, path)) + { + tracing::error!("Skipping device {device_id}: {err}"); + } + } + event_loop + .handle() + .insert_source(udev_backend, move |event, _, data| match event { + UdevEvent::Added { device_id, path } => { + if let Err(err) = DrmNode::from_dev_id(device_id) + .map_err(DeviceAddError::DrmNode) + .and_then(|node| data.state.device_added(node, &path)) + { + tracing::error!("Skipping device {device_id}: {err}"); + } + } + UdevEvent::Changed { device_id } => { + if let Ok(node) = DrmNode::from_dev_id(device_id) { + data.state.device_changed(node) + } + } + UdevEvent::Removed { device_id } => { + if let Ok(node) = DrmNode::from_dev_id(device_id) { + data.state.device_removed(node) + } + } + }) + .unwrap(); + /* * Initialize libinput backend */ @@ -299,14 +338,6 @@ pub fn run_udev() -> Result<(), Box> { }) .unwrap(); - for (device_id, path) in udev_backend.device_list() { - if let Err(err) = DrmNode::from_dev_id(device_id) - .map_err(DeviceAddError::DrmNode) - .and_then(|node| state.device_added(node, path)) - { - tracing::error!("Skipping device {device_id}: {err}"); - } - } state.shm_state.update_formats( state .backend_data @@ -422,30 +453,6 @@ pub fn run_udev() -> Result<(), Box> { }); }); - event_loop - .handle() - .insert_source(udev_backend, move |event, _, data| match event { - UdevEvent::Added { device_id, path } => { - if let Err(err) = DrmNode::from_dev_id(device_id) - .map_err(DeviceAddError::DrmNode) - .and_then(|node| data.state.device_added(node, &path)) - { - tracing::error!("Skipping device {device_id}: {err}"); - } - } - UdevEvent::Changed { device_id } => { - if let Ok(node) = DrmNode::from_dev_id(device_id) { - data.state.device_changed(node) - } - } - UdevEvent::Removed { device_id } => { - if let Ok(node) = DrmNode::from_dev_id(device_id) { - data.state.device_removed(node) - } - } - }) - .unwrap(); - event_loop.run( Some(Duration::from_millis(6)), &mut CalloopData { state, display }, @@ -829,6 +836,8 @@ impl State { ); let global = output.create_global::>(&self.backend_data.display_handle); + self.focus_state.focused_output = Some(output.clone()); + let x = self.space.outputs().fold(0, |acc, o| { acc + self.space.output_geometry(o).unwrap().size.w }); diff --git a/src/backend/winit.rs b/src/backend/winit.rs index b9ddc43..89c7ed9 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -177,7 +177,7 @@ pub fn run_winit() -> Result<(), Box> { } }; - let mut state = State::init( + let mut state = State::::init( WinitData { backend: winit_backend, damage_tracker: OutputDamageTracker::from_output(&output), diff --git a/src/input.rs b/src/input.rs index 32f64fd..82b7e6e 100644 --- a/src/input.rs +++ b/src/input.rs @@ -245,6 +245,12 @@ impl State { .get(&(modifier_mask.into(), raw_sym)) { return FilterResult::Intercept(*callback_id); + } else if modifiers.ctrl + && modifiers.shift + && modifiers.alt + && keysym.modified_sym() == keysyms::KEY_Escape + { + return FilterResult::Intercept(CallbackId(999999)); } } @@ -267,6 +273,9 @@ impl State { self.move_mode = move_mode; if let Some(callback_id) = action { + if callback_id.0 == 999999 { + self.loop_signal.stop(); + } if let Some(stream) = self.api_state.stream.as_ref() { if let Err(err) = crate::api::send_to_client( &mut stream.lock().expect("Could not lock stream mutex"), diff --git a/src/state.rs b/src/state.rs index 303cec5..a65498b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,8 +8,9 @@ use std::{ error::Error, ffi::OsString, os::{fd::AsRawFd, unix::net::UnixStream}, + path::Path, process::Stdio, - sync::{Arc, Mutex}, path::Path, + sync::{Arc, Mutex}, }; use crate::{ @@ -17,8 +18,12 @@ use crate::{ msg::{Args, CallbackId, Msg, OutgoingMsg, Request, RequestResponse}, PinnacleSocketSource, }, + backend::{udev::UdevData, winit::WinitData}, focus::FocusState, - window::{window_state::WindowState, WindowProperties}, output::OutputState, tag::{TagState, Tag}, layout::Layout, + layout::Layout, + output::OutputState, + tag::{Tag, TagState}, + window::{window_state::WindowState, WindowProperties}, }; use calloop::futures::Scheduler; use futures_lite::AsyncBufReadExt; @@ -96,15 +101,426 @@ pub struct State { } impl State { + pub fn handle_msg(&mut self, msg: Msg) { + match msg { + Msg::SetKeybind { + key, + modifiers, + callback_id, + } => { + tracing::info!("set keybind: {:?}, {}", modifiers, key); + self.input_state + .keybinds + .insert((modifiers.into(), key), callback_id); + } + Msg::SetMousebind { button } => todo!(), + Msg::CloseWindow { client_id } => { + // TODO: client_id + tracing::info!("CloseWindow {:?}", client_id); + if let Some(window) = self.focus_state.current_focus() { + window.toplevel().send_close(); + } + } + Msg::ToggleFloating { client_id } => { + // TODO: add client_ids + if let Some(window) = self.focus_state.current_focus() { + crate::window::toggle_floating(self, &window); + } + } + + Msg::Spawn { + command, + callback_id, + } => { + self.handle_spawn(command, callback_id); + } + + Msg::SetWindowSize { window_id, size } => { + let Some(window) = self.space.elements().find(|&win| { + WindowState::with_state(win, |state| state.id == window_id) + }) else { return; }; + + // TODO: tiled vs floating + window.toplevel().with_pending_state(|state| { + state.size = Some(size.into()); + }); + window.toplevel().send_pending_configure(); + } + Msg::MoveWindowToTag { window_id, tag_id } => { + if let Some(window) = self + .windows + .iter() + .find(|&win| WindowState::with_state(win, |state| state.id == window_id)) + { + WindowState::with_state(window, |state| { + state.tags = vec![tag_id.clone()]; + }); + } + + self.re_layout(); + } + Msg::ToggleTagOnWindow { window_id, tag_id } => { + if let Some(window) = self + .windows + .iter() + .find(|&win| WindowState::with_state(win, |state| state.id == window_id)) + { + WindowState::with_state(window, |state| { + if state.tags.contains(&tag_id) { + state.tags.retain(|id| id != &tag_id); + } else { + state.tags.push(tag_id.clone()); + } + }); + + self.re_layout(); + } + } + Msg::ToggleTag { tag_id } => { + OutputState::with( + self.focus_state.focused_output.as_ref().unwrap(), // TODO: handle error + |state| match state.focused_tags.get_mut(&tag_id) { + Some(id) => { + *id = !*id; + tracing::debug!( + "toggled tag {tag_id:?} {}", + if *id { "on" } else { "off" } + ); + } + None => { + state.focused_tags.insert(tag_id.clone(), true); + tracing::debug!("toggled tag {tag_id:?} on"); + } + }, + ); + + self.re_layout(); + } + Msg::SwitchToTag { tag_id } => { + OutputState::with(self.focus_state.focused_output.as_ref().unwrap(), |state| { + for (_, active) in state.focused_tags.iter_mut() { + *active = false; + } + if let Some(active) = state.focused_tags.get_mut(&tag_id) { + *active = true; + } else { + state.focused_tags.insert(tag_id.clone(), true); + } + tracing::debug!("focused tags: {:?}", state.focused_tags); + }); + + self.re_layout(); + } + Msg::AddTags { tags } => { + self.tag_state.tags.extend(tags.into_iter().map(|tag| Tag { + id: tag, + windows: vec![], + })); + } + Msg::RemoveTags { tags } => { + self.tag_state.tags.retain(|tag| !tags.contains(&tag.id)); + } + + Msg::Quit => { + self.loop_signal.stop(); + } + + Msg::Request(request) => match request { + Request::GetWindowByAppId { id, app_id } => todo!(), + Request::GetWindowByTitle { id, title } => todo!(), + Request::GetWindowByFocus { id } => { + let Some(current_focus) = self.focus_state.current_focus() else { return; }; + let (app_id, title) = + compositor::with_states(current_focus.toplevel().wl_surface(), |states| { + let lock = states + .data_map + .get::() + .expect("XdgToplevelSurfaceData doesn't exist") + .lock() + .expect("Couldn't lock XdgToplevelSurfaceData"); + (lock.app_id.clone(), lock.title.clone()) + }); + let (window_id, floating) = WindowState::with_state(¤t_focus, |state| { + (state.id, state.floating.is_floating()) + }); + // TODO: unwrap + let location = self.space.element_location(¤t_focus).unwrap(); + let props = WindowProperties { + id: window_id, + app_id, + title, + size: current_focus.geometry().size.into(), + location: location.into(), + floating, + }; + let stream = self + .api_state + .stream + .as_ref() + .expect("Stream doesn't exist"); + let mut stream = stream.lock().expect("Couldn't lock stream"); + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + request_id: id, + response: RequestResponse::Window { window: props }, + }, + ) + .expect("Send to client failed"); + } + Request::GetAllWindows { id } => { + let window_props = self + .space + .elements() + .map(|win| { + let (app_id, title) = + compositor::with_states(win.toplevel().wl_surface(), |states| { + let lock = states + .data_map + .get::() + .expect("XdgToplevelSurfaceData doesn't exist") + .lock() + .expect("Couldn't lock XdgToplevelSurfaceData"); + (lock.app_id.clone(), lock.title.clone()) + }); + let (window_id, floating) = WindowState::with_state(win, |state| { + (state.id, state.floating.is_floating()) + }); + // TODO: unwrap + let location = self + .space + .element_location(win) + .expect("Window location doesn't exist"); + WindowProperties { + id: window_id, + app_id, + title, + size: win.geometry().size.into(), + location: location.into(), + floating, + } + }) + .collect::>(); + + // FIXME: figure out what to do if error + let stream = self + .api_state + .stream + .as_ref() + .expect("Stream doesn't exist"); + let mut stream = stream.lock().expect("Couldn't lock stream"); + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + request_id: id, + response: RequestResponse::GetAllWindows { + windows: window_props, + }, + }, + ) + .expect("Couldn't send to client"); + } + }, + } + } + + pub fn handle_spawn(&self, command: Vec, callback_id: Option) { + let mut command = command.into_iter(); + let Some(program) = command.next() else { + // TODO: notify that command was nothing + return; + }; + + let program = OsString::from(program); + let Ok(mut child) = async_process::Command::new(&program) + .env("WAYLAND_DISPLAY", self.socket_name.clone()) + .stdin(if callback_id.is_some() { + Stdio::piped() + } else { + // piping to null because foot won't open without a callback_id + // otherwise + Stdio::null() + }) + .stdout(if callback_id.is_some() { + Stdio::piped() + } else { + Stdio::null() + }) + .stderr(if callback_id.is_some() { + Stdio::piped() + } else { + Stdio::null() + }) + .args(command) + .spawn() + else { + // TODO: notify user that program doesn't exist + tracing::warn!("tried to run {}, but it doesn't exist", program.to_string_lossy()); + return; + }; + + if let Some(callback_id) = callback_id { + let stdout = child.stdout.take(); + let stderr = child.stderr.take(); + let stream_out = self + .api_state + .stream + .as_ref() + .expect("Stream doesn't exist") + .clone(); + let stream_err = stream_out.clone(); + let stream_exit = stream_out.clone(); + + if let Some(stdout) = stdout { + let future = async move { + // TODO: use BufReader::new().lines() + let mut reader = futures_lite::io::BufReader::new(stdout); + loop { + let mut buf = String::new(); + match reader.read_line(&mut buf).await { + Ok(0) => break, + Ok(_) => { + let mut stream = stream_out.lock().expect("Couldn't lock stream"); + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::CallCallback { + callback_id, + args: Some(Args::Spawn { + stdout: Some(buf.trim_end_matches('\n').to_string()), + stderr: None, + exit_code: None, + exit_msg: None, + }), + }, + ) + .expect("Send to client failed"); // TODO: notify instead of crash + } + Err(err) => { + tracing::warn!("child read err: {err}"); + break; + } + } + } + }; + + // This is not important enough to crash on error, so just print the error instead + if let Err(err) = self.async_scheduler.schedule(future) { + tracing::error!("Failed to schedule future: {err}"); + } + } + if let Some(stderr) = stderr { + let future = async move { + let mut reader = futures_lite::io::BufReader::new(stderr); + loop { + let mut buf = String::new(); + match reader.read_line(&mut buf).await { + Ok(0) => break, + Ok(_) => { + let mut stream = stream_err.lock().expect("Couldn't lock stream"); + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::CallCallback { + callback_id, + args: Some(Args::Spawn { + stdout: None, + stderr: Some(buf.trim_end_matches('\n').to_string()), + exit_code: None, + exit_msg: None, + }), + }, + ) + .expect("Send to client failed"); // TODO: notify instead of crash + } + Err(err) => { + tracing::warn!("child read err: {err}"); + break; + } + } + } + }; + if let Err(err) = self.async_scheduler.schedule(future) { + tracing::error!("Failed to schedule future: {err}"); + } + } + + let future = async move { + match child.status().await { + Ok(exit_status) => { + let mut stream = stream_exit.lock().expect("Couldn't lock stream"); + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::CallCallback { + callback_id, + args: Some(Args::Spawn { + stdout: None, + stderr: None, + exit_code: exit_status.code(), + exit_msg: Some(exit_status.to_string()), + }), + }, + ) + .expect("Send to client failed"); // TODO: notify instead of crash + } + Err(err) => { + tracing::warn!("child wait() err: {err}"); + } + } + }; + if let Err(err) = self.async_scheduler.schedule(future) { + tracing::error!("Failed to schedule future: {err}"); + } + } + } + + pub fn re_layout(&mut self) { + let windows = + OutputState::with(self.focus_state.focused_output.as_ref().unwrap(), |state| { + for window in self.space.elements().cloned().collect::>() { + let should_render = WindowState::with_state(&window, |win_state| { + for tag_id in win_state.tags.iter() { + if *state.focused_tags.get(tag_id).unwrap_or(&false) { + return true; + } + } + false + }); + if !should_render { + self.space.unmap_elem(&window); + } + } + + self.windows + .iter() + .filter(|&win| { + WindowState::with_state(win, |win_state| { + for tag_id in win_state.tags.iter() { + if *state.focused_tags.get(tag_id).unwrap_or(&false) { + return true; + } + } + false + }) + }) + .cloned() + .collect::>() + }); + + tracing::debug!("Laying out {} windows", windows.len()); + + Layout::master_stack(self, windows, crate::layout::Direction::Left); + } +} + +impl State { /// Create the main [`State`]. /// /// This will set the WAYLAND_DISPLAY environment variable, insert Wayland necessary sources /// into the event loop, and run an implementation of the config API (currently Lua). pub fn init( - backend_data: B, + backend_data: WinitData, display: &mut Display, loop_signal: LoopSignal, - loop_handle: LoopHandle<'static, CalloopData>, + loop_handle: LoopHandle<'static, CalloopData>, ) -> Result> { let socket = ListeningSocketSource::new_auto()?; let socket_name = socket.socket_name().to_os_string(); @@ -118,10 +534,10 @@ impl State { // To fix this, I just set the limit to be higher. As Pinnacle is the whole graphical // environment, I *think* this is ok. if let Err(err) = smithay::reexports::nix::sys::resource::setrlimit( - smithay::reexports::nix::sys::resource::Resource::RLIMIT_NOFILE, - 65536, - 65536 * 2, - ) { + smithay::reexports::nix::sys::resource::Resource::RLIMIT_NOFILE, + 65536, + 65536 * 2, + ) { tracing::error!("Could not raise fd limit: errno {err}"); } @@ -146,232 +562,7 @@ impl State { let (tx_channel, rx_channel) = calloop::channel::channel::(); loop_handle.insert_source(rx_channel, |msg, _, data| match msg { - Event::Msg(msg) => { - // TODO: move this into its own function - // TODO: no like seriously this is getting a bit unwieldy - // TODO: no like rustfmt literally refuses to format the code below - match msg { - Msg::SetKeybind { - key, - modifiers, - callback_id, - } => { - tracing::info!("set keybind: {:?}, {}", modifiers, key); - data.state - .input_state - .keybinds - .insert((modifiers.into(), key), callback_id); - } - Msg::SetMousebind { button } => todo!(), - Msg::CloseWindow { client_id } => { - // TODO: client_id - tracing::info!("CloseWindow {:?}", client_id); - if let Some(window) = data.state.focus_state.current_focus() { - window.toplevel().send_close(); - } - } - Msg::ToggleFloating { client_id } => { - // TODO: add client_ids - if let Some(window) = data.state.focus_state.current_focus() { - crate::window::toggle_floating(&mut data.state, &window); - } - } - - Msg::Spawn { - command, - callback_id, - } => { - data.state.handle_spawn(command, callback_id); - } - - Msg::SetWindowSize { window_id, size } => { - let Some(window) = data.state.space.elements().find(|&win| { - WindowState::with_state(win, |state| state.id == window_id) - }) else { return; }; - - // TODO: tiled vs floating - window.toplevel().with_pending_state(|state| { - state.size = Some(size.into()); - }); - window.toplevel().send_pending_configure(); - } - Msg::MoveWindowToTag { window_id, tag_id } => { - if let Some(window) = data.state.windows.iter().find(|&win| { - WindowState::with_state(win, |state| state.id == window_id) - }) { - WindowState::with_state(window, |state| { - state.tags = vec![tag_id.clone()]; - }); - } - - data.state.re_layout(); - }, - Msg::ToggleTagOnWindow { window_id, tag_id } => { - if let Some(window) = data.state.windows.iter().find(|&win| { - WindowState::with_state(win, |state| state.id == window_id) - }) { - WindowState::with_state(window, |state| { - if state.tags.contains(&tag_id) { - state.tags.retain(|id| id != &tag_id); - } else { - state.tags.push(tag_id.clone()); - } - }); - - data.state.re_layout(); - } - }, - Msg::ToggleTag { tag_id } => { - OutputState::with( - data - .state - .focus_state - .focused_output - .as_ref() - .unwrap(), // TODO: handle error - |state| { - match state.focused_tags.get_mut(&tag_id) { - Some(id) => { - *id = !*id; - tracing::debug!("toggled tag {tag_id:?} {}", if *id { "on" } else { "off" }); - } - None => { - state.focused_tags.insert(tag_id.clone(), true); - tracing::debug!("toggled tag {tag_id:?} on"); - } - } - } - ); - - data.state.re_layout(); - }, - Msg::SwitchToTag { tag_id } => { - OutputState::with(data - .state - .focus_state - .focused_output - .as_ref() - .unwrap(), - |state| { - for (_, active) in state.focused_tags.iter_mut() { - *active = false; - } - if let Some(active) = state.focused_tags.get_mut(&tag_id) { - *active = true; - } else { - state.focused_tags.insert(tag_id.clone(), true); - } - tracing::debug!("focused tags: {:?}", state.focused_tags); - } - ); - - data.state.re_layout(); - } - Msg::AddTags { tags } => { - data - .state - .tag_state - .tags - .extend( - tags - .into_iter() - .map(|tag| Tag { id: tag, windows: vec![] }) - ); - }, - Msg::RemoveTags { tags } => { - data.state.tag_state.tags.retain(|tag| !tags.contains(&tag.id)); - }, - - Msg::Quit => { - data.state.loop_signal.stop(); - } - - Msg::Request(request) => match request { - Request::GetWindowByAppId { id, app_id } => todo!(), - Request::GetWindowByTitle { id, title } => todo!(), - Request::GetWindowByFocus { id } => { - let Some(current_focus) = data.state.focus_state.current_focus() else { return; }; - let (app_id, title) = compositor::with_states( - current_focus.toplevel().wl_surface(), - |states| { - let lock = states. - data_map - .get::() - .expect("XdgToplevelSurfaceData doesn't exist") - .lock() - .expect("Couldn't lock XdgToplevelSurfaceData"); - (lock.app_id.clone(), lock.title.clone()) - } - ); - let (window_id, floating) = WindowState::with_state(¤t_focus, |state| { - (state.id, state.floating.is_floating()) - }); - // TODO: unwrap - let location = data.state.space.element_location(¤t_focus).unwrap(); - let props = WindowProperties { - id: window_id, - app_id, - title, - size: current_focus.geometry().size.into(), - location: location.into(), - floating, - }; - let stream = data.state.api_state.stream.as_ref().expect("Stream doesn't exist"); - let mut stream = stream.lock().expect("Couldn't lock stream"); - crate::api::send_to_client( - &mut stream, - &OutgoingMsg::RequestResponse { - request_id: id, - response: RequestResponse::Window { window: props } - } - ) - .expect("Send to client failed"); - }, - Request::GetAllWindows { id } => { - let window_props = data.state.space.elements().map(|win| { - - let (app_id, title) = compositor::with_states( - win.toplevel().wl_surface(), - |states| { - let lock = states. - data_map - .get::() - .expect("XdgToplevelSurfaceData doesn't exist") - .lock() - .expect("Couldn't lock XdgToplevelSurfaceData"); - (lock.app_id.clone(), lock.title.clone()) - } - ); - let (window_id, floating) = WindowState::with_state(win, |state| { - (state.id, state.floating.is_floating()) - }); - // TODO: unwrap - let location = data.state.space.element_location(win).expect("Window location doesn't exist"); - WindowProperties { - id: window_id, - app_id, - title, - size: win.geometry().size.into(), - location: location.into(), - floating, - } - }).collect::>(); - - // FIXME: figure out what to do if error - let stream = data.state.api_state.stream.as_ref().expect("Stream doesn't exist"); - let mut stream = stream.lock().expect("Couldn't lock stream"); - crate::api::send_to_client( - &mut stream, - &OutgoingMsg::RequestResponse { - request_id: id, - response: RequestResponse::GetAllWindows { windows: window_props }, - } - ) - .expect("Couldn't send to client"); - } - }, - }; - } + Event::Msg(msg) => data.state.handle_msg(msg), Event::Closed => todo!(), })?; @@ -394,7 +585,8 @@ impl State { } })?; - let (executor, sched) = calloop::futures::executor::<()>().expect("Couldn't create executor"); + let (executor, sched) = + calloop::futures::executor::<()>().expect("Couldn't create executor"); loop_handle.insert_source(executor, |_, _, _| {})?; // TODO: move all this into the lua api @@ -428,7 +620,6 @@ impl State { tracing::error!("Could not find {}", config_path); } - let display_handle = display.handle(); let mut seat_state = SeatState::new(); let mut seat = seat_state.new_wl_seat(&display_handle, backend_data.seat_name()); @@ -470,181 +661,159 @@ impl State { windows: vec![], }) } +} - pub fn handle_spawn(&self, command: Vec, callback_id: Option) { - let mut command = command.into_iter(); - let Some(program) = command.next() else { - // TODO: notify that command was nothing - return; - }; +impl State { + pub fn init( + backend_data: UdevData, + display: &mut Display, + loop_signal: LoopSignal, + loop_handle: LoopHandle<'static, CalloopData>, + ) -> Result> { + let socket = ListeningSocketSource::new_auto()?; + let socket_name = socket.socket_name().to_os_string(); - let program = OsString::from(program); - let Ok(mut child) = async_process::Command::new(&program) - .env("WAYLAND_DISPLAY", self.socket_name.clone()) - .stdin(if callback_id.is_some() { - Stdio::piped() - } else { - // piping to null because foot won't open without a callback_id - // otherwise - Stdio::null() - }) - .stdout(if callback_id.is_some() { - Stdio::piped() - } else { - Stdio::null() - }) - .stderr(if callback_id.is_some() { - Stdio::piped() - } else { - Stdio::null() - }) - .args(command) - .spawn() - else { - // TODO: notify user that program doesn't exist - tracing::warn!("tried to run {}, but it doesn't exist", program.to_string_lossy()); - return; - }; + std::env::set_var("WAYLAND_DISPLAY", socket_name.clone()); - if let Some(callback_id) = callback_id { - let stdout = child.stdout.take(); - let stderr = child.stderr.take(); - let stream_out = self.api_state.stream.as_ref().expect("Stream doesn't exist").clone(); - let stream_err = stream_out.clone(); - let stream_exit = stream_out.clone(); - - if let Some(stdout) = stdout { - let future = async move { - // TODO: use BufReader::new().lines() - let mut reader = futures_lite::io::BufReader::new(stdout); - loop { - let mut buf = String::new(); - match reader.read_line(&mut buf).await { - Ok(0) => break, - Ok(_) => { - let mut stream = stream_out.lock().expect("Couldn't lock stream"); - crate::api::send_to_client( - &mut stream, - &OutgoingMsg::CallCallback { - callback_id, - args: Some(Args::Spawn { - stdout: Some(buf.trim_end_matches('\n').to_string()), - stderr: None, - exit_code: None, - exit_msg: None, - }), - }, - ) - .expect("Send to client failed"); // TODO: notify instead of crash - } - Err(err) => { - tracing::warn!("child read err: {err}"); - break; - }, - } - } - }; - - // This is not important enough to crash on error, so just print the error instead - if let Err(err) = self.async_scheduler.schedule(future) { - tracing::error!("Failed to schedule future: {err}"); - } - } - if let Some(stderr) = stderr { - let future = async move { - let mut reader = futures_lite::io::BufReader::new(stderr); - loop { - let mut buf = String::new(); - match reader.read_line(&mut buf).await { - Ok(0) => break, - Ok(_) => { - let mut stream = stream_err.lock().expect("Couldn't lock stream"); - crate::api::send_to_client( - &mut stream, - &OutgoingMsg::CallCallback { - callback_id, - args: Some(Args::Spawn { - stdout: None, - stderr: Some(buf.trim_end_matches('\n').to_string()), - exit_code: None, - exit_msg: None, - }), - }, - ) - .expect("Send to client failed"); // TODO: notify instead of crash - } - Err(err) => { - tracing::warn!("child read err: {err}"); - break; - }, - } - } - }; - if let Err(err) = self.async_scheduler.schedule(future) { - tracing::error!("Failed to schedule future: {err}"); - } - } - - let future = async move { - match child.status().await { - Ok(exit_status) => { - let mut stream = stream_exit.lock().expect("Couldn't lock stream"); - crate::api::send_to_client( - &mut stream, - &OutgoingMsg::CallCallback { - callback_id, - args: Some(Args::Spawn { - stdout: None, - stderr: None, - exit_code: exit_status.code(), - exit_msg: Some(exit_status.to_string()), - }), - }, - ) - .expect("Send to client failed"); // TODO: notify instead of crash - } - Err(err) => { - tracing::warn!("child wait() err: {err}"); - } - } - }; - if let Err(err) = self.async_scheduler.schedule(future) { - tracing::error!("Failed to schedule future: {err}"); - } + // 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. + 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}"); } - } - pub fn re_layout(&mut self) { - let windows = OutputState::with(self.focus_state.focused_output.as_ref().unwrap(), |state| { - for window in self.space.elements().cloned().collect::>() { - let should_render = WindowState::with_state(&window, |win_state| { - for tag_id in win_state.tags.iter() { - if *state.focused_tags.get(tag_id).unwrap_or(&false) { - return true; - } - } - false - }); - if !should_render { - self.space.unmap_elem(&window); - } + 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::(); + + // We want to replace the client if a new one pops up + // TODO: there should only ever be one client working at a time, and creating a new client + // | when one is already running should be impossible. + // INFO: this source try_clone()s the stream + loop_handle.insert_source(PinnacleSocketSource::new(tx_channel)?, |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.windows.iter().filter(|&win| { - WindowState::with_state(win, |win_state| { - for tag_id in win_state.tags.iter() { - if *state.focused_tags.get(tag_id).unwrap_or(&false) { - return true; - } - } - false - }) - }).cloned().collect::>() + let (executor, sched) = + calloop::futures::executor::<()>().expect("Couldn't create executor"); + loop_handle.insert_source(executor, |_, _, _| {})?; + + // TODO: move all this into the lua api + let config_path = std::env::var("PINNACLE_CONFIG").unwrap_or_else(|_| { + let mut default_path = + std::env::var("XDG_CONFIG_HOME").unwrap_or("~/.config".to_string()); + default_path.push_str("/pinnacle/init.lua"); + default_path }); - tracing::debug!("Laying out {} windows", windows.len()); + if Path::new(&config_path).exists() { + let lua_path = std::env::var("LUA_PATH").expect("Lua is not installed!"); + let mut local_lua_path = std::env::current_dir() + .expect("Couldn't get current dir") + .to_string_lossy() + .to_string(); + local_lua_path.push_str("/api/lua"); // TODO: get from crate root and do dynamically + let new_lua_path = + format!("{local_lua_path}/?.lua;{local_lua_path}/?/init.lua;{local_lua_path}/lib/?.lua;{local_lua_path}/lib/?/init.lua;{lua_path}"); - Layout::master_stack(self, windows, crate::layout::Direction::Left); + let lua_cpath = std::env::var("LUA_CPATH").expect("Lua is not installed!"); + let new_lua_cpath = format!("{local_lua_path}/lib/?.so;{lua_cpath}"); + + std::process::Command::new("lua5.4") + .arg(config_path) + .env("LUA_PATH", new_lua_path) + .env("LUA_CPATH", new_lua_cpath) + .spawn() + .expect("Could not start config process"); + } else { + tracing::error!("Could not find {}", config_path); + } + + let display_handle = display.handle(); + let mut seat_state = SeatState::new(); + let mut seat = seat_state.new_wl_seat(&display_handle, backend_data.seat_name()); + 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!(), + }) + .unwrap(); // TODO: unwrap + }); + + Ok(Self { + backend_data, + loop_signal, + loop_handle, + clock: Clock::::new()?, + compositor_state: CompositorState::new::(&display_handle), + data_device_state: DataDeviceState::new::(&display_handle), + seat_state, + pointer_location: (0.0, 0.0).into(), + shm_state: ShmState::new::(&display_handle, vec![]), + space: Space::::default(), + cursor_status: CursorImageStatus::Default, + output_manager_state: OutputManagerState::new_with_xdg_output::(&display_handle), + xdg_shell_state: XdgShellState::new::(&display_handle), + viewporter_state: ViewporterState::new::(&display_handle), + fractional_scale_manager_state: FractionalScaleManagerState::new::( + &display_handle, + ), + input_state: InputState::new(), + api_state: ApiState::new(), + focus_state: FocusState::new(), + tag_state: TagState::new(), + + seat, + + move_mode: false, + socket_name: socket_name.to_string_lossy().to_string(), + + popup_manager: PopupManager::default(), + + async_scheduler: sched, + + windows: vec![], + }) } }