mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2025-01-18 22:26:12 +01:00
Completely rip out the old msgpack stuff
Did this break anything? ¯\_(ツ)_/¯
This commit is contained in:
parent
9acd0e5ce3
commit
0b88ad298b
12 changed files with 1911 additions and 3594 deletions
30
Cargo.lock
generated
30
Cargo.lock
generated
|
@ -1489,12 +1489,6 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
|
@ -1556,8 +1550,6 @@ dependencies = [
|
|||
"pinnacle-api-defs",
|
||||
"prost",
|
||||
"prost-types",
|
||||
"rmp",
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
"shellexpand",
|
||||
"smithay",
|
||||
|
@ -1868,28 +1860,6 @@ version = "0.8.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "rmp"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"num-traits",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp-serde"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rmp",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.23"
|
||||
|
|
|
@ -19,8 +19,6 @@ thiserror = "1"
|
|||
xcursor = { version = "0.3", optional = true }
|
||||
image = { version = "0.24", default-features = false, optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
rmp = { version = "0.8.12" }
|
||||
rmp-serde = { version = "1.1.2" }
|
||||
x11rb = { version = "0.13", default-features = false, features = ["composite"], optional = true }
|
||||
shellexpand = "3.1.0"
|
||||
toml = "0.8"
|
||||
|
|
2030
src/api.rs
2030
src/api.rs
File diff suppressed because it is too large
Load diff
|
@ -1,824 +0,0 @@
|
|||
use std::{ffi::OsString, process::Stdio};
|
||||
|
||||
use smithay::{
|
||||
desktop::space::SpaceElement,
|
||||
input::keyboard::XkbConfig,
|
||||
reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge,
|
||||
utils::{Point, Rectangle, SERIAL_COUNTER},
|
||||
wayland::{compositor, shell::xdg::XdgToplevelSurfaceData},
|
||||
};
|
||||
use sysinfo::ProcessRefreshKind;
|
||||
use tokio::io::AsyncBufReadExt;
|
||||
|
||||
use crate::{
|
||||
api::msg::{
|
||||
Args, CallbackId, KeyIntOrString, Msg, OutgoingMsg, Request, RequestId, RequestResponse,
|
||||
},
|
||||
config::ConnectorSavedState,
|
||||
focus::FocusTarget,
|
||||
tag::Tag,
|
||||
window::WindowElement,
|
||||
};
|
||||
|
||||
use crate::state::{State, WithState};
|
||||
|
||||
impl State {
|
||||
/// Handle a client message.
|
||||
pub fn handle_msg(&mut self, msg: Msg) {
|
||||
tracing::trace!("Got {msg:?}");
|
||||
|
||||
match msg {
|
||||
Msg::SetKeybind {
|
||||
key,
|
||||
modifiers,
|
||||
callback_id,
|
||||
} => {
|
||||
let key = match key {
|
||||
KeyIntOrString::Int(num) => {
|
||||
tracing::info!("set keybind: {:?}, raw {}", modifiers, num);
|
||||
num
|
||||
}
|
||||
KeyIntOrString::String(s) => {
|
||||
if s.chars().count() == 1 {
|
||||
let Some(ch) = s.chars().next() else { unreachable!() };
|
||||
let raw = xkbcommon::xkb::Keysym::from_char(ch).raw();
|
||||
tracing::info!("set keybind: {:?}, {:?} (raw {})", modifiers, ch, raw);
|
||||
raw
|
||||
} else {
|
||||
let raw = xkbcommon::xkb::keysym_from_name(
|
||||
&s,
|
||||
xkbcommon::xkb::KEYSYM_NO_FLAGS,
|
||||
)
|
||||
.raw();
|
||||
tracing::info!("set keybind: {:?}, {:?}", modifiers, raw);
|
||||
raw
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.input_state
|
||||
.keybinds
|
||||
.insert((modifiers.into(), key.into()), callback_id);
|
||||
}
|
||||
Msg::SetMousebind {
|
||||
modifiers,
|
||||
button,
|
||||
edge,
|
||||
callback_id,
|
||||
} => {
|
||||
// TODO: maybe validate/parse valid codes?
|
||||
self.input_state
|
||||
.mousebinds
|
||||
.insert((modifiers.into(), button, edge), callback_id);
|
||||
}
|
||||
Msg::CloseWindow { window_id } => {
|
||||
if let Some(window) = window_id.window(self) {
|
||||
match window {
|
||||
WindowElement::Wayland(window) => window.toplevel().send_close(),
|
||||
WindowElement::X11(surface) => {
|
||||
surface.close().expect("failed to close x11 win");
|
||||
}
|
||||
WindowElement::X11OverrideRedirect(_) => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Msg::Spawn {
|
||||
command,
|
||||
callback_id,
|
||||
} => {
|
||||
self.handle_spawn(command, callback_id);
|
||||
}
|
||||
Msg::SpawnOnce {
|
||||
command,
|
||||
callback_id,
|
||||
} => {
|
||||
self.system_processes
|
||||
.refresh_processes_specifics(ProcessRefreshKind::new());
|
||||
|
||||
let Some(arg0) = command.first() else {
|
||||
tracing::warn!("No command specified for `SpawnOnce`");
|
||||
return;
|
||||
};
|
||||
|
||||
let compositor_pid = std::process::id();
|
||||
let already_running =
|
||||
self.system_processes
|
||||
.processes_by_exact_name(arg0)
|
||||
.any(|proc| {
|
||||
proc.parent()
|
||||
.is_some_and(|parent_pid| parent_pid.as_u32() == compositor_pid)
|
||||
});
|
||||
|
||||
if !already_running {
|
||||
self.handle_spawn(command, callback_id);
|
||||
}
|
||||
}
|
||||
Msg::SetEnv { key, value } => std::env::set_var(key, value),
|
||||
|
||||
Msg::SetWindowSize {
|
||||
window_id,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
let Some(window) = window_id.window(self) else { return };
|
||||
|
||||
// TODO: tiled vs floating
|
||||
// FIXME: this will map unmapped windows at 0,0
|
||||
let window_loc = self
|
||||
.space
|
||||
.element_location(&window)
|
||||
.unwrap_or((0, 0).into());
|
||||
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;
|
||||
}
|
||||
use crate::window::window_state::FloatingOrTiled;
|
||||
|
||||
let rect = Rectangle::from_loc_and_size(window_loc, window_size);
|
||||
window.change_geometry(rect);
|
||||
window.with_state(|state| {
|
||||
state.floating_or_tiled = match state.floating_or_tiled {
|
||||
FloatingOrTiled::Floating(_) => FloatingOrTiled::Floating(rect),
|
||||
FloatingOrTiled::Tiled(_) => FloatingOrTiled::Tiled(Some(rect)),
|
||||
}
|
||||
});
|
||||
|
||||
for output in self.space.outputs_for_element(&window) {
|
||||
self.update_windows(&output);
|
||||
self.schedule_render(&output);
|
||||
}
|
||||
}
|
||||
Msg::MoveWindowToTag { window_id, tag_id } => {
|
||||
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.update_windows(&output);
|
||||
self.schedule_render(&output);
|
||||
}
|
||||
Msg::ToggleTagOnWindow { window_id, tag_id } => {
|
||||
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());
|
||||
}
|
||||
});
|
||||
|
||||
let Some(output) = tag.output(self) else { return };
|
||||
self.update_windows(&output);
|
||||
self.schedule_render(&output);
|
||||
}
|
||||
Msg::ToggleFloating { window_id } => {
|
||||
let Some(window) = window_id.window(self) else { return };
|
||||
window.toggle_floating();
|
||||
|
||||
let Some(output) = window.output(self) else { return };
|
||||
self.update_windows(&output);
|
||||
self.schedule_render(&output);
|
||||
}
|
||||
Msg::ToggleFullscreen { window_id } => {
|
||||
let Some(window) = window_id.window(self) else { return };
|
||||
window.toggle_fullscreen();
|
||||
|
||||
let Some(output) = window.output(self) else { return };
|
||||
self.update_windows(&output);
|
||||
self.schedule_render(&output);
|
||||
}
|
||||
Msg::ToggleMaximized { window_id } => {
|
||||
let Some(window) = window_id.window(self) else { return };
|
||||
window.toggle_maximized();
|
||||
|
||||
let Some(output) = window.output(self) else { return };
|
||||
self.update_windows(&output);
|
||||
self.schedule_render(&output);
|
||||
}
|
||||
Msg::AddWindowRule { cond, rule } => {
|
||||
self.config.window_rules.push((cond, rule));
|
||||
}
|
||||
Msg::WindowMoveGrab { button } => {
|
||||
let Some((FocusTarget::Window(window), _)) =
|
||||
self.focus_target_under(self.pointer_location)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(wl_surf) = window.wl_surface() else { return };
|
||||
let seat = self.seat.clone();
|
||||
|
||||
// We use the server one and not the client because windows like Steam don't provide
|
||||
// GrabStartData, so we need to create it ourselves.
|
||||
crate::grab::move_grab::move_request_server(
|
||||
self,
|
||||
&wl_surf,
|
||||
&seat,
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
button,
|
||||
);
|
||||
}
|
||||
Msg::WindowResizeGrab { button } => {
|
||||
// TODO: in the future, there may be movable layer surfaces
|
||||
let pointer_loc = self.pointer_location;
|
||||
let Some((FocusTarget::Window(window), window_loc)) =
|
||||
self.focus_target_under(pointer_loc)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(wl_surf) = window.wl_surface() else { return };
|
||||
|
||||
let window_geometry = window.geometry();
|
||||
let window_x = window_loc.x as f64;
|
||||
let window_y = window_loc.y as f64;
|
||||
let window_width = window_geometry.size.w as f64;
|
||||
let window_height = window_geometry.size.h as f64;
|
||||
let half_width = window_x + window_width / 2.0;
|
||||
let half_height = window_y + window_height / 2.0;
|
||||
let full_width = window_x + window_width;
|
||||
let full_height = window_y + window_height;
|
||||
|
||||
let edges = match pointer_loc {
|
||||
Point { x, y, .. }
|
||||
if (window_x..=half_width).contains(&x)
|
||||
&& (window_y..=half_height).contains(&y) =>
|
||||
{
|
||||
ResizeEdge::TopLeft
|
||||
}
|
||||
Point { x, y, .. }
|
||||
if (half_width..=full_width).contains(&x)
|
||||
&& (window_y..=half_height).contains(&y) =>
|
||||
{
|
||||
ResizeEdge::TopRight
|
||||
}
|
||||
Point { x, y, .. }
|
||||
if (window_x..=half_width).contains(&x)
|
||||
&& (half_height..=full_height).contains(&y) =>
|
||||
{
|
||||
ResizeEdge::BottomLeft
|
||||
}
|
||||
Point { x, y, .. }
|
||||
if (half_width..=full_width).contains(&x)
|
||||
&& (half_height..=full_height).contains(&y) =>
|
||||
{
|
||||
ResizeEdge::BottomRight
|
||||
}
|
||||
_ => ResizeEdge::None,
|
||||
};
|
||||
|
||||
crate::grab::resize_grab::resize_request_server(
|
||||
self,
|
||||
&wl_surf,
|
||||
&self.seat.clone(),
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
edges.into(),
|
||||
button,
|
||||
);
|
||||
}
|
||||
|
||||
// Tags ----------------------------------------
|
||||
Msg::ToggleTag { tag_id } => {
|
||||
tracing::debug!("ToggleTag");
|
||||
if let Some(tag) = tag_id.tag(self) {
|
||||
tag.set_active(!tag.active());
|
||||
if let Some(output) = tag.output(self) {
|
||||
self.update_windows(&output);
|
||||
self.update_focus(&output);
|
||||
self.schedule_render(&output);
|
||||
}
|
||||
}
|
||||
}
|
||||
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.update_windows(&output);
|
||||
self.update_focus(&output);
|
||||
self.schedule_render(&output);
|
||||
}
|
||||
Msg::AddTags {
|
||||
output_name,
|
||||
tag_names,
|
||||
} => {
|
||||
let new_tags = tag_names.into_iter().map(Tag::new).collect::<Vec<_>>();
|
||||
if let Some(saved_state) = self.config.connector_saved_states.get_mut(&output_name)
|
||||
{
|
||||
let mut tags = saved_state.tags.clone();
|
||||
tags.extend(new_tags.clone());
|
||||
saved_state.tags = tags;
|
||||
} else {
|
||||
self.config.connector_saved_states.insert(
|
||||
output_name.clone(),
|
||||
ConnectorSavedState {
|
||||
tags: new_tags.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(output) = self
|
||||
.space
|
||||
.outputs()
|
||||
.find(|output| output.name() == output_name.0)
|
||||
{
|
||||
output.with_state(|state| {
|
||||
state.tags.extend(new_tags.clone());
|
||||
tracing::debug!("tags added, are now {:?}", state.tags);
|
||||
});
|
||||
|
||||
// replace tags that windows have that are the same id
|
||||
// (this should only happen on config reload)
|
||||
for tag in new_tags {
|
||||
for window in self.windows.iter() {
|
||||
window.with_state(|state| {
|
||||
for win_tag in state.tags.iter_mut() {
|
||||
if win_tag.id() == tag.id() {
|
||||
*win_tag = tag.clone();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Msg::RemoveTags { tag_ids } => {
|
||||
let tags = tag_ids
|
||||
.into_iter()
|
||||
.filter_map(|tag_id| tag_id.tag(self))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for tag in tags {
|
||||
for saved_state in self.config.connector_saved_states.values_mut() {
|
||||
saved_state.tags.retain(|tg| tg != &tag);
|
||||
}
|
||||
let Some(output) = tag.output(self) else { continue };
|
||||
output.with_state(|state| {
|
||||
state.tags.retain(|tg| tg != &tag);
|
||||
});
|
||||
}
|
||||
}
|
||||
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.update_windows(&output);
|
||||
|
||||
self.schedule_render(&output);
|
||||
}
|
||||
|
||||
Msg::ConnectForAllOutputs { callback_id } => {
|
||||
let stream = self
|
||||
.api_state
|
||||
.stream
|
||||
.as_ref()
|
||||
.expect("stream doesn't exist");
|
||||
|
||||
for output in self.space.outputs() {
|
||||
crate::api::send_to_client(
|
||||
&mut stream.lock().expect("couldn't lock stream"),
|
||||
&OutgoingMsg::CallCallback {
|
||||
callback_id,
|
||||
args: Some(Args::ConnectForAllOutputs {
|
||||
output_name: output.name(),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.expect("Send to client failed");
|
||||
}
|
||||
|
||||
self.config.output_callback_ids.push(callback_id);
|
||||
}
|
||||
Msg::SetOutputLocation { output_name, x, y } => {
|
||||
if let Some(saved_state) = self.config.connector_saved_states.get_mut(&output_name)
|
||||
{
|
||||
if let Some(x) = x {
|
||||
saved_state.loc.x = x;
|
||||
}
|
||||
if let Some(y) = y {
|
||||
saved_state.loc.y = y;
|
||||
}
|
||||
} else {
|
||||
self.config.connector_saved_states.insert(
|
||||
output_name.clone(),
|
||||
ConnectorSavedState {
|
||||
loc: (x.unwrap_or_default(), y.unwrap_or_default()).into(),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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.update_windows(&output);
|
||||
}
|
||||
|
||||
Msg::Quit => {
|
||||
tracing::info!("Quitting Pinnacle");
|
||||
self.shutdown();
|
||||
}
|
||||
|
||||
Msg::SetXkbConfig {
|
||||
rules,
|
||||
variant,
|
||||
layout,
|
||||
model,
|
||||
options,
|
||||
} => {
|
||||
let new_config = XkbConfig {
|
||||
rules: &rules.unwrap_or_default(),
|
||||
model: &model.unwrap_or_default(),
|
||||
layout: &layout.unwrap_or_default(),
|
||||
variant: &variant.unwrap_or_default(),
|
||||
options,
|
||||
};
|
||||
if let Some(kb) = self.seat.get_keyboard() {
|
||||
if let Err(err) = kb.set_xkb_config(self, new_config) {
|
||||
tracing::error!("Failed to set xkbconfig: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Msg::SetLibinputSetting(setting) => {
|
||||
for device in self.input_state.libinput_devices.iter_mut() {
|
||||
// We're just gonna indiscriminately apply everything and ignore errors
|
||||
setting.apply_to_device(device);
|
||||
}
|
||||
|
||||
self.input_state.libinput_settings.push(setting);
|
||||
}
|
||||
|
||||
Msg::Request {
|
||||
request_id,
|
||||
request,
|
||||
} => {
|
||||
self.handle_request(request_id, request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a client request.
|
||||
fn handle_request(&mut self, request_id: RequestId, request: Request) {
|
||||
let stream = self
|
||||
.api_state
|
||||
.stream
|
||||
.clone() // clone due to use of self below
|
||||
.expect("Stream doesn't exist");
|
||||
let mut stream = stream.lock().expect("Couldn't lock stream");
|
||||
|
||||
match request {
|
||||
Request::GetWindows => {
|
||||
let window_ids = self
|
||||
.windows
|
||||
.iter()
|
||||
.map(|win| win.with_state(|state| state.id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
request_id,
|
||||
response: RequestResponse::Windows { window_ids },
|
||||
},
|
||||
)
|
||||
.expect("Couldn't send to client");
|
||||
}
|
||||
Request::GetWindowProps { window_id } => {
|
||||
let window = window_id.window(self);
|
||||
|
||||
let size = window
|
||||
.as_ref()
|
||||
.map(|win| (win.geometry().size.w, win.geometry().size.h));
|
||||
|
||||
let loc = window
|
||||
.as_ref()
|
||||
.and_then(|win| self.space.element_location(win))
|
||||
.map(|loc| (loc.x, loc.y));
|
||||
|
||||
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) | WindowElement::X11OverrideRedirect(surface) => {
|
||||
(Some(surface.class()), Some(surface.title()))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
});
|
||||
|
||||
let focused = window.as_ref().and_then(|win| {
|
||||
let output = win.output(self)?;
|
||||
self.focused_window(&output).map(|foc_win| win == &foc_win)
|
||||
});
|
||||
|
||||
let floating = window
|
||||
.as_ref()
|
||||
.map(|win| win.with_state(|state| state.floating_or_tiled.is_floating()));
|
||||
|
||||
let fullscreen_or_maximized = window
|
||||
.as_ref()
|
||||
.map(|win| win.with_state(|state| state.fullscreen_or_maximized));
|
||||
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
request_id,
|
||||
response: RequestResponse::WindowProps {
|
||||
size,
|
||||
loc,
|
||||
class,
|
||||
title,
|
||||
focused,
|
||||
floating,
|
||||
fullscreen_or_maximized,
|
||||
},
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetOutputs => {
|
||||
let output_names = self
|
||||
.space
|
||||
.outputs()
|
||||
.map(|output| output.name())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
request_id,
|
||||
response: RequestResponse::Outputs { output_names },
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetOutputProps { output_name } => {
|
||||
let output = self
|
||||
.space
|
||||
.outputs()
|
||||
.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
|
||||
.focus_state
|
||||
.focused_output
|
||||
.as_ref()
|
||||
.and_then(|foc_op| output.map(|op| op == foc_op));
|
||||
|
||||
let tag_ids = output.as_ref().map(|output| {
|
||||
output.with_state(|state| {
|
||||
state.tags.iter().map(|tag| tag.id()).collect::<Vec<_>>()
|
||||
})
|
||||
});
|
||||
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
request_id,
|
||||
response: RequestResponse::OutputProps {
|
||||
make,
|
||||
model,
|
||||
loc,
|
||||
res,
|
||||
refresh_rate,
|
||||
physical_size,
|
||||
focused,
|
||||
tag_ids,
|
||||
},
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetTags => {
|
||||
let tag_ids = self
|
||||
.space
|
||||
.outputs()
|
||||
.flat_map(|op| op.with_state(|state| state.tags.clone()))
|
||||
.map(|tag| tag.id())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
request_id,
|
||||
response: RequestResponse::Tags { tag_ids },
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetTagProps { tag_id } => {
|
||||
let tag = tag_id.tag(self);
|
||||
|
||||
let output_name = tag
|
||||
.as_ref()
|
||||
.and_then(|tag| tag.output(self))
|
||||
.map(|output| output.name());
|
||||
|
||||
let active = tag.as_ref().map(|tag| tag.active());
|
||||
let name = tag.as_ref().map(|tag| tag.name());
|
||||
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
request_id,
|
||||
response: RequestResponse::TagProps {
|
||||
active,
|
||||
name,
|
||||
output_name,
|
||||
},
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Welcome to indentation hell
|
||||
/// Handle a received spawn command by spawning the command and hooking up any callbacks.
|
||||
pub fn handle_spawn(&self, command: Vec<String>, callback_id: Option<CallbackId>) {
|
||||
let mut command = command.into_iter();
|
||||
let Some(program) = command.next() else {
|
||||
// TODO: notify that command was nothing
|
||||
tracing::warn!("got an empty command");
|
||||
return;
|
||||
};
|
||||
|
||||
let program = OsString::from(program);
|
||||
let Ok(mut child) = tokio::process::Command::new(&program)
|
||||
.envs(
|
||||
[("WAYLAND_DISPLAY", self.socket_name.clone())]
|
||||
.into_iter()
|
||||
.chain(self.xdisplay.map(|xdisp| ("DISPLAY", format!(":{xdisp}")))),
|
||||
)
|
||||
.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.clone().expect("Stream doesn't exist");
|
||||
let stream_err = stream_out.clone();
|
||||
let stream_exit = stream_out.clone();
|
||||
|
||||
if let Some(stdout) = stdout {
|
||||
let future = async move {
|
||||
let mut reader = tokio::io::BufReader::new(stdout).lines();
|
||||
while let Ok(Some(line)) = reader.next_line().await {
|
||||
let msg = OutgoingMsg::CallCallback {
|
||||
callback_id,
|
||||
args: Some(Args::Spawn {
|
||||
stdout: Some(line),
|
||||
stderr: None,
|
||||
exit_code: None,
|
||||
exit_msg: None,
|
||||
}),
|
||||
};
|
||||
|
||||
crate::api::send_to_client(
|
||||
&mut stream_out.lock().expect("Couldn't lock stream"),
|
||||
&msg,
|
||||
)
|
||||
.expect("Send to client failed"); // TODO: notify instead of crash
|
||||
}
|
||||
};
|
||||
|
||||
tokio::spawn(future);
|
||||
}
|
||||
|
||||
if let Some(stderr) = stderr {
|
||||
let future = async move {
|
||||
let mut reader = tokio::io::BufReader::new(stderr).lines();
|
||||
while let Ok(Some(line)) = reader.next_line().await {
|
||||
let msg = OutgoingMsg::CallCallback {
|
||||
callback_id,
|
||||
args: Some(Args::Spawn {
|
||||
stdout: None,
|
||||
stderr: Some(line),
|
||||
exit_code: None,
|
||||
exit_msg: None,
|
||||
}),
|
||||
};
|
||||
|
||||
crate::api::send_to_client(
|
||||
&mut stream_err.lock().expect("Couldn't lock stream"),
|
||||
&msg,
|
||||
)
|
||||
.expect("Send to client failed"); // TODO: notify instead of crash
|
||||
}
|
||||
};
|
||||
|
||||
tokio::spawn(future);
|
||||
}
|
||||
|
||||
let future = async move {
|
||||
match child.wait().await {
|
||||
Ok(exit_status) => {
|
||||
let msg = OutgoingMsg::CallCallback {
|
||||
callback_id,
|
||||
args: Some(Args::Spawn {
|
||||
stdout: None,
|
||||
stderr: None,
|
||||
exit_code: exit_status.code(),
|
||||
exit_msg: Some(exit_status.to_string()),
|
||||
}),
|
||||
};
|
||||
|
||||
crate::api::send_to_client(
|
||||
&mut stream_exit.lock().expect("Couldn't lock stream"),
|
||||
&msg,
|
||||
)
|
||||
.expect("Send to client failed"); // TODO: notify instead of crash
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!("child wait() err: {err}");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tokio::spawn(future);
|
||||
}
|
||||
}
|
||||
}
|
335
src/api/msg.rs
335
src/api/msg.rs
|
@ -1,335 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// The MessagePack format for these is a one-element map where the element's key is the enum name and its
|
||||
// value is a map of the enum's values
|
||||
|
||||
use smithay::input::keyboard::ModifiersState;
|
||||
|
||||
use crate::{
|
||||
input::libinput::LibinputSetting,
|
||||
layout::Layout,
|
||||
output::OutputName,
|
||||
tag::TagId,
|
||||
window::{
|
||||
rules::{WindowRule, WindowRuleCondition},
|
||||
window_state::{FullscreenOrMaximized, WindowId},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)]
|
||||
pub struct CallbackId(pub u32);
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
|
||||
pub enum KeyIntOrString {
|
||||
Int(u32),
|
||||
String(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MouseEdge {
|
||||
Press,
|
||||
Release,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub enum Msg {
|
||||
// Input
|
||||
SetKeybind {
|
||||
key: KeyIntOrString,
|
||||
modifiers: Vec<Modifier>,
|
||||
callback_id: CallbackId,
|
||||
},
|
||||
SetMousebind {
|
||||
modifiers: Vec<Modifier>,
|
||||
button: u32,
|
||||
edge: MouseEdge,
|
||||
callback_id: CallbackId,
|
||||
},
|
||||
|
||||
// Window management
|
||||
CloseWindow {
|
||||
window_id: WindowId,
|
||||
},
|
||||
SetWindowSize {
|
||||
window_id: WindowId,
|
||||
#[serde(default)]
|
||||
width: Option<i32>,
|
||||
#[serde(default)]
|
||||
height: Option<i32>,
|
||||
},
|
||||
MoveWindowToTag {
|
||||
window_id: WindowId,
|
||||
tag_id: TagId,
|
||||
},
|
||||
ToggleTagOnWindow {
|
||||
window_id: WindowId,
|
||||
tag_id: TagId,
|
||||
},
|
||||
ToggleFloating {
|
||||
window_id: WindowId,
|
||||
},
|
||||
ToggleFullscreen {
|
||||
window_id: WindowId,
|
||||
},
|
||||
ToggleMaximized {
|
||||
window_id: WindowId,
|
||||
},
|
||||
AddWindowRule {
|
||||
cond: WindowRuleCondition,
|
||||
rule: WindowRule,
|
||||
},
|
||||
WindowMoveGrab {
|
||||
button: u32,
|
||||
},
|
||||
WindowResizeGrab {
|
||||
button: u32,
|
||||
},
|
||||
|
||||
// Tag management
|
||||
ToggleTag {
|
||||
tag_id: TagId,
|
||||
},
|
||||
SwitchToTag {
|
||||
tag_id: TagId,
|
||||
},
|
||||
AddTags {
|
||||
/// The name of the output you want these tags on.
|
||||
output_name: OutputName,
|
||||
tag_names: Vec<String>,
|
||||
},
|
||||
RemoveTags {
|
||||
/// The name of the output you want these tags removed from.
|
||||
tag_ids: Vec<TagId>,
|
||||
},
|
||||
SetLayout {
|
||||
tag_id: TagId,
|
||||
layout: Layout,
|
||||
},
|
||||
|
||||
// Output management
|
||||
ConnectForAllOutputs {
|
||||
callback_id: CallbackId,
|
||||
},
|
||||
SetOutputLocation {
|
||||
output_name: OutputName,
|
||||
#[serde(default)]
|
||||
x: Option<i32>,
|
||||
#[serde(default)]
|
||||
y: Option<i32>,
|
||||
},
|
||||
|
||||
// Process management
|
||||
/// Spawn a program with an optional callback.
|
||||
Spawn {
|
||||
command: Vec<String>,
|
||||
#[serde(default)]
|
||||
callback_id: Option<CallbackId>,
|
||||
},
|
||||
SpawnOnce {
|
||||
command: Vec<String>,
|
||||
#[serde(default)]
|
||||
callback_id: Option<CallbackId>,
|
||||
},
|
||||
SetEnv {
|
||||
key: String,
|
||||
value: String,
|
||||
},
|
||||
|
||||
// Pinnacle management
|
||||
/// Quit the compositor.
|
||||
Quit,
|
||||
|
||||
// Input management
|
||||
SetXkbConfig {
|
||||
#[serde(default)]
|
||||
rules: Option<String>,
|
||||
#[serde(default)]
|
||||
variant: Option<String>,
|
||||
#[serde(default)]
|
||||
layout: Option<String>,
|
||||
#[serde(default)]
|
||||
model: Option<String>,
|
||||
#[serde(default)]
|
||||
options: Option<String>,
|
||||
},
|
||||
|
||||
SetLibinputSetting(LibinputSetting),
|
||||
|
||||
Request {
|
||||
request_id: RequestId,
|
||||
request: Request,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct RequestId(u32);
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
/// Messages that require a server response, usually to provide some data.
|
||||
pub enum Request {
|
||||
// Windows
|
||||
GetWindows,
|
||||
GetWindowProps { window_id: WindowId },
|
||||
// Outputs
|
||||
GetOutputs,
|
||||
GetOutputProps { output_name: String },
|
||||
// Tags
|
||||
GetTags,
|
||||
GetTagProps { tag_id: TagId },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Modifier {
|
||||
Shift = 0b0000_0001,
|
||||
Ctrl = 0b0000_0010,
|
||||
Alt = 0b0000_0100,
|
||||
Super = 0b0000_1000,
|
||||
}
|
||||
|
||||
/// A bitmask of [`Modifier`]s for the purpose of hashing.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub struct ModifierMask(u8);
|
||||
|
||||
impl From<Vec<Modifier>> for ModifierMask {
|
||||
fn from(value: Vec<Modifier>) -> Self {
|
||||
let value = value.into_iter();
|
||||
let mut mask: u8 = 0b0000_0000;
|
||||
for modifier in value {
|
||||
mask |= modifier as u8;
|
||||
}
|
||||
Self(mask)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[Modifier]> for ModifierMask {
|
||||
fn from(value: &[Modifier]) -> Self {
|
||||
let value = value.iter();
|
||||
let mut mask: u8 = 0b0000_0000;
|
||||
for modifier in value {
|
||||
mask |= *modifier as u8;
|
||||
}
|
||||
Self(mask)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ModifiersState> for ModifierMask {
|
||||
fn from(state: ModifiersState) -> Self {
|
||||
let mut mask: u8 = 0b0000_0000;
|
||||
if state.shift {
|
||||
mask |= Modifier::Shift as u8;
|
||||
}
|
||||
if state.ctrl {
|
||||
mask |= Modifier::Ctrl as u8;
|
||||
}
|
||||
if state.alt {
|
||||
mask |= Modifier::Alt as u8;
|
||||
}
|
||||
if state.logo {
|
||||
mask |= Modifier::Super as u8;
|
||||
}
|
||||
Self(mask)
|
||||
}
|
||||
}
|
||||
|
||||
impl ModifierMask {
|
||||
#[allow(dead_code)]
|
||||
pub fn values(self) -> Vec<Modifier> {
|
||||
let mut res = Vec::<Modifier>::new();
|
||||
if self.0 & Modifier::Shift as u8 == Modifier::Shift as u8 {
|
||||
res.push(Modifier::Shift);
|
||||
}
|
||||
if self.0 & Modifier::Ctrl as u8 == Modifier::Ctrl as u8 {
|
||||
res.push(Modifier::Ctrl);
|
||||
}
|
||||
if self.0 & Modifier::Alt as u8 == Modifier::Alt as u8 {
|
||||
res.push(Modifier::Alt);
|
||||
}
|
||||
if self.0 & Modifier::Super as u8 == Modifier::Super as u8 {
|
||||
res.push(Modifier::Super);
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// Messages sent from the server to the client.
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum OutgoingMsg {
|
||||
CallCallback {
|
||||
callback_id: CallbackId,
|
||||
#[serde(default)]
|
||||
args: Option<Args>,
|
||||
},
|
||||
RequestResponse {
|
||||
request_id: RequestId,
|
||||
response: RequestResponse,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Args {
|
||||
/// Send a message with lines from the spawned process.
|
||||
Spawn {
|
||||
#[serde(default)]
|
||||
stdout: Option<String>,
|
||||
#[serde(default)]
|
||||
stderr: Option<String>,
|
||||
#[serde(default)]
|
||||
exit_code: Option<i32>,
|
||||
#[serde(default)]
|
||||
exit_msg: Option<String>,
|
||||
},
|
||||
ConnectForAllOutputs {
|
||||
output_name: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum RequestResponse {
|
||||
Window {
|
||||
window_id: Option<WindowId>,
|
||||
},
|
||||
Windows {
|
||||
window_ids: Vec<WindowId>,
|
||||
},
|
||||
WindowProps {
|
||||
size: Option<(i32, i32)>,
|
||||
loc: Option<(i32, i32)>,
|
||||
class: Option<String>,
|
||||
title: Option<String>,
|
||||
focused: Option<bool>,
|
||||
floating: Option<bool>,
|
||||
fullscreen_or_maximized: Option<FullscreenOrMaximized>,
|
||||
},
|
||||
Output {
|
||||
output_name: Option<String>,
|
||||
},
|
||||
Outputs {
|
||||
output_names: Vec<String>,
|
||||
},
|
||||
OutputProps {
|
||||
/// The make of the output.
|
||||
make: Option<String>,
|
||||
/// The model of the output.
|
||||
model: Option<String>,
|
||||
/// The location of the output in the space.
|
||||
loc: Option<(i32, i32)>,
|
||||
/// The resolution of the output.
|
||||
res: Option<(i32, i32)>,
|
||||
/// The refresh rate of the output.
|
||||
refresh_rate: Option<i32>,
|
||||
/// The size of the output, in millimeters.
|
||||
physical_size: Option<(i32, i32)>,
|
||||
/// Whether the output is focused or not.
|
||||
focused: Option<bool>,
|
||||
tag_ids: Option<Vec<TagId>>,
|
||||
},
|
||||
Tags {
|
||||
tag_ids: Vec<TagId>,
|
||||
},
|
||||
TagProps {
|
||||
active: Option<bool>,
|
||||
name: Option<String>,
|
||||
output_name: Option<String>,
|
||||
},
|
||||
}
|
1887
src/api/protocol.rs
1887
src/api/protocol.rs
File diff suppressed because it is too large
Load diff
|
@ -72,7 +72,6 @@ use smithay_drm_extras::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
api::msg::{Args, OutgoingMsg},
|
||||
backend::Backend,
|
||||
config::ConnectorSavedState,
|
||||
output::OutputName,
|
||||
|
@ -985,36 +984,11 @@ impl State {
|
|||
output.with_state(|state| state.tags = tags.clone());
|
||||
} else {
|
||||
// Run any output callbacks
|
||||
let clone = output.clone();
|
||||
self.schedule(
|
||||
|dt| dt.state.api_state.stream.is_some(),
|
||||
move |dt| {
|
||||
let stream = dt
|
||||
.state
|
||||
.api_state
|
||||
.stream
|
||||
.as_ref()
|
||||
.expect("stream doesn't exist");
|
||||
let mut stream = stream.lock().expect("couldn't lock stream");
|
||||
for callback_id in dt.state.config.output_callback_ids.iter() {
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::CallCallback {
|
||||
callback_id: *callback_id,
|
||||
args: Some(Args::ConnectForAllOutputs {
|
||||
output_name: clone.name(),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.expect("Send to client failed");
|
||||
}
|
||||
for grpc_sender in dt.state.config.grpc_output_callback_senders.iter() {
|
||||
let _ = grpc_sender.send(Ok(ConnectForAllResponse {
|
||||
output_name: Some(clone.name()),
|
||||
}));
|
||||
}
|
||||
},
|
||||
);
|
||||
for sender in self.config.output_callback_senders.iter() {
|
||||
let _ = sender.send(Ok(ConnectForAllResponse {
|
||||
output_name: Some(output.name()),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
use crate::{
|
||||
api::{
|
||||
msg::ModifierMask,
|
||||
protocol::{
|
||||
InputService, OutputService, PinnacleService, ProcessService, TagService, WindowService,
|
||||
},
|
||||
PinnacleSocketSource,
|
||||
InputService, OutputService, PinnacleService, ProcessService, TagService, WindowService,
|
||||
},
|
||||
input::ModifierMask,
|
||||
output::OutputName,
|
||||
tag::Tag,
|
||||
window::rules::{WindowRule, WindowRuleCondition},
|
||||
|
@ -14,7 +11,6 @@ use std::{
|
|||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
process::Stdio,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
|
@ -32,9 +28,9 @@ use smithay::{
|
|||
utils::{Logical, Point},
|
||||
};
|
||||
use sysinfo::ProcessRefreshKind;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use toml::Table;
|
||||
|
||||
use crate::api::msg::{CallbackId, Modifier};
|
||||
use xkbcommon::xkb::Keysym;
|
||||
|
||||
use crate::{
|
||||
|
@ -57,8 +53,34 @@ pub struct Metaconfig {
|
|||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
pub struct Keybind {
|
||||
pub modifiers: Vec<Modifier>,
|
||||
pub key: Key,
|
||||
modifiers: Vec<Modifier>,
|
||||
key: Key,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug, Clone, Copy)]
|
||||
enum Modifier {
|
||||
Shift,
|
||||
Ctrl,
|
||||
Alt,
|
||||
Super,
|
||||
}
|
||||
|
||||
// TODO: refactor metaconfig input
|
||||
impl From<Vec<self::Modifier>> for ModifierMask {
|
||||
fn from(mods: Vec<self::Modifier>) -> Self {
|
||||
let mut mask = ModifierMask::empty();
|
||||
|
||||
for m in mods {
|
||||
match m {
|
||||
Modifier::Shift => mask |= ModifierMask::SHIFT,
|
||||
Modifier::Ctrl => mask |= ModifierMask::CTRL,
|
||||
Modifier::Alt => mask |= ModifierMask::ALT,
|
||||
Modifier::Super => mask |= ModifierMask::SUPER,
|
||||
}
|
||||
}
|
||||
|
||||
mask
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: accept xkbcommon names instead
|
||||
|
@ -141,10 +163,7 @@ pub enum Key {
|
|||
pub struct Config {
|
||||
/// Window rules and conditions on when those rules should apply
|
||||
pub window_rules: Vec<(WindowRuleCondition, WindowRule)>,
|
||||
/// All callbacks that should be run when outputs are connected
|
||||
pub output_callback_ids: Vec<CallbackId>,
|
||||
pub grpc_output_callback_senders:
|
||||
Vec<tokio::sync::mpsc::UnboundedSender<Result<ConnectForAllResponse, tonic::Status>>>,
|
||||
pub output_callback_senders: Vec<UnboundedSender<Result<ConnectForAllResponse, tonic::Status>>>,
|
||||
/// Saved states when outputs are disconnected
|
||||
pub connector_saved_states: HashMap<OutputName, ConnectorSavedState>,
|
||||
}
|
||||
|
@ -214,13 +233,6 @@ impl State {
|
|||
config_join_handle.abort();
|
||||
}
|
||||
|
||||
if let Some(token) = self.api_state.socket_token {
|
||||
// Should only happen if parsing the metaconfig failed
|
||||
self.loop_handle.remove(token);
|
||||
}
|
||||
|
||||
let tx_channel = self.api_state.tx_channel.clone();
|
||||
|
||||
// Love that trailing slash
|
||||
let data_home = PathBuf::from(
|
||||
crate::XDG_BASE_DIRS
|
||||
|
@ -255,19 +267,6 @@ impl State {
|
|||
|
||||
self.start_grpc_server(socket_dir.as_path())?;
|
||||
|
||||
self.system_processes
|
||||
.refresh_processes_specifics(ProcessRefreshKind::new());
|
||||
|
||||
let multiple_instances = self
|
||||
.system_processes
|
||||
.processes_by_exact_name("pinnacle")
|
||||
.filter(|proc| proc.thread_kind().is_none())
|
||||
.count()
|
||||
> 1;
|
||||
|
||||
let socket_source = PinnacleSocketSource::new(tx_channel, &socket_dir, multiple_instances)
|
||||
.context("Failed to create socket source")?;
|
||||
|
||||
let reload_keybind = metaconfig.reload_keybind;
|
||||
let kill_keybind = metaconfig.kill_keybind;
|
||||
|
||||
|
@ -324,28 +323,9 @@ impl State {
|
|||
let reload_keybind = (reload_mask, Keysym::from(reload_keybind.key as u32));
|
||||
let kill_keybind = (kill_mask, Keysym::from(kill_keybind.key as u32));
|
||||
|
||||
let socket_token = self
|
||||
.loop_handle
|
||||
.insert_source(socket_source, |stream, _, data| {
|
||||
if let Some(old_stream) = data
|
||||
.state
|
||||
.api_state
|
||||
.stream
|
||||
.replace(Arc::new(Mutex::new(stream)))
|
||||
{
|
||||
old_stream
|
||||
.lock()
|
||||
.expect("Couldn't lock old stream")
|
||||
.shutdown(std::net::Shutdown::Both)
|
||||
.expect("Couldn't shutdown old stream");
|
||||
}
|
||||
})?;
|
||||
|
||||
self.input_state.reload_keybind = Some(reload_keybind);
|
||||
self.input_state.kill_keybind = Some(kill_keybind);
|
||||
|
||||
self.api_state.socket_token = Some(socket_token);
|
||||
|
||||
self.config_join_handle = Some(tokio::spawn(async move {
|
||||
let _ = child.wait().await;
|
||||
}));
|
||||
|
|
138
src/input.rs
138
src/input.rs
|
@ -4,12 +4,7 @@ pub mod libinput;
|
|||
|
||||
use std::{collections::HashMap, mem::Discriminant};
|
||||
|
||||
use crate::{
|
||||
api::msg::{CallbackId, Modifier, MouseEdge, OutgoingMsg},
|
||||
focus::FocusTarget,
|
||||
state::WithState,
|
||||
window::WindowElement,
|
||||
};
|
||||
use crate::{focus::FocusTarget, state::WithState, window::WindowElement};
|
||||
use pinnacle_api_defs::pinnacle::input::v0alpha1::{
|
||||
set_libinput_setting_request::Setting, set_mousebind_request, SetKeybindResponse,
|
||||
SetMousebindResponse,
|
||||
|
@ -33,8 +28,6 @@ use xkbcommon::xkb::Keysym;
|
|||
|
||||
use crate::state::State;
|
||||
|
||||
use self::libinput::LibinputSetting;
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Debug, Hash, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct ModifierMask: u8 {
|
||||
|
@ -85,39 +78,30 @@ impl From<&ModifiersState> for ModifierMask {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct InputState {
|
||||
/// A hashmap of modifier keys and keycodes to callback IDs
|
||||
pub keybinds: HashMap<(crate::api::msg::ModifierMask, Keysym), CallbackId>,
|
||||
/// A hashmap of modifier keys and mouse button codes to callback IDs
|
||||
pub mousebinds: HashMap<(crate::api::msg::ModifierMask, u32, MouseEdge), CallbackId>,
|
||||
pub reload_keybind: Option<(crate::api::msg::ModifierMask, Keysym)>,
|
||||
pub kill_keybind: Option<(crate::api::msg::ModifierMask, Keysym)>,
|
||||
/// User defined libinput settings that will be applied
|
||||
pub libinput_settings: Vec<LibinputSetting>,
|
||||
pub reload_keybind: Option<(ModifierMask, Keysym)>,
|
||||
pub kill_keybind: Option<(ModifierMask, Keysym)>,
|
||||
/// All libinput devices that have been connected
|
||||
pub libinput_devices: Vec<input::Device>,
|
||||
|
||||
pub grpc_keybinds:
|
||||
pub keybinds:
|
||||
HashMap<(ModifierMask, Keysym), UnboundedSender<Result<SetKeybindResponse, tonic::Status>>>,
|
||||
pub grpc_mousebinds: HashMap<
|
||||
pub mousebinds: HashMap<
|
||||
(ModifierMask, u32, set_mousebind_request::MouseEdge),
|
||||
UnboundedSender<Result<SetMousebindResponse, tonic::Status>>,
|
||||
>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub grpc_libinput_settings:
|
||||
HashMap<Discriminant<Setting>, Box<dyn Fn(&mut input::Device) + Send>>,
|
||||
pub libinput_settings: HashMap<Discriminant<Setting>, Box<dyn Fn(&mut input::Device) + Send>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for InputState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("InputState")
|
||||
.field("keybinds", &self.keybinds)
|
||||
.field("mousebinds", &self.mousebinds)
|
||||
.field("reload_keybind", &self.reload_keybind)
|
||||
.field("kill_keybind", &self.kill_keybind)
|
||||
.field("libinput_settings", &self.libinput_settings)
|
||||
.field("libinput_devices", &self.libinput_devices)
|
||||
.field("grpc_keybinds", &self.grpc_keybinds)
|
||||
.field("grpc_libinput_settings", &"...")
|
||||
.field("keybinds", &self.keybinds)
|
||||
.field("mousebinds", &self.mousebinds)
|
||||
.field("libinput_settings", &"...")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -130,9 +114,7 @@ impl InputState {
|
|||
|
||||
#[derive(Debug)]
|
||||
enum KeyAction {
|
||||
/// Call a callback from a config process
|
||||
CallCallback(CallbackId),
|
||||
CallGrpcCallback(UnboundedSender<Result<SetKeybindResponse, tonic::Status>>),
|
||||
CallCallback(UnboundedSender<Result<SetKeybindResponse, tonic::Status>>),
|
||||
Quit,
|
||||
SwitchVt(i32),
|
||||
ReloadConfig,
|
||||
|
@ -257,59 +239,23 @@ impl State {
|
|||
|state, modifiers, keysym| {
|
||||
// tracing::debug!(keysym = ?keysym, raw_keysyms = ?keysym.raw_syms(), modified_syms = ?keysym.modified_syms());
|
||||
if press_state == KeyState::Pressed {
|
||||
let mut modifier_mask = Vec::<Modifier>::new();
|
||||
if modifiers.alt {
|
||||
modifier_mask.push(Modifier::Alt);
|
||||
}
|
||||
if modifiers.shift {
|
||||
modifier_mask.push(Modifier::Shift);
|
||||
}
|
||||
if modifiers.ctrl {
|
||||
modifier_mask.push(Modifier::Ctrl);
|
||||
}
|
||||
if modifiers.logo {
|
||||
modifier_mask.push(Modifier::Super);
|
||||
}
|
||||
let modifier_mask = crate::api::msg::ModifierMask::from(modifier_mask);
|
||||
|
||||
let grpc_modifiers = ModifierMask::from(modifiers);
|
||||
let mod_mask = ModifierMask::from(modifiers);
|
||||
|
||||
let raw_sym = keysym.raw_syms().iter().next();
|
||||
let mod_sym = keysym.modified_sym();
|
||||
|
||||
if let (Some(sender), _) | (None, Some(sender)) = (
|
||||
state
|
||||
.input_state
|
||||
.grpc_keybinds
|
||||
.get(&(grpc_modifiers, mod_sym)),
|
||||
state.input_state.keybinds.get(&(mod_mask, mod_sym)),
|
||||
raw_sym.and_then(|raw_sym| {
|
||||
state
|
||||
.input_state
|
||||
.grpc_keybinds
|
||||
.get(&(grpc_modifiers, *raw_sym))
|
||||
state.input_state.keybinds.get(&(mod_mask, *raw_sym))
|
||||
}),
|
||||
) {
|
||||
return FilterResult::Intercept(KeyAction::CallGrpcCallback(
|
||||
sender.clone(),
|
||||
));
|
||||
return FilterResult::Intercept(KeyAction::CallCallback(sender.clone()));
|
||||
}
|
||||
|
||||
let cb_id_mod = state.input_state.keybinds.get(&(modifier_mask, mod_sym));
|
||||
|
||||
let cb_id_raw = raw_sym.and_then(|raw_sym| {
|
||||
state.input_state.keybinds.get(&(modifier_mask, *raw_sym))
|
||||
});
|
||||
|
||||
match (cb_id_mod, cb_id_raw) {
|
||||
(Some(cb_id), _) | (None, Some(cb_id)) => {
|
||||
return FilterResult::Intercept(KeyAction::CallCallback(*cb_id));
|
||||
}
|
||||
(None, None) => (),
|
||||
}
|
||||
|
||||
if kill_keybind == Some((modifier_mask, mod_sym)) {
|
||||
if kill_keybind == Some((mod_mask, mod_sym)) {
|
||||
return FilterResult::Intercept(KeyAction::Quit);
|
||||
} else if reload_keybind == Some((modifier_mask, mod_sym)) {
|
||||
} else if reload_keybind == Some((mod_mask, mod_sym)) {
|
||||
return FilterResult::Intercept(KeyAction::ReloadConfig);
|
||||
} else if let mut vt @ keysyms::KEY_XF86Switch_VT_1
|
||||
..=keysyms::KEY_XF86Switch_VT_12 = keysym.modified_sym().raw()
|
||||
|
@ -325,20 +271,7 @@ impl State {
|
|||
);
|
||||
|
||||
match action {
|
||||
Some(KeyAction::CallCallback(callback_id)) => {
|
||||
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"),
|
||||
&OutgoingMsg::CallCallback {
|
||||
callback_id,
|
||||
args: None,
|
||||
},
|
||||
) {
|
||||
tracing::error!("error sending msg to client: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(KeyAction::CallGrpcCallback(sender)) => {
|
||||
Some(KeyAction::CallCallback(sender)) => {
|
||||
let _ = sender.send(Ok(SetKeybindResponse {}));
|
||||
}
|
||||
Some(KeyAction::SwitchVt(vt)) => {
|
||||
|
@ -367,42 +300,17 @@ impl State {
|
|||
|
||||
let pointer_loc = pointer.current_location();
|
||||
|
||||
let mod_mask = ModifierMask::from(keyboard.modifier_state());
|
||||
|
||||
let mouse_edge = match button_state {
|
||||
ButtonState::Released => MouseEdge::Release,
|
||||
ButtonState::Pressed => MouseEdge::Press,
|
||||
};
|
||||
let modifier_mask = crate::api::msg::ModifierMask::from(keyboard.modifier_state());
|
||||
|
||||
let grpc_modifier_mask = ModifierMask::from(keyboard.modifier_state());
|
||||
|
||||
// If any mousebinds are detected, call the config's callback and return.
|
||||
if let Some(&callback_id) =
|
||||
self.input_state
|
||||
.mousebinds
|
||||
.get(&(modifier_mask, button, mouse_edge))
|
||||
{
|
||||
if let Some(stream) = self.api_state.stream.as_ref() {
|
||||
crate::api::send_to_client(
|
||||
&mut stream.lock().expect("failed to lock api stream"),
|
||||
&OutgoingMsg::CallCallback {
|
||||
callback_id,
|
||||
args: None,
|
||||
},
|
||||
)
|
||||
.expect("failed to call callback");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let grpc_mouse_edge = match button_state {
|
||||
ButtonState::Released => set_mousebind_request::MouseEdge::Release,
|
||||
ButtonState::Pressed => set_mousebind_request::MouseEdge::Press,
|
||||
};
|
||||
|
||||
if let Some(stream) =
|
||||
self.input_state
|
||||
.grpc_mousebinds
|
||||
.get(&(grpc_modifier_mask, button, grpc_mouse_edge))
|
||||
if let Some(stream) = self
|
||||
.input_state
|
||||
.mousebinds
|
||||
.get(&(mod_mask, button, mouse_edge))
|
||||
{
|
||||
let _ = stream.send(Ok(SetMousebindResponse {}));
|
||||
}
|
||||
|
|
|
@ -1,104 +1,7 @@
|
|||
use smithay::{
|
||||
backend::{input::InputEvent, libinput::LibinputInputBackend},
|
||||
reexports::input::{self, AccelProfile, ClickMethod, ScrollMethod, TapButtonMap},
|
||||
};
|
||||
use smithay::backend::{input::InputEvent, libinput::LibinputInputBackend};
|
||||
|
||||
use crate::state::State;
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
#[serde(remote = "AccelProfile")]
|
||||
enum AccelProfileDef {
|
||||
Flat,
|
||||
Adaptive,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
#[serde(remote = "ClickMethod")]
|
||||
enum ClickMethodDef {
|
||||
ButtonAreas,
|
||||
Clickfinger,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
#[serde(remote = "ScrollMethod")]
|
||||
enum ScrollMethodDef {
|
||||
NoScroll,
|
||||
TwoFinger,
|
||||
Edge,
|
||||
OnButtonDown,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
#[serde(remote = "TapButtonMap")]
|
||||
enum TapButtonMapDef {
|
||||
LeftRightMiddle,
|
||||
LeftMiddleRight,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone, serde::Deserialize)]
|
||||
pub enum LibinputSetting {
|
||||
#[serde(with = "AccelProfileDef")]
|
||||
AccelProfile(AccelProfile),
|
||||
AccelSpeed(f64),
|
||||
CalibrationMatrix([f32; 6]),
|
||||
#[serde(with = "ClickMethodDef")]
|
||||
ClickMethod(ClickMethod),
|
||||
DisableWhileTypingEnabled(bool),
|
||||
LeftHanded(bool),
|
||||
MiddleEmulationEnabled(bool),
|
||||
RotationAngle(u32),
|
||||
#[serde(with = "ScrollMethodDef")]
|
||||
ScrollMethod(ScrollMethod),
|
||||
NaturalScrollEnabled(bool),
|
||||
ScrollButton(u32),
|
||||
#[serde(with = "TapButtonMapDef")]
|
||||
TapButtonMap(TapButtonMap),
|
||||
TapDragEnabled(bool),
|
||||
TapDragLockEnabled(bool),
|
||||
TapEnabled(bool),
|
||||
}
|
||||
|
||||
impl LibinputSetting {
|
||||
pub fn apply_to_device(&self, device: &mut input::Device) {
|
||||
let _ = match self {
|
||||
LibinputSetting::AccelProfile(profile) => device.config_accel_set_profile(*profile),
|
||||
LibinputSetting::AccelSpeed(speed) => device.config_accel_set_speed(*speed),
|
||||
LibinputSetting::CalibrationMatrix(matrix) => {
|
||||
device.config_calibration_set_matrix(*matrix)
|
||||
}
|
||||
LibinputSetting::ClickMethod(method) => device.config_click_set_method(*method),
|
||||
LibinputSetting::DisableWhileTypingEnabled(enabled) => {
|
||||
device.config_dwt_set_enabled(*enabled)
|
||||
}
|
||||
LibinputSetting::LeftHanded(enabled) => device.config_left_handed_set(*enabled),
|
||||
LibinputSetting::MiddleEmulationEnabled(enabled) => {
|
||||
device.config_middle_emulation_set_enabled(*enabled)
|
||||
}
|
||||
LibinputSetting::RotationAngle(angle) => device.config_rotation_set_angle(*angle),
|
||||
LibinputSetting::ScrollMethod(method) => device.config_scroll_set_method(*method),
|
||||
LibinputSetting::NaturalScrollEnabled(enabled) => {
|
||||
device.config_scroll_set_natural_scroll_enabled(*enabled)
|
||||
}
|
||||
LibinputSetting::ScrollButton(button) => device.config_scroll_set_button(*button),
|
||||
LibinputSetting::TapButtonMap(map) => device.config_tap_set_button_map(*map),
|
||||
LibinputSetting::TapDragEnabled(enabled) => {
|
||||
device.config_tap_set_drag_enabled(*enabled)
|
||||
}
|
||||
LibinputSetting::TapDragLockEnabled(enabled) => {
|
||||
device.config_tap_set_drag_lock_enabled(*enabled)
|
||||
}
|
||||
LibinputSetting::TapEnabled(enabled) => device.config_tap_set_enabled(*enabled),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// We want to completely replace old settings, so we hash only the discriminant.
|
||||
impl std::hash::Hash for LibinputSetting {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
core::mem::discriminant(self).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Apply current libinput settings to new devices.
|
||||
pub fn apply_libinput_settings(&mut self, event: &InputEvent<LibinputInputBackend>) {
|
||||
|
@ -117,10 +20,7 @@ impl State {
|
|||
return;
|
||||
}
|
||||
|
||||
for setting in self.input_state.libinput_settings.iter() {
|
||||
setting.apply_to_device(&mut device);
|
||||
}
|
||||
for setting in self.input_state.grpc_libinput_settings.values() {
|
||||
for setting in self.input_state.libinput_settings.values() {
|
||||
setting(&mut device);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
//! While Pinnacle is not a library, this documentation serves to guide those who want to
|
||||
//! contribute or learn how building something like this works.
|
||||
|
||||
// #![deny(unused_imports)] // gonna force myself to keep stuff clean
|
||||
// #![deny(unused_imports)] // this has remained commented out for months lol
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
use clap::Parser;
|
||||
|
|
33
src/state.rs
33
src/state.rs
|
@ -1,22 +1,14 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use crate::{
|
||||
api::{msg::Msg, ApiState},
|
||||
backend::Backend,
|
||||
config::Config,
|
||||
cursor::Cursor,
|
||||
focus::FocusState,
|
||||
grab::resize_grab::ResizeSurfaceState,
|
||||
window::WindowElement,
|
||||
backend::Backend, config::Config, cursor::Cursor, focus::FocusState,
|
||||
grab::resize_grab::ResizeSurfaceState, window::WindowElement,
|
||||
};
|
||||
use smithay::{
|
||||
desktop::{PopupManager, Space},
|
||||
input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState},
|
||||
reexports::{
|
||||
calloop::{
|
||||
self, channel::Event, generic::Generic, Interest, LoopHandle, LoopSignal, Mode,
|
||||
PostAction,
|
||||
},
|
||||
calloop::{generic::Generic, Interest, LoopHandle, LoopSignal, Mode, PostAction},
|
||||
wayland_server::{
|
||||
backend::{ClientData, ClientId, DisconnectReason},
|
||||
protocol::wl_surface::WlSurface,
|
||||
|
@ -74,8 +66,6 @@ pub struct State {
|
|||
|
||||
/// The state of key and mousebinds along with libinput settings
|
||||
pub input_state: InputState,
|
||||
/// The state holding stuff dealing with the api, like the stream
|
||||
pub api_state: ApiState,
|
||||
/// Keeps track of the focus stack and focused output
|
||||
pub focus_state: FocusState,
|
||||
|
||||
|
@ -159,8 +149,6 @@ impl State {
|
|||
},
|
||||
)?;
|
||||
|
||||
let (tx_channel, rx_channel) = calloop::channel::channel::<Msg>();
|
||||
|
||||
loop_handle.insert_idle(|data| {
|
||||
if let Err(err) = data.state.start_config(crate::config::get_config_dir()) {
|
||||
panic!("failed to start config: {err}");
|
||||
|
@ -174,16 +162,6 @@ impl State {
|
|||
|
||||
seat.add_keyboard(XkbConfig::default(), 500, 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");
|
||||
});
|
||||
|
||||
let xwayland = {
|
||||
let (xwayland, channel) = XWayland::new(&display_handle);
|
||||
let clone = display_handle.clone();
|
||||
|
@ -253,11 +231,6 @@ impl State {
|
|||
layer_shell_state: WlrLayerShellState::new::<Self>(&display_handle),
|
||||
|
||||
input_state: InputState::new(),
|
||||
api_state: ApiState {
|
||||
stream: None,
|
||||
socket_token: None,
|
||||
tx_channel,
|
||||
},
|
||||
focus_state: FocusState::new(),
|
||||
|
||||
config: Config::default(),
|
||||
|
|
Loading…
Reference in a new issue