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-06-17 18:55:04 -05:00
|
|
|
use std::{
|
2023-07-10 17:14:37 -05:00
|
|
|
cell::RefCell,
|
2023-06-17 18:55:04 -05:00
|
|
|
error::Error,
|
2023-06-29 11:58:33 -05:00
|
|
|
ffi::OsString,
|
2023-06-17 18:55:04 -05:00
|
|
|
os::{fd::AsRawFd, unix::net::UnixStream},
|
2023-07-02 18:15:44 -05:00
|
|
|
path::Path,
|
2023-06-21 14:48:38 -05:00
|
|
|
process::Stdio,
|
2023-07-16 20:44:18 -05:00
|
|
|
sync::{Arc, Mutex},
|
2023-07-24 11:43:47 -05:00
|
|
|
time::Duration,
|
2023-06-17 18:55:04 -05:00
|
|
|
};
|
2023-06-04 18:11:14 -05:00
|
|
|
|
2023-06-17 21:02:58 -05:00
|
|
|
use crate::{
|
2023-06-21 14:48:38 -05:00
|
|
|
api::{
|
2023-07-21 14:36:32 -05:00
|
|
|
msg::{Args, CallbackId, Msg, OutgoingMsg, Request, RequestId, RequestResponse},
|
2023-06-21 14:48:38 -05:00
|
|
|
PinnacleSocketSource,
|
|
|
|
},
|
2023-07-24 11:43:47 -05:00
|
|
|
cursor::Cursor,
|
2023-06-17 21:02:58 -05:00
|
|
|
focus::FocusState,
|
2023-07-10 17:14:37 -05:00
|
|
|
grab::resize_grab::ResizeSurfaceState,
|
2023-07-18 15:12:23 -05:00
|
|
|
tag::Tag,
|
2023-08-02 10:13:18 -05:00
|
|
|
window::{
|
|
|
|
window_state::{Float, WindowResizeState},
|
|
|
|
WindowElement,
|
|
|
|
},
|
2023-06-17 21:02:58 -05:00
|
|
|
};
|
2023-06-29 11:58:33 -05:00
|
|
|
use calloop::futures::Scheduler;
|
|
|
|
use futures_lite::AsyncBufReadExt;
|
2023-06-02 16:01:48 -05:00
|
|
|
use smithay::{
|
2023-06-09 20:29:17 -05:00
|
|
|
backend::renderer::element::RenderElementStates,
|
|
|
|
desktop::{
|
2023-07-24 18:59:05 -05:00
|
|
|
space::SpaceElement,
|
2023-06-09 20:29:17 -05:00
|
|
|
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},
|
2023-07-10 17:14:37 -05:00
|
|
|
protocol::wl_surface::WlSurface,
|
2023-07-27 17:31:40 -05:00
|
|
|
Display, DisplayHandle,
|
2023-06-02 16:01:48 -05:00
|
|
|
},
|
|
|
|
},
|
2023-08-01 21:23:30 -05:00
|
|
|
utils::{Clock, IsAlive, Logical, Monotonic, Point, Size},
|
2023-06-02 16:01:48 -05:00
|
|
|
wayland::{
|
2023-06-26 18:48:29 -05:00
|
|
|
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,
|
2023-06-04 18:11:14 -05:00
|
|
|
fractional_scale::FractionalScaleManagerState,
|
2023-06-02 16:01:48 -05:00
|
|
|
output::OutputManagerState,
|
2023-08-02 15:12:10 -05:00
|
|
|
primary_selection::PrimarySelectionState,
|
2023-07-18 21:10:43 -05:00
|
|
|
shell::xdg::{XdgShellState, XdgToplevelSurfaceData},
|
2023-06-02 16:01:48 -05:00
|
|
|
shm::ShmState,
|
2023-06-09 20:29:17 -05:00
|
|
|
socket::ListeningSocketSource,
|
2023-06-04 18:11:14 -05:00
|
|
|
viewporter::ViewporterState,
|
2023-06-02 16:01:48 -05:00
|
|
|
},
|
2023-07-24 11:43:47 -05:00
|
|
|
xwayland::{X11Wm, XWayland, XWaylandEvent},
|
2023-06-02 16:01:48 -05:00
|
|
|
};
|
|
|
|
|
2023-06-15 16:43:33 -05:00
|
|
|
use crate::{backend::Backend, input::InputState};
|
2023-06-02 16:01:48 -05:00
|
|
|
|
2023-06-21 18:58:49 -05:00
|
|
|
/// The main state of the application.
|
2023-06-02 16:01:48 -05:00
|
|
|
pub struct State<B: Backend> {
|
|
|
|
pub backend_data: B,
|
2023-06-07 12:12:54 -05:00
|
|
|
|
2023-06-02 16:01:48 -05:00
|
|
|
pub loop_signal: LoopSignal,
|
2023-06-09 20:29:17 -05:00
|
|
|
pub loop_handle: LoopHandle<'static, CalloopData<B>>,
|
2023-07-27 17:31:40 -05:00
|
|
|
pub display_handle: DisplayHandle,
|
2023-06-02 16:01:48 -05:00
|
|
|
pub clock: Clock<Monotonic>,
|
2023-06-07 12:12:54 -05:00
|
|
|
|
2023-07-24 18:59:05 -05:00
|
|
|
pub space: Space<WindowElement>,
|
2023-06-07 12:12:54 -05:00
|
|
|
pub move_mode: bool,
|
2023-06-09 20:29:17 -05:00
|
|
|
pub socket_name: String,
|
|
|
|
|
|
|
|
pub seat: Seat<State<B>>,
|
2023-06-07 12:12:54 -05:00
|
|
|
|
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,
|
2023-06-04 18:11:14 -05:00
|
|
|
pub viewporter_state: ViewporterState,
|
|
|
|
pub fractional_scale_manager_state: FractionalScaleManagerState,
|
2023-08-02 15:12:10 -05:00
|
|
|
pub primary_selection_state: PrimarySelectionState,
|
|
|
|
|
2023-06-15 16:43:33 -05:00
|
|
|
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
|
|
|
|
2023-06-07 12:12:54 -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-06-29 11:58:33 -05:00
|
|
|
|
|
|
|
pub async_scheduler: Scheduler<()>,
|
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>,
|
2023-07-24 11:43:47 -05:00
|
|
|
|
|
|
|
pub xwayland: XWayland,
|
|
|
|
pub xwm: Option<X11Wm>,
|
|
|
|
pub xdisplay: Option<u32>,
|
2023-06-02 16:01:48 -05:00
|
|
|
}
|
|
|
|
|
2023-06-05 21:08:37 -05:00
|
|
|
impl<B: Backend> State<B> {
|
2023-07-02 18:15:44 -05:00
|
|
|
pub fn handle_msg(&mut self, msg: Msg) {
|
2023-07-18 12:37:40 -05:00
|
|
|
// tracing::debug!("Got {msg:?}");
|
2023-07-02 18:15:44 -05:00
|
|
|
match msg {
|
|
|
|
Msg::SetKeybind {
|
|
|
|
key,
|
|
|
|
modifiers,
|
|
|
|
callback_id,
|
|
|
|
} => {
|
|
|
|
tracing::info!("set keybind: {:?}, {}", modifiers, key);
|
|
|
|
self.input_state
|
|
|
|
.keybinds
|
|
|
|
.insert((modifiers.into(), key), callback_id);
|
|
|
|
}
|
2023-07-18 15:12:23 -05:00
|
|
|
Msg::SetMousebind { button: _ } => todo!(),
|
|
|
|
Msg::CloseWindow { window_id } => {
|
2023-07-18 21:10:43 -05:00
|
|
|
if let Some(window) = window_id.window(self) {
|
2023-07-24 18:59:05 -05:00
|
|
|
match window {
|
|
|
|
WindowElement::Wayland(window) => window.toplevel().send_close(),
|
|
|
|
WindowElement::X11(surface) => {
|
|
|
|
surface.close().expect("failed to close x11 win");
|
|
|
|
}
|
|
|
|
}
|
2023-07-02 18:15:44 -05:00
|
|
|
}
|
|
|
|
}
|
2023-07-18 15:12:23 -05:00
|
|
|
Msg::ToggleFloating { window_id } => {
|
2023-07-18 21:10:43 -05:00
|
|
|
if let Some(window) = window_id.window(self) {
|
2023-07-02 18:15:44 -05:00
|
|
|
crate::window::toggle_floating(self, &window);
|
|
|
|
}
|
|
|
|
}
|
2023-06-09 20:29:17 -05:00
|
|
|
|
2023-07-02 18:15:44 -05:00
|
|
|
Msg::Spawn {
|
|
|
|
command,
|
|
|
|
callback_id,
|
|
|
|
} => {
|
|
|
|
self.handle_spawn(command, callback_id);
|
|
|
|
}
|
2023-06-28 16:41:36 -05:00
|
|
|
|
2023-07-18 21:10:43 -05:00
|
|
|
Msg::SetWindowSize {
|
|
|
|
window_id,
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
} => {
|
|
|
|
let Some(window) = window_id.window(self) else { return };
|
2023-06-09 20:29:17 -05:00
|
|
|
|
2023-07-02 18:15:44 -05:00
|
|
|
// TODO: tiled vs floating
|
2023-07-24 18:59:05 -05:00
|
|
|
// FIXME: this will map unmapped windows at 0,0
|
|
|
|
let window_loc = self
|
|
|
|
.space
|
|
|
|
.element_location(&window)
|
|
|
|
.unwrap_or((0, 0).into());
|
2023-07-28 14:33:14 -05:00
|
|
|
let mut window_size = window.geometry().size;
|
|
|
|
if let Some(width) = width {
|
|
|
|
window_size.w = width;
|
|
|
|
}
|
|
|
|
if let Some(height) = height {
|
|
|
|
window_size.h = height;
|
|
|
|
}
|
2023-07-26 16:01:53 -05:00
|
|
|
window.request_size_change(self, window_loc, window_size);
|
2023-07-02 18:15:44 -05:00
|
|
|
}
|
|
|
|
Msg::MoveWindowToTag { window_id, tag_id } => {
|
2023-07-21 11:38:46 -05:00
|
|
|
let Some(window) = window_id.window(self) else { return };
|
|
|
|
let Some(tag) = tag_id.tag(self) else { return };
|
|
|
|
window.with_state(|state| {
|
|
|
|
state.tags = vec![tag.clone()];
|
|
|
|
});
|
|
|
|
let Some(output) = tag.output(self) else { return };
|
|
|
|
self.re_layout(&output);
|
2023-07-02 18:15:44 -05:00
|
|
|
}
|
|
|
|
Msg::ToggleTagOnWindow { window_id, tag_id } => {
|
2023-07-21 11:38:46 -05:00
|
|
|
let Some(window) = window_id.window(self) else { return };
|
|
|
|
let Some(tag) = tag_id.tag(self) else { return };
|
|
|
|
|
|
|
|
window.with_state(|state| {
|
|
|
|
if state.tags.contains(&tag) {
|
|
|
|
state.tags.retain(|tg| tg != &tag);
|
|
|
|
} else {
|
|
|
|
state.tags.push(tag.clone());
|
|
|
|
}
|
|
|
|
});
|
2023-06-21 14:48:38 -05:00
|
|
|
|
2023-07-21 11:38:46 -05:00
|
|
|
let Some(output) = tag.output(self) else { return };
|
|
|
|
self.re_layout(&output);
|
2023-07-02 18:15:44 -05:00
|
|
|
}
|
2023-07-21 11:38:46 -05:00
|
|
|
Msg::ToggleTag { tag_id } => {
|
2023-07-11 16:10:31 -05:00
|
|
|
tracing::debug!("ToggleTag");
|
2023-07-21 11:38:46 -05:00
|
|
|
if let Some(tag) = tag_id.tag(self) {
|
|
|
|
tag.set_active(!tag.active());
|
|
|
|
if let Some(output) = tag.output(self) {
|
|
|
|
self.re_layout(&output);
|
|
|
|
}
|
2023-07-11 16:10:31 -05:00
|
|
|
}
|
2023-07-02 18:15:44 -05:00
|
|
|
}
|
2023-07-21 11:38:46 -05:00
|
|
|
Msg::SwitchToTag { tag_id } => {
|
|
|
|
let Some(tag) = tag_id.tag(self) else { return };
|
|
|
|
let Some(output) = tag.output(self) else { return };
|
|
|
|
output.with_state(|state| {
|
|
|
|
for op_tag in state.tags.iter_mut() {
|
|
|
|
op_tag.set_active(false);
|
|
|
|
}
|
|
|
|
tag.set_active(true);
|
|
|
|
});
|
|
|
|
self.re_layout(&output);
|
2023-07-02 18:15:44 -05:00
|
|
|
}
|
2023-07-18 21:10:43 -05:00
|
|
|
Msg::AddTags {
|
|
|
|
output_name,
|
|
|
|
tag_names,
|
|
|
|
} => {
|
2023-07-04 21:27:23 -05:00
|
|
|
if let Some(output) = self
|
2023-07-11 11:59:38 -05:00
|
|
|
.space
|
|
|
|
.outputs()
|
|
|
|
.find(|output| output.name() == output_name)
|
2023-07-04 21:27:23 -05:00
|
|
|
{
|
2023-07-10 17:14:37 -05:00
|
|
|
output.with_state(|state| {
|
2023-07-18 21:10:43 -05:00
|
|
|
state.tags.extend(tag_names.iter().cloned().map(Tag::new));
|
2023-07-11 16:10:31 -05:00
|
|
|
tracing::debug!("tags added, are now {:?}", state.tags);
|
2023-07-09 17:48:46 -05:00
|
|
|
});
|
2023-07-04 21:27:23 -05:00
|
|
|
}
|
2023-07-02 18:15:44 -05:00
|
|
|
}
|
2023-07-21 11:38:46 -05:00
|
|
|
Msg::RemoveTags { tag_ids } => {
|
|
|
|
let tags = tag_ids.into_iter().filter_map(|tag_id| tag_id.tag(self));
|
|
|
|
for tag in tags {
|
|
|
|
let Some(output) = tag.output(self) else { continue };
|
2023-07-10 17:14:37 -05:00
|
|
|
output.with_state(|state| {
|
2023-07-21 11:38:46 -05:00
|
|
|
state.tags.retain(|tg| tg != &tag);
|
2023-07-09 17:48:46 -05:00
|
|
|
});
|
|
|
|
}
|
2023-07-02 18:15:44 -05:00
|
|
|
}
|
2023-07-21 11:38:46 -05:00
|
|
|
Msg::SetLayout { tag_id, layout } => {
|
|
|
|
let Some(tag) = tag_id.tag(self) else { return };
|
|
|
|
tag.set_layout(layout);
|
|
|
|
let Some(output) = tag.output(self) else { return };
|
|
|
|
self.re_layout(&output);
|
2023-07-12 18:50:41 -05:00
|
|
|
}
|
2023-06-26 18:48:29 -05:00
|
|
|
|
2023-07-11 11:59:38 -05:00
|
|
|
Msg::ConnectForAllOutputs { callback_id } => {
|
|
|
|
let stream = self
|
|
|
|
.api_state
|
|
|
|
.stream
|
|
|
|
.as_ref()
|
|
|
|
.expect("Stream doesn't exist");
|
|
|
|
let mut stream = stream.lock().expect("Couldn't lock stream");
|
|
|
|
for output in self.space.outputs() {
|
|
|
|
crate::api::send_to_client(
|
|
|
|
&mut stream,
|
|
|
|
&OutgoingMsg::CallCallback {
|
|
|
|
callback_id,
|
|
|
|
args: Some(Args::ConnectForAllOutputs {
|
|
|
|
output_name: output.name(),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
.expect("Send to client failed");
|
|
|
|
}
|
|
|
|
self.output_callback_ids.push(callback_id);
|
|
|
|
}
|
2023-08-04 13:45:38 -05:00
|
|
|
Msg::SetOutputLocation { output_name, x, y } => {
|
|
|
|
let Some(output) = output_name.output(self) else { return };
|
|
|
|
let mut loc = output.current_location();
|
|
|
|
if let Some(x) = x {
|
|
|
|
loc.x = x;
|
|
|
|
}
|
|
|
|
if let Some(y) = y {
|
|
|
|
loc.y = y;
|
|
|
|
}
|
|
|
|
output.change_current_state(None, None, None, Some(loc));
|
|
|
|
self.space.map_output(&output, loc);
|
|
|
|
tracing::debug!("mapping output {} to {loc:?}", output.name());
|
|
|
|
self.re_layout(&output);
|
|
|
|
}
|
2023-07-11 11:59:38 -05:00
|
|
|
|
2023-07-02 18:15:44 -05:00
|
|
|
Msg::Quit => {
|
|
|
|
self.loop_signal.stop();
|
|
|
|
}
|
|
|
|
|
2023-07-21 14:36:32 -05:00
|
|
|
Msg::Request {
|
|
|
|
request_id,
|
|
|
|
request,
|
|
|
|
} => {
|
|
|
|
self.handle_request(request_id, request);
|
2023-07-18 21:10:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-21 14:36:32 -05:00
|
|
|
fn handle_request(&mut self, request_id: RequestId, request: Request) {
|
2023-07-18 21:10:43 -05:00
|
|
|
let stream = self
|
|
|
|
.api_state
|
|
|
|
.stream
|
|
|
|
.as_ref()
|
|
|
|
.expect("Stream doesn't exist");
|
|
|
|
let mut stream = stream.lock().expect("Couldn't lock stream");
|
|
|
|
match request {
|
2023-07-19 20:11:15 -05:00
|
|
|
Request::GetWindows => {
|
2023-07-18 21:10:43 -05:00
|
|
|
let window_ids = self
|
|
|
|
.windows
|
|
|
|
.iter()
|
|
|
|
.map(|win| win.with_state(|state| state.id))
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
// FIXME: figure out what to do if error
|
|
|
|
crate::api::send_to_client(
|
|
|
|
&mut stream,
|
|
|
|
&OutgoingMsg::RequestResponse {
|
2023-07-21 14:36:32 -05:00
|
|
|
request_id,
|
2023-07-18 21:10:43 -05:00
|
|
|
response: RequestResponse::Windows { window_ids },
|
|
|
|
},
|
|
|
|
)
|
|
|
|
.expect("Couldn't send to client");
|
|
|
|
}
|
2023-07-19 20:11:15 -05:00
|
|
|
Request::GetWindowProps { window_id } => {
|
|
|
|
let window = window_id.window(self);
|
|
|
|
let size = window
|
|
|
|
.as_ref()
|
2023-07-18 21:10:43 -05:00
|
|
|
.map(|win| (win.geometry().size.w, win.geometry().size.h));
|
2023-07-19 20:11:15 -05:00
|
|
|
let loc = window
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|win| self.space.element_location(win))
|
2023-07-18 21:10:43 -05:00
|
|
|
.map(|loc| (loc.x, loc.y));
|
2023-07-25 10:23:34 -05:00
|
|
|
let (class, title) = window.as_ref().map_or((None, None), |win| match &win {
|
|
|
|
WindowElement::Wayland(_) => {
|
|
|
|
if let Some(wl_surf) = win.wl_surface() {
|
|
|
|
compositor::with_states(&wl_surf, |states| {
|
|
|
|
let lock = states
|
|
|
|
.data_map
|
|
|
|
.get::<XdgToplevelSurfaceData>()
|
|
|
|
.expect("XdgToplevelSurfaceData wasn't in surface's data map")
|
|
|
|
.lock()
|
|
|
|
.expect("failed to acquire lock");
|
|
|
|
(lock.app_id.clone(), lock.title.clone())
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
(None, None)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
WindowElement::X11(surface) => (Some(surface.class()), Some(surface.title())),
|
|
|
|
});
|
2023-07-19 20:11:15 -05:00
|
|
|
let floating = window
|
|
|
|
.as_ref()
|
2023-07-18 21:10:43 -05:00
|
|
|
.map(|win| win.with_state(|state| state.floating.is_floating()));
|
2023-07-19 20:11:15 -05:00
|
|
|
let focused = window.as_ref().and_then(|win| {
|
|
|
|
self.focus_state
|
|
|
|
.current_focus() // TODO: actual focus
|
|
|
|
.map(|foc_win| win == &foc_win)
|
|
|
|
});
|
2023-07-18 21:10:43 -05:00
|
|
|
crate::api::send_to_client(
|
|
|
|
&mut stream,
|
|
|
|
&OutgoingMsg::RequestResponse {
|
2023-07-21 14:36:32 -05:00
|
|
|
request_id,
|
2023-07-19 20:11:15 -05:00
|
|
|
response: RequestResponse::WindowProps {
|
|
|
|
size,
|
|
|
|
loc,
|
|
|
|
class,
|
|
|
|
title,
|
|
|
|
floating,
|
|
|
|
focused,
|
|
|
|
},
|
2023-07-18 21:10:43 -05:00
|
|
|
},
|
|
|
|
)
|
|
|
|
.expect("failed to send to client");
|
|
|
|
}
|
2023-07-19 18:55:22 -05:00
|
|
|
Request::GetOutputs => {
|
|
|
|
let output_names = self
|
2023-07-18 21:10:43 -05:00
|
|
|
.space
|
|
|
|
.outputs()
|
|
|
|
.map(|output| output.name())
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
crate::api::send_to_client(
|
|
|
|
&mut stream,
|
|
|
|
&OutgoingMsg::RequestResponse {
|
2023-07-21 14:36:32 -05:00
|
|
|
request_id,
|
2023-07-19 18:55:22 -05:00
|
|
|
response: RequestResponse::Outputs { output_names },
|
2023-07-18 21:10:43 -05:00
|
|
|
},
|
|
|
|
)
|
|
|
|
.expect("failed to send to client");
|
|
|
|
}
|
2023-07-19 18:55:22 -05:00
|
|
|
Request::GetOutputProps { output_name } => {
|
|
|
|
let output = self
|
2023-07-18 21:10:43 -05:00
|
|
|
.space
|
|
|
|
.outputs()
|
2023-07-19 18:55:22 -05:00
|
|
|
.find(|output| output.name() == output_name);
|
|
|
|
let res = output.as_ref().and_then(|output| {
|
|
|
|
output.current_mode().map(|mode| (mode.size.w, mode.size.h))
|
|
|
|
});
|
|
|
|
let refresh_rate = output
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|output| output.current_mode().map(|mode| mode.refresh));
|
|
|
|
let model = output
|
|
|
|
.as_ref()
|
|
|
|
.map(|output| output.physical_properties().model);
|
|
|
|
let physical_size = output.as_ref().map(|output| {
|
|
|
|
(
|
|
|
|
output.physical_properties().size.w,
|
|
|
|
output.physical_properties().size.h,
|
|
|
|
)
|
|
|
|
});
|
|
|
|
let make = output
|
|
|
|
.as_ref()
|
|
|
|
.map(|output| output.physical_properties().make);
|
|
|
|
let loc = output
|
|
|
|
.as_ref()
|
|
|
|
.map(|output| (output.current_location().x, output.current_location().y));
|
|
|
|
let focused = self
|
2023-07-18 21:10:43 -05:00
|
|
|
.focus_state
|
|
|
|
.focused_output
|
|
|
|
.as_ref()
|
2023-07-19 18:55:22 -05:00
|
|
|
.and_then(|foc_op| output.map(|op| op == foc_op));
|
2023-07-20 15:22:22 -05:00
|
|
|
let tag_ids = output.as_ref().map(|output| {
|
|
|
|
output.with_state(|state| {
|
|
|
|
state.tags.iter().map(|tag| tag.id()).collect::<Vec<_>>()
|
|
|
|
})
|
|
|
|
});
|
2023-07-18 21:10:43 -05:00
|
|
|
crate::api::send_to_client(
|
|
|
|
&mut stream,
|
|
|
|
&OutgoingMsg::RequestResponse {
|
2023-07-21 14:36:32 -05:00
|
|
|
request_id,
|
2023-07-19 18:55:22 -05:00
|
|
|
response: RequestResponse::OutputProps {
|
|
|
|
make,
|
|
|
|
model,
|
|
|
|
loc,
|
|
|
|
res,
|
|
|
|
refresh_rate,
|
|
|
|
physical_size,
|
|
|
|
focused,
|
2023-07-20 15:22:22 -05:00
|
|
|
tag_ids,
|
2023-07-19 18:55:22 -05:00
|
|
|
},
|
2023-07-18 21:10:43 -05:00
|
|
|
},
|
|
|
|
)
|
|
|
|
.expect("failed to send to client");
|
|
|
|
}
|
2023-07-20 16:05:26 -05:00
|
|
|
Request::GetTags => {
|
2023-07-19 18:55:22 -05:00
|
|
|
let tag_ids = self
|
2023-07-18 21:10:43 -05:00
|
|
|
.space
|
|
|
|
.outputs()
|
|
|
|
.flat_map(|op| op.with_state(|state| state.tags.clone()))
|
2023-07-19 18:55:22 -05:00
|
|
|
.map(|tag| tag.id())
|
|
|
|
.collect::<Vec<_>>();
|
2023-07-21 14:36:32 -05:00
|
|
|
tracing::debug!("GetTags: {:?}", tag_ids);
|
2023-07-19 18:55:22 -05:00
|
|
|
crate::api::send_to_client(
|
|
|
|
&mut stream,
|
|
|
|
&OutgoingMsg::RequestResponse {
|
2023-07-21 14:36:32 -05:00
|
|
|
request_id,
|
2023-07-19 18:55:22 -05:00
|
|
|
response: RequestResponse::Tags { tag_ids },
|
|
|
|
},
|
|
|
|
)
|
|
|
|
.expect("failed to send to client");
|
|
|
|
}
|
2023-07-20 16:05:26 -05:00
|
|
|
Request::GetTagProps { tag_id } => {
|
|
|
|
let tag = tag_id.tag(self);
|
|
|
|
let output_name = tag
|
|
|
|
.as_ref()
|
2023-07-19 18:55:22 -05:00
|
|
|
.and_then(|tag| tag.output(self))
|
|
|
|
.map(|output| output.name());
|
2023-07-20 16:05:26 -05:00
|
|
|
let active = tag.as_ref().map(|tag| tag.active());
|
|
|
|
let name = tag.as_ref().map(|tag| tag.name());
|
2023-07-19 18:55:22 -05:00
|
|
|
crate::api::send_to_client(
|
|
|
|
&mut stream,
|
|
|
|
&OutgoingMsg::RequestResponse {
|
2023-07-21 14:36:32 -05:00
|
|
|
request_id,
|
2023-07-20 16:05:26 -05:00
|
|
|
response: RequestResponse::TagProps {
|
|
|
|
active,
|
|
|
|
name,
|
|
|
|
output_name,
|
|
|
|
},
|
2023-07-19 18:55:22 -05:00
|
|
|
},
|
|
|
|
)
|
|
|
|
.expect("failed to send to client");
|
2023-07-18 21:10:43 -05:00
|
|
|
}
|
2023-07-01 19:06:37 -05:00
|
|
|
}
|
2023-06-09 20:29:17 -05:00
|
|
|
}
|
2023-06-28 16:41:36 -05:00
|
|
|
|
|
|
|
pub fn handle_spawn(&self, command: Vec<String>, callback_id: Option<CallbackId>) {
|
2023-06-29 17:41:08 -05:00
|
|
|
let mut command = command.into_iter();
|
|
|
|
let Some(program) = command.next() else {
|
2023-06-28 16:41:36 -05:00
|
|
|
// TODO: notify that command was nothing
|
|
|
|
return;
|
2023-06-29 17:41:08 -05:00
|
|
|
};
|
2023-06-28 16:41:36 -05:00
|
|
|
|
2023-06-29 17:41:08 -05:00
|
|
|
let program = OsString::from(program);
|
|
|
|
let Ok(mut child) = async_process::Command::new(&program)
|
2023-07-24 18:59:05 -05:00
|
|
|
.envs(
|
|
|
|
[("WAYLAND_DISPLAY", self.socket_name.clone())]
|
|
|
|
.into_iter()
|
|
|
|
.chain(
|
|
|
|
self.xdisplay.map(|xdisp| ("DISPLAY", format!(":{xdisp}")))
|
|
|
|
)
|
|
|
|
)
|
2023-06-28 16:41:36 -05:00
|
|
|
.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()
|
2023-06-29 17:41:08 -05:00
|
|
|
else {
|
|
|
|
// TODO: notify user that program doesn't exist
|
|
|
|
tracing::warn!("tried to run {}, but it doesn't exist", program.to_string_lossy());
|
|
|
|
return;
|
|
|
|
};
|
2023-06-28 16:41:36 -05:00
|
|
|
|
|
|
|
if let Some(callback_id) = callback_id {
|
|
|
|
let stdout = child.stdout.take();
|
|
|
|
let stderr = child.stderr.take();
|
2023-07-02 18:15:44 -05:00
|
|
|
let stream_out = self
|
|
|
|
.api_state
|
|
|
|
.stream
|
|
|
|
.as_ref()
|
|
|
|
.expect("Stream doesn't exist")
|
|
|
|
.clone();
|
2023-06-28 16:41:36 -05:00
|
|
|
let stream_err = stream_out.clone();
|
|
|
|
let stream_exit = stream_out.clone();
|
|
|
|
|
|
|
|
if let Some(stdout) = stdout {
|
2023-06-29 11:58:33 -05:00
|
|
|
let future = async move {
|
|
|
|
// TODO: use BufReader::new().lines()
|
|
|
|
let mut reader = futures_lite::io::BufReader::new(stdout);
|
2023-06-28 16:41:36 -05:00
|
|
|
loop {
|
|
|
|
let mut buf = String::new();
|
2023-06-29 11:58:33 -05:00
|
|
|
match reader.read_line(&mut buf).await {
|
|
|
|
Ok(0) => break,
|
2023-06-28 16:41:36 -05:00
|
|
|
Ok(_) => {
|
2023-06-29 17:41:08 -05:00
|
|
|
let mut stream = stream_out.lock().expect("Couldn't lock stream");
|
2023-06-28 16:41:36 -05:00
|
|
|
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,
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
)
|
2023-06-29 17:41:08 -05:00
|
|
|
.expect("Send to client failed"); // TODO: notify instead of crash
|
2023-06-28 16:41:36 -05:00
|
|
|
}
|
|
|
|
Err(err) => {
|
2023-06-29 11:58:33 -05:00
|
|
|
tracing::warn!("child read err: {err}");
|
2023-06-28 16:41:36 -05:00
|
|
|
break;
|
2023-07-02 18:15:44 -05:00
|
|
|
}
|
2023-06-28 16:41:36 -05:00
|
|
|
}
|
|
|
|
}
|
2023-06-29 11:58:33 -05:00
|
|
|
};
|
2023-06-29 17:41:08 -05:00
|
|
|
|
|
|
|
// 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}");
|
|
|
|
}
|
2023-06-28 16:41:36 -05:00
|
|
|
}
|
|
|
|
if let Some(stderr) = stderr {
|
2023-06-29 11:58:33 -05:00
|
|
|
let future = async move {
|
|
|
|
let mut reader = futures_lite::io::BufReader::new(stderr);
|
2023-06-28 16:41:36 -05:00
|
|
|
loop {
|
|
|
|
let mut buf = String::new();
|
2023-06-29 11:58:33 -05:00
|
|
|
match reader.read_line(&mut buf).await {
|
|
|
|
Ok(0) => break,
|
2023-06-28 16:41:36 -05:00
|
|
|
Ok(_) => {
|
2023-06-29 17:41:08 -05:00
|
|
|
let mut stream = stream_err.lock().expect("Couldn't lock stream");
|
2023-06-28 16:41:36 -05:00
|
|
|
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,
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
)
|
2023-06-29 17:41:08 -05:00
|
|
|
.expect("Send to client failed"); // TODO: notify instead of crash
|
2023-06-28 16:41:36 -05:00
|
|
|
}
|
|
|
|
Err(err) => {
|
2023-06-29 11:58:33 -05:00
|
|
|
tracing::warn!("child read err: {err}");
|
2023-06-28 16:41:36 -05:00
|
|
|
break;
|
2023-07-02 18:15:44 -05:00
|
|
|
}
|
2023-06-28 16:41:36 -05:00
|
|
|
}
|
|
|
|
}
|
2023-06-29 11:58:33 -05:00
|
|
|
};
|
2023-06-29 17:41:08 -05:00
|
|
|
if let Err(err) = self.async_scheduler.schedule(future) {
|
|
|
|
tracing::error!("Failed to schedule future: {err}");
|
|
|
|
}
|
2023-06-28 16:41:36 -05:00
|
|
|
}
|
2023-06-29 11:58:33 -05:00
|
|
|
|
|
|
|
let future = async move {
|
|
|
|
match child.status().await {
|
|
|
|
Ok(exit_status) => {
|
2023-06-29 17:41:08 -05:00
|
|
|
let mut stream = stream_exit.lock().expect("Couldn't lock stream");
|
2023-06-29 11:58:33 -05:00
|
|
|
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()),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
)
|
2023-06-29 17:41:08 -05:00
|
|
|
.expect("Send to client failed"); // TODO: notify instead of crash
|
2023-06-29 11:58:33 -05:00
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
tracing::warn!("child wait() err: {err}");
|
|
|
|
}
|
2023-06-28 16:41:36 -05:00
|
|
|
}
|
2023-06-29 11:58:33 -05:00
|
|
|
};
|
2023-06-29 17:41:08 -05:00
|
|
|
if let Err(err) = self.async_scheduler.schedule(future) {
|
|
|
|
tracing::error!("Failed to schedule future: {err}");
|
|
|
|
}
|
2023-06-28 16:41:36 -05:00
|
|
|
}
|
|
|
|
}
|
2023-07-02 10:26:07 -05:00
|
|
|
|
2023-07-11 16:10:31 -05:00
|
|
|
pub fn re_layout(&mut self, output: &Output) {
|
2023-08-02 19:45:56 -05:00
|
|
|
for win in self.windows.iter() {
|
|
|
|
if win.is_wayland() {
|
|
|
|
win.with_state(|state| {
|
|
|
|
tracing::debug!("{:?}", state.resize_state);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-18 21:10:43 -05:00
|
|
|
let windows = self
|
|
|
|
.windows
|
|
|
|
.iter()
|
|
|
|
.filter(|win| {
|
|
|
|
win.with_state(|state| {
|
|
|
|
state
|
|
|
|
.tags
|
|
|
|
.iter()
|
2023-07-19 18:55:22 -05:00
|
|
|
.any(|tag| tag.output(self).is_some_and(|op| &op == output))
|
2023-07-18 21:10:43 -05:00
|
|
|
})
|
|
|
|
})
|
|
|
|
.cloned()
|
|
|
|
.collect::<Vec<_>>();
|
2023-07-10 17:14:37 -05:00
|
|
|
let (render, do_not_render) = output.with_state(|state| {
|
2023-07-11 21:07:51 -05:00
|
|
|
let first_tag = state.focused_tags().next();
|
|
|
|
if let Some(first_tag) = first_tag {
|
|
|
|
first_tag.layout().layout(
|
|
|
|
self.windows.clone(),
|
|
|
|
state.focused_tags().cloned().collect(),
|
2023-07-26 16:01:53 -05:00
|
|
|
self,
|
2023-07-11 16:10:31 -05:00
|
|
|
output,
|
2023-07-11 21:07:51 -05:00
|
|
|
);
|
|
|
|
}
|
2023-07-11 16:10:31 -05:00
|
|
|
|
|
|
|
windows.iter().cloned().partition::<Vec<_>, _>(|win| {
|
2023-07-10 17:14:37 -05:00
|
|
|
win.with_state(|win_state| {
|
2023-08-02 10:13:18 -05:00
|
|
|
win_state
|
|
|
|
.tags
|
|
|
|
.iter()
|
|
|
|
.any(|tag| state.focused_tags().any(|tg| tag == tg))
|
2023-07-09 20:33:25 -05:00
|
|
|
})
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
2023-07-26 16:01:53 -05:00
|
|
|
tracing::debug!(
|
|
|
|
"{} to render, {} to not render",
|
|
|
|
render.len(),
|
|
|
|
do_not_render.len()
|
|
|
|
);
|
|
|
|
|
2023-07-16 20:44:18 -05:00
|
|
|
let clone = render.clone();
|
2023-07-09 20:33:25 -05:00
|
|
|
self.loop_handle.insert_idle(|data| {
|
2023-08-02 10:13:18 -05:00
|
|
|
schedule_on_commit(data, clone.clone(), |dt| {
|
2023-07-16 20:44:18 -05:00
|
|
|
for win in do_not_render {
|
|
|
|
dt.state.space.unmap_elem(&win);
|
2023-07-26 16:01:53 -05:00
|
|
|
if let WindowElement::X11(surface) = win {
|
|
|
|
if !surface.is_override_redirect() {
|
|
|
|
surface.set_mapped(false).expect("failed to unmap x11 win");
|
|
|
|
}
|
|
|
|
}
|
2023-07-02 10:26:07 -05:00
|
|
|
}
|
2023-07-02 18:15:44 -05:00
|
|
|
|
2023-08-02 10:13:18 -05:00
|
|
|
for (win, loc) in clone.into_iter().filter_map(|win| {
|
|
|
|
match win.with_state(|state| state.floating.clone()) {
|
|
|
|
Float::Tiled(_) => None,
|
|
|
|
Float::Floating(loc) => Some((win, loc)),
|
|
|
|
}
|
|
|
|
}) {
|
2023-08-02 19:45:56 -05:00
|
|
|
if let WindowElement::X11(surface) = &win {
|
|
|
|
surface.set_mapped(true).expect("failed to map x11 win");
|
|
|
|
}
|
2023-08-02 10:13:18 -05:00
|
|
|
dt.state.space.map_element(win, loc, false);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2023-07-16 20:44:18 -05:00
|
|
|
}
|
|
|
|
}
|
2023-08-02 10:13:18 -05:00
|
|
|
|
2023-07-16 20:44:18 -05:00
|
|
|
/// Schedule something to be done when windows have finished committing and have become
|
|
|
|
/// idle.
|
2023-07-18 21:10:43 -05:00
|
|
|
pub fn schedule_on_commit<F, B: Backend>(
|
|
|
|
data: &mut CalloopData<B>,
|
2023-07-24 18:59:05 -05:00
|
|
|
windows: Vec<WindowElement>,
|
2023-07-18 21:10:43 -05:00
|
|
|
on_commit: F,
|
|
|
|
) where
|
2023-07-16 20:44:18 -05:00
|
|
|
F: FnOnce(&mut CalloopData<B>) + 'static,
|
|
|
|
{
|
2023-08-01 21:23:30 -05:00
|
|
|
for window in windows.iter().filter(|win| win.alive()) {
|
2023-07-18 21:10:43 -05:00
|
|
|
if window.with_state(|state| !matches!(state.resize_state, WindowResizeState::Idle)) {
|
2023-07-16 20:44:18 -05:00
|
|
|
data.state.loop_handle.insert_idle(|data| {
|
|
|
|
schedule_on_commit(data, windows, on_commit);
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
2023-07-02 18:15:44 -05:00
|
|
|
}
|
2023-07-16 20:44:18 -05:00
|
|
|
|
|
|
|
on_commit(data);
|
2023-07-02 18:15:44 -05:00
|
|
|
}
|
|
|
|
|
2023-07-04 21:27:23 -05:00
|
|
|
impl<B: Backend> State<B> {
|
2023-07-02 18:15:44 -05:00
|
|
|
pub fn init(
|
2023-07-04 21:27:23 -05:00
|
|
|
backend_data: B,
|
2023-07-02 18:15:44 -05:00
|
|
|
display: &mut Display<Self>,
|
|
|
|
loop_signal: LoopSignal,
|
2023-07-04 21:27:23 -05:00
|
|
|
loop_handle: LoopHandle<'static, CalloopData<B>>,
|
2023-07-02 18:15:44 -05:00
|
|
|
) -> Result<Self, Box<dyn Error>> {
|
|
|
|
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>();
|
|
|
|
|
|
|
|
// 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
|
2023-08-06 19:41:48 -05:00
|
|
|
|
|
|
|
// TODO: probably use anyhow or something
|
|
|
|
let socket_source = match PinnacleSocketSource::new(tx_channel) {
|
|
|
|
Ok(source) => source,
|
|
|
|
Err(err) => {
|
|
|
|
tracing::error!("Failed to create the socket source: {err}");
|
|
|
|
Err(err)?
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
})?;
|
|
|
|
|
|
|
|
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
|
|
|
|
});
|
|
|
|
|
|
|
|
if Path::new(&config_path).exists() {
|
2023-08-06 19:41:48 -05:00
|
|
|
let lua_path = std::env::var("LUA_PATH").unwrap_or_else(|_| {
|
|
|
|
tracing::info!("LUA_PATH was not set, using empty string");
|
|
|
|
"".to_string()
|
|
|
|
});
|
2023-07-02 18:15:44 -05:00
|
|
|
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}");
|
|
|
|
|
2023-08-06 19:41:48 -05:00
|
|
|
let lua_cpath = std::env::var("LUA_CPATH").unwrap_or_else(|_| {
|
|
|
|
tracing::info!("LUA_CPATH was not set, using empty string");
|
|
|
|
"".to_string()
|
|
|
|
});
|
2023-07-02 18:15:44 -05:00
|
|
|
let new_lua_cpath = format!("{local_lua_path}/lib/?.so;{lua_cpath}");
|
|
|
|
|
2023-08-06 19:41:48 -05:00
|
|
|
if let Err(err) = std::process::Command::new("lua")
|
2023-07-02 18:15:44 -05:00
|
|
|
.arg(config_path)
|
|
|
|
.env("LUA_PATH", new_lua_path)
|
|
|
|
.env("LUA_CPATH", new_lua_cpath)
|
|
|
|
.spawn()
|
2023-08-06 19:41:48 -05:00
|
|
|
{
|
|
|
|
tracing::error!("Failed to start Lua: {err}");
|
|
|
|
return Err(err)?;
|
|
|
|
}
|
2023-07-02 18:15:44 -05:00
|
|
|
} 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!(),
|
2023-07-02 10:26:07 -05:00
|
|
|
})
|
2023-07-24 11:43:47 -05:00
|
|
|
.expect("failed to insert rx_channel into loop");
|
2023-07-02 10:26:07 -05:00
|
|
|
});
|
|
|
|
|
2023-07-24 18:59:05 -05:00
|
|
|
tracing::debug!("before xwayland");
|
2023-07-24 11:43:47 -05:00
|
|
|
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(),
|
2023-07-24 11:43:47 -05:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
});
|
2023-07-24 11:43:47 -05:00
|
|
|
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-24 11:43:47 -05:00
|
|
|
|
2023-07-02 18:15:44 -05:00
|
|
|
Ok(Self {
|
|
|
|
backend_data,
|
|
|
|
loop_signal,
|
|
|
|
loop_handle,
|
2023-07-27 17:31:40 -05:00
|
|
|
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,
|
|
|
|
),
|
2023-08-02 15:12:10 -05:00
|
|
|
primary_selection_state: PrimarySelectionState::new::<Self>(&display_handle),
|
|
|
|
|
2023-07-02 18:15:44 -05:00
|
|
|
input_state: InputState::new(),
|
|
|
|
api_state: ApiState::new(),
|
|
|
|
focus_state: FocusState::new(),
|
2023-07-02 17:10:15 -05:00
|
|
|
|
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,
|
|
|
|
|
|
|
|
windows: vec![],
|
2023-07-11 11:59:38 -05:00
|
|
|
output_callback_ids: vec![],
|
2023-07-24 11:43:47 -05:00
|
|
|
|
|
|
|
xwayland,
|
|
|
|
xwm: None,
|
|
|
|
xdisplay: None,
|
2023-07-02 18:15:44 -05:00
|
|
|
})
|
2023-07-02 10:26:07 -05:00
|
|
|
}
|
2023-06-05 21:08:37 -05:00
|
|
|
}
|
|
|
|
|
2023-06-09 20:29:17 -05:00
|
|
|
pub struct CalloopData<B: Backend> {
|
|
|
|
pub display: Display<State<B>>,
|
|
|
|
pub state: State<B>,
|
2023-06-02 16:01:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct ClientState {
|
|
|
|
pub compositor_state: CompositorClientState,
|
|
|
|
}
|
|
|
|
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-06-11 17:56:34 -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()
|
|
|
|
}
|
|
|
|
}
|
2023-07-10 17:14:37 -05:00
|
|
|
|
|
|
|
pub trait WithState {
|
|
|
|
type State: Default;
|
|
|
|
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())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|