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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "paste"
|
|
||||||
version = "1.0.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
|
@ -1556,8 +1550,6 @@ dependencies = [
|
||||||
"pinnacle-api-defs",
|
"pinnacle-api-defs",
|
||||||
"prost",
|
"prost",
|
||||||
"prost-types",
|
"prost-types",
|
||||||
"rmp",
|
|
||||||
"rmp-serde",
|
|
||||||
"serde",
|
"serde",
|
||||||
"shellexpand",
|
"shellexpand",
|
||||||
"smithay",
|
"smithay",
|
||||||
|
@ -1868,28 +1860,6 @@ version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
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]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.23"
|
version = "0.1.23"
|
||||||
|
|
|
@ -19,8 +19,6 @@ thiserror = "1"
|
||||||
xcursor = { version = "0.3", optional = true }
|
xcursor = { version = "0.3", optional = true }
|
||||||
image = { version = "0.24", default-features = false, optional = true }
|
image = { version = "0.24", default-features = false, optional = true }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
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 }
|
x11rb = { version = "0.13", default-features = false, features = ["composite"], optional = true }
|
||||||
shellexpand = "3.1.0"
|
shellexpand = "3.1.0"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
|
|
2034
src/api.rs
2034
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::{
|
use crate::{
|
||||||
api::msg::{Args, OutgoingMsg},
|
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
config::ConnectorSavedState,
|
config::ConnectorSavedState,
|
||||||
output::OutputName,
|
output::OutputName,
|
||||||
|
@ -985,36 +984,11 @@ impl State {
|
||||||
output.with_state(|state| state.tags = tags.clone());
|
output.with_state(|state| state.tags = tags.clone());
|
||||||
} else {
|
} else {
|
||||||
// Run any output callbacks
|
// Run any output callbacks
|
||||||
let clone = output.clone();
|
for sender in self.config.output_callback_senders.iter() {
|
||||||
self.schedule(
|
let _ = sender.send(Ok(ConnectForAllResponse {
|
||||||
|dt| dt.state.api_state.stream.is_some(),
|
output_name: Some(output.name()),
|
||||||
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()),
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{
|
api::{
|
||||||
msg::ModifierMask,
|
|
||||||
protocol::{
|
|
||||||
InputService, OutputService, PinnacleService, ProcessService, TagService, WindowService,
|
InputService, OutputService, PinnacleService, ProcessService, TagService, WindowService,
|
||||||
},
|
},
|
||||||
PinnacleSocketSource,
|
input::ModifierMask,
|
||||||
},
|
|
||||||
output::OutputName,
|
output::OutputName,
|
||||||
tag::Tag,
|
tag::Tag,
|
||||||
window::rules::{WindowRule, WindowRuleCondition},
|
window::rules::{WindowRule, WindowRuleCondition},
|
||||||
|
@ -14,7 +11,6 @@ use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::Stdio,
|
process::Stdio,
|
||||||
sync::{Arc, Mutex},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
@ -32,9 +28,9 @@ use smithay::{
|
||||||
utils::{Logical, Point},
|
utils::{Logical, Point},
|
||||||
};
|
};
|
||||||
use sysinfo::ProcessRefreshKind;
|
use sysinfo::ProcessRefreshKind;
|
||||||
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
use toml::Table;
|
use toml::Table;
|
||||||
|
|
||||||
use crate::api::msg::{CallbackId, Modifier};
|
|
||||||
use xkbcommon::xkb::Keysym;
|
use xkbcommon::xkb::Keysym;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -57,8 +53,34 @@ pub struct Metaconfig {
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
pub struct Keybind {
|
pub struct Keybind {
|
||||||
pub modifiers: Vec<Modifier>,
|
modifiers: Vec<Modifier>,
|
||||||
pub key: Key,
|
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
|
// TODO: accept xkbcommon names instead
|
||||||
|
@ -141,10 +163,7 @@ pub enum Key {
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Window rules and conditions on when those rules should apply
|
/// Window rules and conditions on when those rules should apply
|
||||||
pub window_rules: Vec<(WindowRuleCondition, WindowRule)>,
|
pub window_rules: Vec<(WindowRuleCondition, WindowRule)>,
|
||||||
/// All callbacks that should be run when outputs are connected
|
pub output_callback_senders: Vec<UnboundedSender<Result<ConnectForAllResponse, tonic::Status>>>,
|
||||||
pub output_callback_ids: Vec<CallbackId>,
|
|
||||||
pub grpc_output_callback_senders:
|
|
||||||
Vec<tokio::sync::mpsc::UnboundedSender<Result<ConnectForAllResponse, tonic::Status>>>,
|
|
||||||
/// Saved states when outputs are disconnected
|
/// Saved states when outputs are disconnected
|
||||||
pub connector_saved_states: HashMap<OutputName, ConnectorSavedState>,
|
pub connector_saved_states: HashMap<OutputName, ConnectorSavedState>,
|
||||||
}
|
}
|
||||||
|
@ -214,13 +233,6 @@ impl State {
|
||||||
config_join_handle.abort();
|
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
|
// Love that trailing slash
|
||||||
let data_home = PathBuf::from(
|
let data_home = PathBuf::from(
|
||||||
crate::XDG_BASE_DIRS
|
crate::XDG_BASE_DIRS
|
||||||
|
@ -255,19 +267,6 @@ impl State {
|
||||||
|
|
||||||
self.start_grpc_server(socket_dir.as_path())?;
|
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 reload_keybind = metaconfig.reload_keybind;
|
||||||
let kill_keybind = metaconfig.kill_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 reload_keybind = (reload_mask, Keysym::from(reload_keybind.key as u32));
|
||||||
let kill_keybind = (kill_mask, Keysym::from(kill_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.reload_keybind = Some(reload_keybind);
|
||||||
self.input_state.kill_keybind = Some(kill_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 {
|
self.config_join_handle = Some(tokio::spawn(async move {
|
||||||
let _ = child.wait().await;
|
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 std::{collections::HashMap, mem::Discriminant};
|
||||||
|
|
||||||
use crate::{
|
use crate::{focus::FocusTarget, state::WithState, window::WindowElement};
|
||||||
api::msg::{CallbackId, Modifier, MouseEdge, OutgoingMsg},
|
|
||||||
focus::FocusTarget,
|
|
||||||
state::WithState,
|
|
||||||
window::WindowElement,
|
|
||||||
};
|
|
||||||
use pinnacle_api_defs::pinnacle::input::v0alpha1::{
|
use pinnacle_api_defs::pinnacle::input::v0alpha1::{
|
||||||
set_libinput_setting_request::Setting, set_mousebind_request, SetKeybindResponse,
|
set_libinput_setting_request::Setting, set_mousebind_request, SetKeybindResponse,
|
||||||
SetMousebindResponse,
|
SetMousebindResponse,
|
||||||
|
@ -33,8 +28,6 @@ use xkbcommon::xkb::Keysym;
|
||||||
|
|
||||||
use crate::state::State;
|
use crate::state::State;
|
||||||
|
|
||||||
use self::libinput::LibinputSetting;
|
|
||||||
|
|
||||||
bitflags::bitflags! {
|
bitflags::bitflags! {
|
||||||
#[derive(Debug, Hash, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Hash, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct ModifierMask: u8 {
|
pub struct ModifierMask: u8 {
|
||||||
|
@ -85,39 +78,30 @@ impl From<&ModifiersState> for ModifierMask {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct InputState {
|
pub struct InputState {
|
||||||
/// A hashmap of modifier keys and keycodes to callback IDs
|
pub reload_keybind: Option<(ModifierMask, Keysym)>,
|
||||||
pub keybinds: HashMap<(crate::api::msg::ModifierMask, Keysym), CallbackId>,
|
pub kill_keybind: Option<(ModifierMask, Keysym)>,
|
||||||
/// 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>,
|
|
||||||
/// All libinput devices that have been connected
|
/// All libinput devices that have been connected
|
||||||
pub libinput_devices: Vec<input::Device>,
|
pub libinput_devices: Vec<input::Device>,
|
||||||
|
|
||||||
pub grpc_keybinds:
|
pub keybinds:
|
||||||
HashMap<(ModifierMask, Keysym), UnboundedSender<Result<SetKeybindResponse, tonic::Status>>>,
|
HashMap<(ModifierMask, Keysym), UnboundedSender<Result<SetKeybindResponse, tonic::Status>>>,
|
||||||
pub grpc_mousebinds: HashMap<
|
pub mousebinds: HashMap<
|
||||||
(ModifierMask, u32, set_mousebind_request::MouseEdge),
|
(ModifierMask, u32, set_mousebind_request::MouseEdge),
|
||||||
UnboundedSender<Result<SetMousebindResponse, tonic::Status>>,
|
UnboundedSender<Result<SetMousebindResponse, tonic::Status>>,
|
||||||
>,
|
>,
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
pub grpc_libinput_settings:
|
pub libinput_settings: HashMap<Discriminant<Setting>, Box<dyn Fn(&mut input::Device) + Send>>,
|
||||||
HashMap<Discriminant<Setting>, Box<dyn Fn(&mut input::Device) + Send>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for InputState {
|
impl std::fmt::Debug for InputState {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("InputState")
|
f.debug_struct("InputState")
|
||||||
.field("keybinds", &self.keybinds)
|
|
||||||
.field("mousebinds", &self.mousebinds)
|
|
||||||
.field("reload_keybind", &self.reload_keybind)
|
.field("reload_keybind", &self.reload_keybind)
|
||||||
.field("kill_keybind", &self.kill_keybind)
|
.field("kill_keybind", &self.kill_keybind)
|
||||||
.field("libinput_settings", &self.libinput_settings)
|
|
||||||
.field("libinput_devices", &self.libinput_devices)
|
.field("libinput_devices", &self.libinput_devices)
|
||||||
.field("grpc_keybinds", &self.grpc_keybinds)
|
.field("keybinds", &self.keybinds)
|
||||||
.field("grpc_libinput_settings", &"...")
|
.field("mousebinds", &self.mousebinds)
|
||||||
|
.field("libinput_settings", &"...")
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,9 +114,7 @@ impl InputState {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum KeyAction {
|
enum KeyAction {
|
||||||
/// Call a callback from a config process
|
CallCallback(UnboundedSender<Result<SetKeybindResponse, tonic::Status>>),
|
||||||
CallCallback(CallbackId),
|
|
||||||
CallGrpcCallback(UnboundedSender<Result<SetKeybindResponse, tonic::Status>>),
|
|
||||||
Quit,
|
Quit,
|
||||||
SwitchVt(i32),
|
SwitchVt(i32),
|
||||||
ReloadConfig,
|
ReloadConfig,
|
||||||
|
@ -257,59 +239,23 @@ impl State {
|
||||||
|state, modifiers, keysym| {
|
|state, modifiers, keysym| {
|
||||||
// tracing::debug!(keysym = ?keysym, raw_keysyms = ?keysym.raw_syms(), modified_syms = ?keysym.modified_syms());
|
// tracing::debug!(keysym = ?keysym, raw_keysyms = ?keysym.raw_syms(), modified_syms = ?keysym.modified_syms());
|
||||||
if press_state == KeyState::Pressed {
|
if press_state == KeyState::Pressed {
|
||||||
let mut modifier_mask = Vec::<Modifier>::new();
|
let mod_mask = ModifierMask::from(modifiers);
|
||||||
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 raw_sym = keysym.raw_syms().iter().next();
|
let raw_sym = keysym.raw_syms().iter().next();
|
||||||
let mod_sym = keysym.modified_sym();
|
let mod_sym = keysym.modified_sym();
|
||||||
|
|
||||||
if let (Some(sender), _) | (None, Some(sender)) = (
|
if let (Some(sender), _) | (None, Some(sender)) = (
|
||||||
state
|
state.input_state.keybinds.get(&(mod_mask, mod_sym)),
|
||||||
.input_state
|
|
||||||
.grpc_keybinds
|
|
||||||
.get(&(grpc_modifiers, mod_sym)),
|
|
||||||
raw_sym.and_then(|raw_sym| {
|
raw_sym.and_then(|raw_sym| {
|
||||||
state
|
state.input_state.keybinds.get(&(mod_mask, *raw_sym))
|
||||||
.input_state
|
|
||||||
.grpc_keybinds
|
|
||||||
.get(&(grpc_modifiers, *raw_sym))
|
|
||||||
}),
|
}),
|
||||||
) {
|
) {
|
||||||
return FilterResult::Intercept(KeyAction::CallGrpcCallback(
|
return FilterResult::Intercept(KeyAction::CallCallback(sender.clone()));
|
||||||
sender.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let cb_id_mod = state.input_state.keybinds.get(&(modifier_mask, mod_sym));
|
if kill_keybind == Some((mod_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)) {
|
|
||||||
return FilterResult::Intercept(KeyAction::Quit);
|
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);
|
return FilterResult::Intercept(KeyAction::ReloadConfig);
|
||||||
} else if let mut vt @ keysyms::KEY_XF86Switch_VT_1
|
} else if let mut vt @ keysyms::KEY_XF86Switch_VT_1
|
||||||
..=keysyms::KEY_XF86Switch_VT_12 = keysym.modified_sym().raw()
|
..=keysyms::KEY_XF86Switch_VT_12 = keysym.modified_sym().raw()
|
||||||
|
@ -325,20 +271,7 @@ impl State {
|
||||||
);
|
);
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
Some(KeyAction::CallCallback(callback_id)) => {
|
Some(KeyAction::CallCallback(sender)) => {
|
||||||
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)) => {
|
|
||||||
let _ = sender.send(Ok(SetKeybindResponse {}));
|
let _ = sender.send(Ok(SetKeybindResponse {}));
|
||||||
}
|
}
|
||||||
Some(KeyAction::SwitchVt(vt)) => {
|
Some(KeyAction::SwitchVt(vt)) => {
|
||||||
|
@ -367,42 +300,17 @@ impl State {
|
||||||
|
|
||||||
let pointer_loc = pointer.current_location();
|
let pointer_loc = pointer.current_location();
|
||||||
|
|
||||||
|
let mod_mask = ModifierMask::from(keyboard.modifier_state());
|
||||||
|
|
||||||
let mouse_edge = match button_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::Released => set_mousebind_request::MouseEdge::Release,
|
||||||
ButtonState::Pressed => set_mousebind_request::MouseEdge::Press,
|
ButtonState::Pressed => set_mousebind_request::MouseEdge::Press,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(stream) =
|
if let Some(stream) = self
|
||||||
self.input_state
|
.input_state
|
||||||
.grpc_mousebinds
|
.mousebinds
|
||||||
.get(&(grpc_modifier_mask, button, grpc_mouse_edge))
|
.get(&(mod_mask, button, mouse_edge))
|
||||||
{
|
{
|
||||||
let _ = stream.send(Ok(SetMousebindResponse {}));
|
let _ = stream.send(Ok(SetMousebindResponse {}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,104 +1,7 @@
|
||||||
use smithay::{
|
use smithay::backend::{input::InputEvent, libinput::LibinputInputBackend};
|
||||||
backend::{input::InputEvent, libinput::LibinputInputBackend},
|
|
||||||
reexports::input::{self, AccelProfile, ClickMethod, ScrollMethod, TapButtonMap},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::state::State;
|
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 {
|
impl State {
|
||||||
/// Apply current libinput settings to new devices.
|
/// Apply current libinput settings to new devices.
|
||||||
pub fn apply_libinput_settings(&mut self, event: &InputEvent<LibinputInputBackend>) {
|
pub fn apply_libinput_settings(&mut self, event: &InputEvent<LibinputInputBackend>) {
|
||||||
|
@ -117,10 +20,7 @@ impl State {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for setting in self.input_state.libinput_settings.iter() {
|
for setting in self.input_state.libinput_settings.values() {
|
||||||
setting.apply_to_device(&mut device);
|
|
||||||
}
|
|
||||||
for setting in self.input_state.grpc_libinput_settings.values() {
|
|
||||||
setting(&mut device);
|
setting(&mut device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
//! While Pinnacle is not a library, this documentation serves to guide those who want to
|
//! While Pinnacle is not a library, this documentation serves to guide those who want to
|
||||||
//! contribute or learn how building something like this works.
|
//! 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)]
|
#![warn(clippy::unwrap_used)]
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
33
src/state.rs
33
src/state.rs
|
@ -1,22 +1,14 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{msg::Msg, ApiState},
|
backend::Backend, config::Config, cursor::Cursor, focus::FocusState,
|
||||||
backend::Backend,
|
grab::resize_grab::ResizeSurfaceState, window::WindowElement,
|
||||||
config::Config,
|
|
||||||
cursor::Cursor,
|
|
||||||
focus::FocusState,
|
|
||||||
grab::resize_grab::ResizeSurfaceState,
|
|
||||||
window::WindowElement,
|
|
||||||
};
|
};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
desktop::{PopupManager, Space},
|
desktop::{PopupManager, Space},
|
||||||
input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState},
|
input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState},
|
||||||
reexports::{
|
reexports::{
|
||||||
calloop::{
|
calloop::{generic::Generic, Interest, LoopHandle, LoopSignal, Mode, PostAction},
|
||||||
self, channel::Event, generic::Generic, Interest, LoopHandle, LoopSignal, Mode,
|
|
||||||
PostAction,
|
|
||||||
},
|
|
||||||
wayland_server::{
|
wayland_server::{
|
||||||
backend::{ClientData, ClientId, DisconnectReason},
|
backend::{ClientData, ClientId, DisconnectReason},
|
||||||
protocol::wl_surface::WlSurface,
|
protocol::wl_surface::WlSurface,
|
||||||
|
@ -74,8 +66,6 @@ pub struct State {
|
||||||
|
|
||||||
/// The state of key and mousebinds along with libinput settings
|
/// The state of key and mousebinds along with libinput settings
|
||||||
pub input_state: InputState,
|
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
|
/// Keeps track of the focus stack and focused output
|
||||||
pub focus_state: FocusState,
|
pub focus_state: FocusState,
|
||||||
|
|
||||||
|
@ -159,8 +149,6 @@ impl State {
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let (tx_channel, rx_channel) = calloop::channel::channel::<Msg>();
|
|
||||||
|
|
||||||
loop_handle.insert_idle(|data| {
|
loop_handle.insert_idle(|data| {
|
||||||
if let Err(err) = data.state.start_config(crate::config::get_config_dir()) {
|
if let Err(err) = data.state.start_config(crate::config::get_config_dir()) {
|
||||||
panic!("failed to start config: {err}");
|
panic!("failed to start config: {err}");
|
||||||
|
@ -174,16 +162,6 @@ impl State {
|
||||||
|
|
||||||
seat.add_keyboard(XkbConfig::default(), 500, 25)?;
|
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 = {
|
||||||
let (xwayland, channel) = XWayland::new(&display_handle);
|
let (xwayland, channel) = XWayland::new(&display_handle);
|
||||||
let clone = display_handle.clone();
|
let clone = display_handle.clone();
|
||||||
|
@ -253,11 +231,6 @@ impl State {
|
||||||
layer_shell_state: WlrLayerShellState::new::<Self>(&display_handle),
|
layer_shell_state: WlrLayerShellState::new::<Self>(&display_handle),
|
||||||
|
|
||||||
input_state: InputState::new(),
|
input_state: InputState::new(),
|
||||||
api_state: ApiState {
|
|
||||||
stream: None,
|
|
||||||
socket_token: None,
|
|
||||||
tx_channel,
|
|
||||||
},
|
|
||||||
focus_state: FocusState::new(),
|
focus_state: FocusState::new(),
|
||||||
|
|
||||||
config: Config::default(),
|
config: Config::default(),
|
||||||
|
|
Loading…
Reference in a new issue